CouchDB’s stale views are a performance optimization that deliberately sacrifices immediate data consistency for faster query responses.
Let’s see this in action. Imagine a simple products database with documents like this:
{
"_id": "prod1",
"_rev": "1-a1b2c3d4e5f67890abcdef1234567890",
"name": "Awesome Widget",
"price": 19.99,
"category": "widgets"
}
We want to find all products in the "widgets" category. A standard view might look like this:
function (doc) {
if (doc.category) {
emit(doc.category, doc.name);
}
}
When you query this view for the first time after adding a new product, CouchDB has to build the index from scratch. This can be slow. The query URL would look like:
GET /mydb/_design/products/_view/by_category
CouchDB will respond with something like:
{
"total_rows": 1,
"offset": 0,
"rows": [
{
"key": "widgets",
"value": "Awesome Widget"
}
]
}
Now, if you immediately add another product to the "widgets" category:
{
"_id": "prod2",
"_rev": "1-b2c3d4e5f67890abcdef1234567890abcdef1234567890",
"name": "Super Widget",
"price": 25.50,
"category": "widgets"
}
And then query the same view again:
GET /mydb/_design/products/_view/by_category
CouchDB, by default, will notice that the view index is "stale" (meaning it might not reflect the latest changes) and will re-index before returning results. The response will be correct, but it might take longer than subsequent queries if the index is large.
This is where the stale=ok query parameter comes in. If you make the same request but add ?stale=ok:
GET /mydb/_design/products/_view/by_category?stale=ok
CouchDB will not wait to re-index. It will immediately return the results based on the last time the index was updated, even if new documents have been added since then. The response might look like this:
{
"total_rows": 1,
"offset": 0,
"rows": [
{
"key": "widgets",
"value": "Awesome Widget"
}
]
}
Notice that "Super Widget" is missing. The total_rows is also potentially inaccurate. This is the trade-off: speed for potential staleness.
The mental model here is that CouchDB maintains an index for each view. When documents change, the index becomes "dirty." For performance, CouchDB doesn’t always immediately clean up the index. Instead, when you query a view, it checks if the index is dirty. If it is, it can rebuild it. The stale parameter tells CouchDB, "I don’t care if it’s dirty; give me what you have now."
The stale parameter has three possible values:
ok: Returns results from the last built index, even if stale. This is the fastest option.update_after: Returns results from the last built index, and then triggers an index update in the background. The next query will get fresh results.- (Omitted): The default behavior. If the index is stale, CouchDB rebuilds it before returning results.
The core problem this solves is the latency introduced by view re-indexing in high-write environments. If your application can tolerate slightly outdated data for a brief period, using stale=ok can significantly improve read performance by avoiding the I/O and CPU cost of index updates on every query. You might use this for displaying lists of items where absolute real-time accuracy isn’t critical, or for dashboards that refresh periodically.
When you query a view with stale=ok, CouchDB doesn’t re-evaluate the view function for any documents that have been added or modified since the last index build. It simply reads from the existing B-tree structure that represents the view’s index. The total_rows and offset values reported in the response are also based on this stale index, meaning they might not accurately reflect the current state of the database.
The real danger with stale=ok is not just that you might miss a document, but that a document you do see might be an older version that has since been deleted or modified in a way that would remove it from the view’s results.
The next concept to explore is how to manage view updates more proactively when you need to eventually get fresh data, such as using update_after or implementing background view building.