Skip to content

Consistency in MongoDB

People arriving from the relational world often reach for a transaction the moment they need to change more than one thing. In MongoDB that instinct usually leads you astray. The database gives you a strong guarantee for free, and reaching past it for a multi-document transaction is something you do rarely and deliberately, not by default. This module is about understanding exactly what you already have, what you can ask for when you genuinely need more, and how to dial the durability and visibility of every operation up or down.

The single fact that everything else hangs on is this: a write to one document is atomic. When you update a member’s record, MongoDB applies the whole change — even if it touches a nested object, several fields, and an array all at once — as one indivisible step. No other reader ever sees the document half-changed, and if the write fails, the document is left exactly as it was. You do not open anything, you do not commit anything, and you do not pay any extra cost. The atomicity is simply how a document write works.

Because one document is the unit of atomicity, the way you model your data decides how much of your consistency comes for free. Data that changes together and belongs together should live in the same document, so a single write keeps it consistent. When related data is genuinely spread across documents or collections and must change as a unit, MongoDB offers multi-document transactions — real ACID transactions across a session. They work, but they cost more and require a replica set, so they sit at the bottom of the toolbox rather than the top.

Layered on top of both is a pair of knobs that apply to every operation. A write concern controls how many replica-set members must acknowledge a write, and whether it must be flushed to the on-disk journal, before the driver calls the write done — this is the durability knob. A read concern controls how fresh and how committed the data you read must be — this is the visibility knob. Together they let you trade latency for safety, operation by operation.

flowchart TD
  W["A write request"] --> Q{"How many documents?"}
  Q -->|"One document"| A["Atomic automatically — no transaction needed"]
  Q -->|"Many documents as a unit"| T["Multi-document transaction in a session"]
  A --> WC["Write concern tunes durability"]
  T --> WC
  WC --> RC["Read concern tunes what readers see"]
The decision path: single-document atomicity by default, transactions only when several documents must change as one unit, with concerns tuning durability and visibility
  • Single-document atomicity — why any change to one document, however deep, is all-or-nothing, and how to model data so this covers most of your needs.
  • Multi-document transactions — opening a session, running several operations as one ACID unit, and committing or aborting, with the classic transfer-between-accounts example.
  • Read and write concerns — the w, j, and wtimeout settings for writes, the local, majority, and linearizable levels for reads, and read preference for choosing which member answers.
  • Change streams — subscribing to a live feed of inserts, updates, and deletes so your application can react to data changes as they happen.
  • Single-document atomicity — a failing update changed nothing; 100 concurrent read-modify-writes lost most increments while $inc did not; updateMany was observed half-applied.
  • Transactions — rollback, WriteConflict with the retry label, the 60 s lifetime, and a forgotten { session } that silently committed a debit.
  • Concerns — the default write concern is already w: "majority"; a w: 1 write acknowledged by a partitioned primary was rolled back; a w: 3 timeout still applied the write.
  • Change streams — the Node driver opens lazily and misses an insert made right after watch(); real resume tokens are 188 hex characters.

One document is the unit of atomicity and costs nothing. Everything bigger is a sequence of atomic steps you must either fold into one document, guard in a filter, or wrap in a transaction. Concerns are two independent dials on top: write concern is what the writer is told, read concern is what the reader sees.

ChoiceBenefitCost
Design documents so single-document atomicity covers the needAtomicity for free, no session or commit overhead, lowest latency (about half a transaction’s time in the measured run)Requires upfront schema thought; does not fit data that truly must stay independent entities
Multi-document transaction (full ACID)Guarantees correctness across documents and collectionsNeeds a replica set, retries on WriteConflict, 60 s limit
Write concern w: "majority" (the default)An acknowledged write survives a failoverWaits for replication; times out if a majority is unreachable
Read concern majorityNo reads of data that can be rolled backLags local and your own w: 1 writes
  • “Reach for a transaction whenever more than one document is touched.” — first ask whether the data that changes together can live together; the single-document write was the cheapest in every measurement.
  • “Write concern and read concern are the same knob.” — durability for the writer versus visibility for the reader; they are independent.
  • “Set the strongest concern everywhere.” — the default is already majority; the deliberate, risky choice is lowering it.
  • “An acknowledged write is safe.” — only if the concern covers a majority; a w: 1 acknowledgement was rolled back after a partition.
What is the unit of atomicity in MongoDB by default?
When should you reach for a multi-document transaction?
What does a write concern control?
You call session.withTransaction on a standalone mongod started without --replSet. What happens?
What is the implicit default write concern on MongoDB 8 for a normal three-node set?