Multi-document transactions

MongoDB · Advanced · 12 min

Some invariants, like moving money between accounts, span several documents and need all-or-nothing writes.

Single-document writes are always atomic, and good modeling makes them sufficient most of the time. When an operation must change several documents or collections together, MongoDB offers multi-document ACID transactions on replica sets and sharded clusters (not on a standalone server).

Transactions run inside a session. The convenient API is session.withTransaction(async () => {...}): you pass the session to every operation inside, and the driver commits at the end, aborts on error, and retries the whole callback on transient errors such as write conflicts, and the commit on unknown commit results.

Semantics to know:

  • reads inside see a consistent snapshot; writes are invisible to others until commit,
  • if two transactions modify the same document, the second gets a WriteConflict and is retried,
  • a transaction should be short: the default limit is 60 seconds (transactionLifetimeLimitSeconds), and long ones pin old snapshots in the cache,
  • use writeConcern: "majority" on the transaction for durable commits.

Transactions cost more than single-document writes, so don't wrap every request in one. Reach for them for real cross-document invariants: transfers, inventory reservation together with order creation, or keeping two collections in sync.

Example

// Node.js driver: transfer money between two account documents
const session = client.startSession();
try {
  await session.withTransaction(async () => {
    const accounts = client.db("bank").collection("accounts");
    const from = await accounts.findOneAndUpdate(
      { _id: "ana", balance: { $gte: 100 } },
      { $inc: { balance: -100 } },
      { session, returnDocument: "after" });
    if (!from) throw new Error("insufficient funds");   // aborts the transaction
    await accounts.updateOne({ _id: "ben" }, { $inc: { balance: 100 } }, { session });
  }, { writeConcern: { w: "majority" } });
} finally {
  await session.endSession();
}

Remember

  • Transactions need a replica set or sharded cluster
  • Pass the session to every operation inside the transaction
  • withTransaction commits, aborts and retries transient errors for you
  • Keep them short; prefer single-document atomicity when the model allows

Watch out for

  • Forgetting { session } on one operation, which then runs outside the transaction and is not rolled back.

Check yourself

  1. Inside withTransaction one update omits the session option and the transaction later aborts. What happens to that update?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.mongodb.com/docs/manual/core/transactions/