What this usually means
WriteConflict is MongoDB's way of telling you that two concurrent operations tried to modify the same document or set of documents within a transaction. MongoDB uses optimistic concurrency control for multi-document transactions: it assumes conflicts are rare and only detects them at commit time. When a conflict occurs, the transaction aborts and must be retried by the application. The root cause is usually a design issue: either your application is holding transactions open too long, your data model causes hot-spotting (many transactions hitting the same document), or your retry logic is missing exponential backoff, flooding the server with retries that collide again.
The first ten minutes — establish facts before touching code.
- 1Run db.currentOp() on the mongod and filter by 'transaction' to see active transactions and their lock conflicts.
- 2Check MongoDB logs for lines containing 'WriteConflict' with transaction IDs; note the namespace and document key involved.
- 3Review application code for transaction boundaries: look for unnecessary reads/writes inside the transaction that extend its duration.
- 4Use mongostat or mongotop to see if there's a spike in 'wt' (wiredTiger) lock waits during the conflict period.
- 5Enable profiler (db.setProfilingLevel(2)) briefly on the affected collection to capture exact operations colliding.
- 6Inspect the retry logic: does it use exponential backoff? Are there random jitter? Is the max retry count reasonable?
The specific files, logs, configs, and dashboards that usually own this bug.
- searchApplication server logs: grep for 'WriteConflict' and surrounding stack traces.
- searchMongoDB server log (mongod.log): search for 'WriteConflict' and 'transaction' entries.
- searchdb.currentOp({ 'transaction': { $exists: true } }) output for each mongod in the replica set.
- searchWiredTiger stats: db.serverStatus().wiredTiger.concurrentTransactions.read/write ratios.
- searchMongoDB slow query log (if profiler enabled) for operations inside transactions.
- searchNetwork latency dashboards (if transactions span multiple shards) – higher latency increases conflict windows.
Practical causes, not theory. These are the things you will actually find.
- warningHot document: multiple transactions updating the same document concurrently (e.g., counters, leaderboards).
- warningLong-running transactions: holding a transaction open for >1s increases probability of conflict.
- warningMissing retry logic: application does not catch WriteConflict or retries without backoff, causing livelock.
- warningToo many writes per transaction: large number of operations inside a single transaction escalates lock contention.
- warningShard key collision: in sharded clusters, transactions spanning multiple shards use a distributed commit that is more conflict-prone.
- warningSecondary read preference in transaction: reading from secondaries inside a transaction may cause conflicts due to stale snapshots.
Concrete fix directions. Pick the one that matches your root cause.
- buildImplement a retry loop with exponential backoff and jitter; catch WriteConflict explicitly (error code 112) and retry.
- buildReduce transaction scope: move read-only operations outside the transaction, keep only necessary writes inside.
- buildUse document-level locking wisely: consider bucketing hot documents (e.g., split a counter into N sub-documents).
- buildShorten transaction duration: minimize idle time inside transaction boundaries; commit as soon as possible.
- buildAvoid reading from secondaries inside transactions—use primary read preference to ensure consistency.
- buildIf using sharded clusters, try to keep transaction operations within a single shard by choosing an appropriate shard key.
A fix you cannot prove is a guess. Close the loop.
- verifiedRun load test with concurrent transactions that previously caused WriteConflicts; observe zero errors after fix.
- verifiedCheck db.currentOp() during peak load—transaction count should be low and short-lived.
- verifiedMonitor mongod logs for 'WriteConflict' count; should drop to zero after fix.
- verifiedUse db.serverStatus().transactions metrics: 'retriedTransactionsCount' should stabilize.
- verifiedVerify application retry logs show at most 1-2 retries per transaction, not infinite loops.
Things that make this bug worse or harder to find.
- warningCatching WriteConflict and immediately retrying without backoff—causes livelock and server CPU spike.
- warningSetting maxTransactionLockRequestTimeoutMillis too high—transactions wait longer, increasing conflict window.
- warningUsing snapshot read concern unnecessarily—forces snapshot isolation which increases conflict probability.
- warningIgnoring WriteConflicts in sharded clusters—they may appear as 'TransactionAborted' errors with different codes.
- warningNot updating application driver—older drivers may have buggy retry logic or missing features.
- warningAssuming WriteConflict always means deadlock—it's normal contention; only fix if retries cause unacceptable latency.
The Booking System Meltdown: WriteConflict Livetime
Timeline
- 10:00PagerDuty alerts: booking API latency spikes to 5s and error rate jumps to 15%.
- 10:03Check Grafana: CPU on mongod primary hits 90%, many 'WriteConflict' errors in logs.
- 10:05Run db.currentOp() – see 60+ transactions idle in 'snapshot' for >2s, all on same 'bookings' collection.
- 10:10Identify hot document: a single 'concert:123' seat map updated by all concurrent bookings.
- 10:12Check application retry logic: no backoff, immediate retry causing exponential conflict cascade.
- 10:15Hotfix: reduce transaction scope – move seat-map read outside transaction, only write inside.
- 10:20Push hotfix; observe latency drops to 200ms, error rate to 0%.
- 10:30Post-mortem: add exponential backoff to retry logic, shard seat-map by event ID.
The incident started at 10:00 with a sudden spike in booking failures. I jumped into the Kubernetes pod logs and immediately saw the repeating pattern: 'MongoError: WriteConflict' with txnNumber and session ID. The MongoDB logs confirmed the same—dozens of transactions failing on the 'bookings' collection. The server CPU was pinned, and the application was stuck in a retry loop, making things worse.
I used db.currentOp() to identify the culprit: over 60 transactions were all trying to update the same seat-map document for the hottest concert event. Each transaction read the seat map, selected a seat, wrote the booking, and updated the map. With no backoff, retries happened immediately, causing all transactions to collide repeatedly. The retry logic was a simple 'catch and retry' with zero delay—a textbook anti-pattern.
We hotfixed by splitting the transaction: the seat-map read was moved outside the transaction (using snapshot read), and only the booking insert and seat-map update remained inside. That cut the transaction duration from 2 seconds to 50ms. We also added exponential backoff with jitter. The fix worked instantly. Later, we redesigned the seat-map to be bucketed per section, preventing any single document from becoming a hotspot.
Root cause
A concert's seat-map document became a hot spot; long-running transactions (due to unnecessary reads inside) and immediate retry without backoff caused livelock.
The fix
Moved seat-map read outside transaction; added exponential backoff with jitter to retry logic.
The lesson
Always read-only outside transactions; never retry without backoff; monitor hot documents via slow ops and currentOp.
WriteConflict in MongoDB is raised by the WiredTiger storage engine when two transactions try to modify the same document and one detects that its snapshot is stale. MongoDB uses multi-version concurrency control (MVCC): each transaction sees a consistent snapshot of data at the start. When transaction A modifies a document and commits, transaction B (which started before A's commit) will see a conflict if it also modified that document. The conflict is detected at commit time, not at write time.
The error code is 112 for WriteConflict. The driver will throw a MongoError with that code. The transaction is automatically aborted by the server; the application must catch the error and retry. Importantly, the session remains valid, but a new transaction must be started. The retry must use the same session (or a new one) to avoid reusing stale snapshot data.
The first step is always to identify which documents are causing conflicts. Use db.currentOp() with the filter { 'transaction': { $exists: true } } to list all active transactions. Look for the 'transaction.parameters.txnNumber' and the 'oplog' to see which documents are being written. Also, enable the profiler on the collection with a slowms threshold of 0 to capture all operations. Then grep for 'WriteConflict' in the profiler logs to see the exact query patterns.
Another technique: use the serverStatus command and examine 'wiredTiger.concurrentTransactions'. If 'write' throughput is maxed out (i.e., many threads blocked), contention is high. Also check 'transactions.retriedTransactionsCount'—if it's climbing rapidly, your retries are causing secondary conflicts.
A correct retry loop must include: (1) catch WriteConflict error (code 112) specifically; (2) abort the current transaction; (3) wait with exponential backoff (e.g., 100ms * 2^attempt) plus random jitter (0-50ms); (4) start a new transaction; (5) retry the entire operation. Set a maximum retry count (e.g., 5) and a timeout (e.g., 10s) to avoid infinite loops.
Important: Do not reuse the old transaction's snapshot. Always start a new transaction with a fresh snapshot. In MongoDB drivers, that means calling session.startTransaction() again. Also ensure that the session is not used concurrently—each session can have only one active transaction at a time.
Hot documents are a common cause of WriteConflicts. If you have a counter or a frequently updated embedded array (e.g., 'likes' array), consider bucketing: split the document into multiple sub-documents (e.g., likes_by_hour) and aggregate when reading. For seat-maps, partition by section or row so that concurrent bookings hit different documents.
Another approach: use atomic operators ($inc, $push) outside transactions for simple operations, avoiding the need for multi-document transactions altogether. Reserve transactions only for operations that truly require atomicity across multiple documents.
Frequently asked questions
Is WriteConflict always a bug?
No. WriteConflicts are a normal part of optimistic concurrency. They indicate contention, not necessarily a bug. Only if they occur excessively (causing high latency or retry storms) do they need investigation and mitigation.
Can I ignore WriteConflicts if my application retries?
You should not ignore them entirely. If you have a proper retry loop, occasional conflicts are fine. However, if you see many retries, it indicates a design problem (hot document, long transactions) that should be fixed to maintain performance.
What's the difference between WriteConflict and TransactionAborted?
WriteConflict (error 112) is a specific conflict within WiredTiger. TransactionAborted (error 251) is a broader error that can occur due to other reasons like network issues, server timeout, or shard coordination failure. Both require retry, but the root cause differs.
Does using snapshot read concern increase WriteConflicts?
Yes. Snapshot read concern ensures that all reads see a consistent snapshot, but it holds that snapshot for the transaction's duration, increasing the chance of conflict with other transactions. Use 'local' read concern if you don't need snapshot isolation inside the transaction.
How do I test my retry logic?
You can simulate WriteConflicts by running two concurrent operations that modify the same document inside transactions. Use a script that starts two sessions and forces them to collide. Or use the 'failCommand' fail point to inject WriteConflicts on a specific command.