Fix MongoServerError: WriteConflict in MongoDB Transactions

intermediate๐Ÿƒ MongoDB2026-03-18| MongoDB 4.0+ (replica sets), MongoDB 4.2+ (sharded clusters), Node.js (native driver / Mongoose), Python (pymongo)

Error Message

MongoServerError: WriteConflict error: this operation conflicted with another operation. Please retry your operation or multi-document transaction.
#mongodb#transaction#write-conflict#concurrency

The Error

MongoServerError: WriteConflict error: this operation conflicted with another operation. Please retry your operation or multi-document transaction.

Two concurrent transactions tried to write to the same document(s) at the same time, and yours lost. MongoDB uses optimistic concurrency control โ€” it lets both transactions run in parallel, then detects the conflict at commit time and aborts one of them. This is expected behavior, not a driver bug. It's your application's job to catch this and retry.

Common triggers:

  • Multiple workers processing orders and updating the same inventory document simultaneously
  • Parallel API requests updating the same user account within milliseconds of each other
  • Batch jobs running overlapping transactions on shared data sets

Step-by-Step Fix

Step 1: Implement Retry Logic (Do This First)

WriteConflict is a transient error โ€” MongoDB explicitly tells you to retry, and it usually succeeds on the next attempt. Check for the TransientTransactionError error label rather than pattern-matching the message string. String matching breaks across driver versions; the label is stable.

Node.js (native driver or Mongoose):

async function runWithRetry(session, fn, maxRetries = 3) {
  let attempt = 0;
  while (attempt  setTimeout(r, 50 * attempt)); // exponential backoff
        continue;
      }
      throw err;
    }
  }
}

// Usage
const session = await mongoose.startSession();
try {
  await runWithRetry(session, async () => {
    await Order.findByIdAndUpdate(orderId, { status: 'processing' }, { session });
    await Inventory.findByIdAndUpdate(itemId, { $inc: { stock: -1 } }, { session });
  });
} finally {
  await session.endSession();
}

Python (pymongo):

from pymongo.errors import PyMongoError
import time

def run_with_retry(client, fn, max_retries=3):
    for attempt in range(max_retries):
        with client.start_session() as session:
            try:
                with session.start_transaction():
                    fn(session)
                    return  # committed successfully
            except PyMongoError as e:
                if e.has_error_label("TransientTransactionError") and attempt  {
  const price = await fetchPriceFromExternalAPI(); // network call = bad
  await Order.create([{ price }], { session });
});

// GOOD: fetch outside, write inside
const price = await fetchPriceFromExternalAPI();
await session.withTransaction(async () => {
  await Order.create([{ price }], { session });
});

Target transaction duration under 1 second. MongoDB's default transactionLifetimeLimitSeconds is 60s, but conflicts stack up fast once you have more than a handful of concurrent transactions touching the same documents.

Step 3: Access Documents in Consistent Order

When multiple transactions touch the same set of documents, always access them in the same sorted order. This breaks the deadlock cycle that causes repeated conflicts.

// Sort document IDs before iterating โ€” every transaction uses the same order
const docIds = [id1, id2, id3].sort((a, b) => a.toString().localeCompare(b.toString()));
for (const id of docIds) {
  await collection.updateOne({ _id: id }, update, { session });
}

Step 4: Replace Transactions With Atomic Single-Document Operations Where Possible

Single-document operations in MongoDB are always atomic and never produce WriteConflict errors. If your transaction only touches one document, you don't need a transaction at all.

// Atomic decrement โ€” no transaction, no WriteConflict possible
const result = await Inventory.findOneAndUpdate(
  { _id: itemId, stock: { $gt: 0 } },
  { $inc: { stock: -1 } },
  { returnDocument: 'after' }
);

if (!result) {
  throw new Error('Out of stock');
}

Step 5: Check Transaction Lifetime Settings

MongoDB aborts any transaction that exceeds transactionLifetimeLimitSeconds. Check the current value and tighten it if conflicts are piling up โ€” a shorter limit fails faster and frees locks sooner:

// Check current value
db.adminCommand({ getParameter: 1, transactionLifetimeLimitSeconds: 1 })

// Reduce to 30s to fail faster and free locks sooner
db.adminCommand({ setParameter: 1, transactionLifetimeLimitSeconds: 30 })

Verify the Fix

Once your retry wrapper is deployed, confirm conflicts are being caught rather than surfacing as application errors:

# Count WriteConflict occurrences in logs (JSON log format)
grep -c "WriteConflict" /var/log/mongodb/mongod.log

# Check transaction metrics via serverStatus
db.serverStatus().metrics.operation

# Monitor in real time
watch -n 2 'mongo --quiet --eval "printjson(db.serverStatus().metrics.operation)"'

Instrument your retry wrapper to track retry rates in production. A retry rate above 5% of total transaction attempts is a warning sign. At that point, retrying isn't going to save you โ€” you've got a hot document problem that needs a data model fix, not a tighter loop.

Persistent Conflicts: When Retry Isn't Enough

Some documents attract conflict by design โ€” a global counter, a shared cart, a live leaderboard getting thousands of writes per second. Retries will keep colliding. You need to rethink the data model instead:

  • Bucket pattern: Split the hot document into N shards and randomly pick one per write, merge periodically
  • Queue-based serialization: Route writes to the hot document through a single worker with a job queue
  • Pre-aggregated counters: Write increments to per-user or per-session documents, aggregate on read

Checklist Before Moving On

  • Retry wrapper checks TransientTransactionError label (not string matching)
  • Retry uses exponential backoff โ€” not a tight loop
  • No network calls, file I/O, or heavy computation inside transaction blocks
  • Documents accessed in consistent sort order across concurrent transactions
  • Single-document updates use atomic operators ($inc, $set) instead of read-modify-write
  • Retry rate monitored in production โ€” alert if it climbs above 5%

Related Error Notes