MongoDB Transactions

Transaction Basics (mongosh)

Multi-document transactions require a replica set or sharded cluster. Available since MongoDB 4.0.

// Start a session and transaction
const session = db.getMongo().startSession();
session.startTransaction({
  readConcern:  { level: "snapshot" },
  writeConcern: { w: "majority" }
});

try {
  const orders   = session.getDatabase("shop").orders;
  const inventory = session.getDatabase("shop").inventory;

  orders.insertOne(
    { userId: "u1", productId: "p1", qty: 2, total: 49.98 },
    { session }
  );
  inventory.updateOne(
    { productId: "p1" },
    { $inc: { stock: -2 } },
    { session }
  );

  session.commitTransaction();
  print("Transaction committed");
} catch (err) {
  session.abortTransaction();
  print("Transaction aborted:", err);
} finally {
  session.endSession();
}

Node.js Driver Example

// Using withTransaction() helper (auto-retry on transient errors)
const { MongoClient } = require("mongodb");
const client = new MongoClient(uri);

async function transferFunds(fromId, toId, amount) {
  const session = client.startSession();
  try {
    await session.withTransaction(async () => {
      const accounts = client.db("bank").collection("accounts");

      const from = await accounts.findOne({ _id: fromId }, { session });
      if (from.balance < amount) throw new Error("Insufficient funds");

      await accounts.updateOne(
        { _id: fromId },
        { $inc: { balance: -amount } },
        { session }
      );
      await accounts.updateOne(
        { _id: toId },
        { $inc: { balance: amount } },
        { session }
      );
    }, {
      readConcern:  { level: "snapshot" },
      writeConcern: { w: "majority" },
      readPreference: "primary"
    });
  } finally {
    await session.endSession();
  }
}

Retryable Writes

Retryable writes automatically retry once on network errors and primary failovers, enabled by default in drivers 3.6+.

// Enable in connection string (default in modern drivers)
const client = new MongoClient("mongodb://host:27017/db?retryWrites=true");

// Retryable writes work for:
// - insertOne, insertMany
// - updateOne, replaceOne
// - deleteOne
// - findOneAndUpdate, findOneAndReplace, findOneAndDelete
// - bulkWrite (ordered and unordered)

// NOT retryable: multi-document writes (updateMany, deleteMany)
// NOT retryable: operations inside transactions (managed separately)

// Disable retryable writes (e.g., for legacy compatibility)
const client2 = new MongoClient(uri, { retryWrites: false });

Causal Consistency

Causal consistency ensures a session reads its own writes, even against secondary nodes.

// Causally consistent session
const session = client.startSession({ causalConsistency: true });

// Write to primary
await db.collection("settings").updateOne(
  { key: "theme" },
  { $set: { value: "dark" } },
  { session, writeConcern: { w: "majority" } }
);

// Read from secondary โ€” guaranteed to see the write above
await db.collection("settings").findOne(
  { key: "theme" },
  {
    session,
    readPreference: "secondary",
    readConcern: { level: "majority" }
  }
);

Transaction Limits & Best Practices

ConstraintDefault / Recommendation
Max transaction duration60 seconds (transactionLifetimeLimitSeconds)
Max document lock countNo hard limit, but keep small
Oplog entry size limit16 MB per transaction
Create collections in txnSupported since MongoDB 4.4
Keep transactions shortMinimize lock contention and WiredTiger conflicts
Avoid long-running queriesPrefer pre-computed or indexed lookups inside txn