CouchDB Conflict Resolution: Automatic vs Explicit Merging
CouchDB MVCC model lets concurrent writes coexist, but conflict handling choices affect data integrity. This guide compares automatic and explicit merging, outlines trade-offs, and shows how to detect and resolve conflicts using ?revs=true.
10 Oct 2025, 18:13 UTC

Decision Point – Auto-Resolution vs Explicit Merging in CouchDB
CouchDB MVCC model lets any number of readers access a document revision without blocking writers, and lets writers append new revisions without blocking readers. This design is ideal for high-throughput, intermittently connected, or sync-driven workloads. However, when two or more clients write to the same document concurrently, CouchDB creates separate revisions identified by distinct _rev values. The platform does not prevent divergent data; it surfaces the conflict and lets you decide how to resolve it.
Decision & Constraints
- Consistency tolerance: Can your application accept a temporary winner selected by CouchDB internal algorithm, or does data require semantic merging?
- Latency budget: Automatic resolution adds zero round trips, but explicit merging requires application code path and possibly a second write.
- Complexity budget: Implementing a robust merge function increases code surface area; relying on CouchDB default reduces engineering effort but risks overwriting intent.
- Replication topology: In conflict-replicating topologies (most CouchDB setups), conflicts will surface on sync. The choice affects how they propagate.
Supported Options Comparison
| Option | How it works | When it shines | Risks |
|---|---|---|---|
| Automatic resolution | CouchDB selects one revision as the winning based on internal tie-breaking (typically lexicographic _rev order). The losing revision becomes a conflict sibling, retained in the document revision tree. | Rapid prototyping, read-heavy workloads where last-write-wins semantics are acceptable, or when downstream systems can tolerate occasional data siloing. | May discard intentional updates; application must later detect and manually merge if the chosen winner does not match business logic. |
| Explicit merging | Your application code reads the full revision tree (via ?revs=true), identifies divergent revisions, merges fields per business rules, and writes a single new revision. | Financial records, user-generated content with structured fields, or any domain where losing an update is unacceptable. | Higher code complexity, potential for merge bugs, extra write latency, and the need for idempotent update logic. |
Trade-offs at a Glance
Automatic is zero-maintenance but black-box. If your use case can tolerate last-write-wins, it is the fastest path. If not, you will spend debugging surprise data loss later.
Explicit gives you control and guarantees that merged state reflects your domain logic, but it shifts responsibility to your code. You will need to handle cases where the merge itself produces a conflict, and you must ensure the merged revision is saved without re-triggering the same conflict.
Concrete Detection and Validation Workflow
CouchDB makes conflict visibility straightforward. The following steps demonstrate how to list all revisions of a document, spot conflicts, and validate the revision tree.
Prerequisites
- A running CouchDB instance (default http://127.0.0.1:5984)
- curl or any HTTP client
- Read (or admin) access to the target database
Step 1: Create a baseline document
curl -X PUT "http://127.0.0.1:5984/products/widget-42" \
-H "Content-Type: application/json" \
-d '{"name":"Widget A","price":10}'Response includes id and rev. Keep the rev value; it is the starting revision for conflict demonstrations.
Step 2: Simulate a concurrent write
In a real sync scenario, two replicas update the same doc offline. CouchDB will automatically retain both revisions as siblings when they are merged. For demonstration, the standard path is to let two independent clients POST updates; CouchDB will generate new _rev values and mark them as conflicts.
Step 3: Fetch the revision tree with conflict details
curl "http://127.0.0.1:5984/products/widget-42?revs=true"
Expected JSON includes a _revs array listing all known revisions, and if conflicts exist, a _conflicts array listing the _rev values of sibling revisions. Each sibling represents a divergent write that CouchDB retained because it could not auto-resolve cleanly.
Step 4: Check for conflicts after a second concurrent write
If two clients independently send {"name":"Widget B","price":12} to the same doc, CouchDB will keep both as conflicts. Running the request from Step 3 will show "_conflicts":["3-rev2","4-rev3"] (exact values depend on internal ordering). The original revision remains accessible via its _rev.
Step 5: Validate the revision tree structure
Use the returned _revs to iterate each revision via GET /{db}/{doc}?rev={id}. CouchDB MVCC model guarantees that readers never block writers: a GET on any _rev returns a consistent snapshot of that revision state, regardless of in-flight writes.
Hands-on: Explicit Merge Example (Node.js-style Pseudocode)
The following pseudocode illustrates how an application might read the revision tree, perform a simple merge, and write a new revision. It is illustrative only and does not constitute a tested or production-ready merge strategy.
const fetch = require('node-fetch');
async function mergeDocument(db, docId) {
const resp = await fetch('http://127.0.0.1:5984/' + db + '/' + docId + '?revs=true');
const doc = await resp.json();
// doc._revs contains all known revision ids
// doc._conflicts lists sibling revisions that diverged
const revisions = await Promise.all(doc._revs.map(r =>
fetch('http://127.0.0.1:5984/' + db + '/' + docId + '?rev=' + r).then(r => r.json()))
));
// Simple merge: take the most recent price, keep the name from the earliest revision
const merged = revisions.reduce((acc, cur) => {
acc.price = Math.max(acc.price, cur.price);
if (!acc.name) acc.name = cur.name;
return acc;
}, {name: null, price: 0});
// Write the merged revision
await fetch('http://127.0.0.1:5984/' + db + '/' + docId, {
method: 'PUT',
headers: {'Content-Type':'application/json'},
body: JSON.stringify(merged)
});
}Important: This code is illustrative only. It does not constitute a tested or production-ready merge strategy. Real merges must account for field types, nested objects, and idempotency. CouchDB does not validate merge correctness; that responsibility rests entirely on your application.
Limitations & Practical Check
- Automatic resolution is semantic-blind: CouchDB will never know that you wanted to sum two quantities; it will merely pick one revision.
- Explicit merging shifts responsibility: If your merge function has bugs, you may introduce data corruption that replication will happily propagate.
- Check for conflicts: Run GET /{db}/{doc}?revs=true and inspect the _conflicts field. An empty array means the most recent _rev is the sole winner; a non-empty array means sibling revisions exist.
- Risk of silent data loss: If you rely on automatic resolution and later realize the wrong revision won, there is no built-in undo. You will need to manually re-introduce the lost data as a new revision.
Verification Checklist
- Run GET /{db}/{doc}?revs=true on a document that has received at least two independent writes.
- Confirm that _conflicts is non-empty when divergent revisions exist.
- Confirm that each listed _rev returns a valid document via GET /{db}/{doc}?rev={id}.
- If pursuing explicit merging, validate that your merge function produces a single new revision without immediately re-triggering a conflict (re-run ?revs=true after the merge write).
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.