Preventing Data Loss in CouchDB with Optimistic Concurrency Control
Learn how to use CouchDB's _rev tokens to implement optimistic concurrency control, preventing data loss from concurrent writes and handling HTTP 409 conflicts.
09 Jul 2025, 14:13 UTC

The Problem: Lost Updates in Concurrent Environments
When multiple clients attempt to update the same CouchDB document simultaneously, a last-write-wins scenario can occur. If Client A and Client B both read a document, modify it, and save it back, the client that saves last will silently overwrite the changes made by the first. This results in permanent data loss for the first client's update.
CouchDB solves this using Multi-Version Concurrency Control. By requiring a revision token _rev for every update, CouchDB ensures a client can only modify a document if they have the most recent version. If the version on the server has changed since the client last read it, the server rejects the write with HTTP 409 Conflict.
Prerequisites
- An active CouchDB instance v2.x or v3.x.
- A database with at least one existing document.
- An HTTP client such as curl or Postman with write permissions to the database. No admin privileges are required beyond write access.
Implementing the Update Workflow
To update a document without risking data loss, follow this sequence.
1. Fetch the Current State
Retrieve the document to obtain its current _id and _rev. The revision token is a string that uniquely identifies the current version.
# Run on your local terminal as a user with read access
curl -X GET http://admin:password@localhost:5984/my_database/doc_123
Expected Result: A JSON object containing the document data and a field like _rev: 1-abc123xyz.
2. Modify and Submit the Document
Apply your changes to the local JSON copy. When sending the PUT request, include the _rev value you just retrieved. If you omit it or provide an outdated one, the request will fail.
# Run on your local terminal as a user with write access
curl -X PUT http://admin:password@localhost:5984/my_database/doc_123 \
-H Content-Type: application/json \
-d '{"_id":"doc_123","_rev":"1-abc123xyz","status":"updated"}'
Expected Result: HTTP 201 Created with a new _rev value, e.g. 2-def456uvw.
Handling the 409 Conflict
A 409 Conflict occurs when the _rev provided in the PUT request does not match the current _rev on the server. This indicates another process updated the document while your client was processing the change.
Conflict Resolution Strategy
- Re-fetch: Perform a GET request to retrieve the latest version of the document.
- Merge: Integrate your intended changes with the new server-side data. Do not simply overwrite the server's data, as you will lose the changes made by the other client.
- Retry: Submit the PUT request again using the newly acquired
_rev.
Risk: The _rev token changes on every successful write, so caching or reusing an old revision after a failed attempt will repeatedly produce 409 conflicts.
Comparison: Standard Update vs. Conflict Scenario
| Scenario | Request _rev | Server _rev | Result | Outcome |
|---|---|---|---|---|
| Clean Update | 1-abc | 1-abc | 201 Created | Document updated; new _rev issued. |
| Stale Update | 1-abc | 2-def | 409 Conflict | Write rejected; data preserved. |
| Missing _rev | none | 2-def | 409 Conflict | Write rejected; requires revision. |
Limitations and Engineering Trade-offs
- Storage Overhead: CouchDB uses MVCC and keeps old revisions until database compaction runs. Frequent conflict-retry loops can increase disk usage.
- High Contention: If a single document is updated hundreds of times per second, the GET-Merge-PUT cycle becomes a bottleneck. Consider sharding data into multiple documents or an append-only pattern with a separate aggregation view.
- Cluster Quorum: In clustered CouchDB, a 409 may also appear if the write quorum is not met. Verify cluster health via _node/_local when conflicts appear unexpectedly.
- Deletes require _rev: Deleting a document also requires the current _rev in the query string or body; omitting it returns 409 and leaves the document intact.
Verification
To verify the guard is active:
- Fetch a document and note its _rev.
- Perform a successful update using that _rev.
- Attempt a second update using the original now stale _rev.
- Confirm the server returns 409 Conflict with a conflict error reason.
- Perform a final GET to ensure the document reflects the first successful update and not the rejected second attempt.
For bulk or offline scenarios, use _bulk_docs with new_edits=false to import historical revisions explicitly.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.