skip to content

How do read concern and write concern apply inside a MongoDB multi-document transaction?

level: seniorimportance: should knowfreq 45%

answer

  1. Ask which object owns the setting
  2. Not the operation — something bigger
  3. One of them only matters at the very end
  4. Three read concern levels are permitted
  5. Reads have nowhere to go but the primary

basics

~20 s

Both are set once for the whole transaction, not per operation. The read concern governs every read in it, the write concern is applied only at commit, and reads must come from the primary. Individual operations may not override them.

solid answer

~50 s

A transaction carries transaction-level options. The **read concern** — `local`, `majority` or `snapshot` — applies to all reads in the transaction; `snapshot` is the one that guarantees reads come from a single majority-committed point in time, and on a sharded cluster it aligns that point across shards. The **write concern** is not applied to the individual writes at all: the writes are staged and the concern is applied to `commitTransaction()`, so `w: "majority"` means "the commit is majority-acknowledged". Setting a write concern on an operation inside the transaction is an error. Two consequences bite in practice: reading with `majority` inside a transaction only yields a majority-durable guarantee if the transaction actually commits with `w: "majority"`, and read preference inside a transaction must be primary — asking for a secondary fails. `maxCommitTimeMS` bounds how long the commit may take.

code

javascript · 8 lines
javascript
session.startTransaction({
  readConcern:  { level: "snapshot" },   // all reads: one point in time
  writeConcern: { w: "majority" },       // applied at commitTransaction()
  maxCommitTimeMS: 5000
});
// writes here carry NO write concern of their own
await ledger.insertOne({ from, to, amount }, { session });
await session.commitTransaction();       // majority ack happens here, once

go deeper

for a junior

Recall that a transaction sets its read and write concern once, when it starts, and that individual operations inside it do not carry their own.

for a middle

Explain that the write concern is applied at commit rather than per write, name the three read concern levels transactions accept, and say what snapshot adds over local.

for a senior

Show the operational consequences: the commit is the single majority-acknowledgement point, a held snapshot costs cache, transaction reads pin load on the primary, and an unknown commit result must be retried rather than treated as failure.

for a principal

Own the durability policy across the estate — which workloads commit with majority, where snapshot reads are worth their resource cost, and how those defaults are enforced so a wrapped-in-a-transaction refactor cannot silently weaken a guarantee.

## Concerns move up a level Outside a transaction, each operation carries its own read concern and write concern. Inside one, they belong to the transaction. You set them on `startTransaction()` (or pass them to `withTransaction()`), and every operation in the transaction inherits them. Attaching a write concern to a write *inside* an open transaction is rejected — the write has no independent acknowledgement to wait for. ```javascript session.startTransaction({ readConcern: { level: "snapshot" }, writeConcern: { w: "majority" }, maxCommitTimeMS: 5000 }); ``` ## Write concern applies to the commit This is the mental model shift. Writes made inside a transaction are staged; nothing is visible to other clients until commit. The transaction's write concern therefore describes the **commit**: with `w: "majority"` the driver waits until the commit has been acknowledged by a majority of the replica set, meaning the transaction's effects will survive a failover. With a weaker concern the commit returns sooner and the whole transaction can be rolled back by a subsequent election, exactly as an ordinary write can be. A practical corollary: a transaction that writes ten documents pays the majority-acknowledgement latency once, at commit, rather than ten times. That is one of the few ways in which a transaction is cheaper than the equivalent loop of individual majority writes. Abort has a write concern too, and `maxCommitTimeMS` caps how long the driver waits for the commit before it returns a timeout — which, importantly, arrives labelled as an unknown commit result, not as a failure. ## Read concern governs all reads in the transaction Three levels are supported: `local`, `majority` and `snapshot`. `linearizable` and `available` are not permitted inside a transaction. - **`snapshot`** reads from a single snapshot of majority-committed data. All reads in the transaction see the same point in time, and on a sharded cluster the participating shards use a common cluster time, so a multi-shard read is coherent. This is the level to reach for when the transaction's correctness depends on reading a consistent world. - **`majority`** reads data that has been acknowledged by a majority. Inside a transaction this level only gives its full guarantee when the transaction goes on to commit with `w: "majority"`; a transaction that reads `majority` and commits weakly does not carry a majority-durability guarantee end to end. - **`local`** reads the most recent data on the node without the majority requirement. It is the cheapest and, on a sharded cluster, gives no cross-shard point-in-time alignment. In all cases the transaction reads its **own** writes: an operation later in the transaction sees the documents earlier operations in the same transaction modified, even though no other client can. ## Reads come from the primary Multi-document transactions read from the primary. Read preference inside a transaction must be primary, and setting `secondary` or `secondaryPreferred` produces an error rather than silently downgrading. Teams that route analytics traffic to secondaries sometimes assume they can wrap a large read in a transaction to get a snapshot cheaply on a secondary — they cannot; the transaction pins load onto the primary. ## Where this goes wrong in production **Assuming per-operation concerns still apply.** Code that carefully sets `w: "majority"` on each write and is later wrapped in a transaction either errors or, if the concerns are stripped, silently commits under the transaction's default — which may be weaker than the author intended. Set them on the transaction and review them there. **Assuming `snapshot` is free.** Holding a snapshot for the life of the transaction keeps older versions pinned, which competes with cache and with cleanup. Long transactions at `snapshot` are the ones most likely to hurt. **Assuming an unknown commit result means failure.** A commit that times out or loses its connection is *unknown*, not failed. The correct response is to re-send `commitTransaction()` on the same session; treating it as a failure and re-running the work risks doing it twice. **Assuming a transaction upgrades durability automatically.** Read concern and write concern are still yours to choose. A transaction gives atomicity and isolation; it does not silently give you majority durability unless you ask for it. ## What to say in an interview Name the level shift (concerns belong to the transaction), state that write concern lands on the commit, name the three permissible read concerns and what `snapshot` buys, mention the primary-only read rule, and close with the coupling: `majority` reads only mean what you think they mean if the commit is also `majority`.

  • Why does a transaction with ten writes and w:"majority" often cost less latency than ten separate majority writes?
    Outside a transaction each write waits for its own majority acknowledgement, so you pay that round trip ten times. Inside a transaction the writes are staged locally and only the commit is majority-acknowledged, so the replication wait happens once. That does not make transactions cheap overall — the snapshot, the locks and the abort risk are real — but on the durability axis specifically the commit amortises the cost.
  • What does read concern snapshot add over majority for a transaction on a sharded cluster?
    `majority` says the data you read was majority-acknowledged, but different reads — and especially reads served by different shards — need not correspond to the same instant. `snapshot` pins the transaction to one cluster-wide point in time, so every read in it, on every participating shard, reflects the same state. That is what makes a multi-shard read inside a transaction coherent rather than merely durable.
  • Your commit returns a network error. What is the correct next step?
    Treat it as unknown, not failed. The driver surfaces it with the unknown-commit-result label, which means the commit may already have succeeded. Re-send `commitTransaction()` on the same session: if it committed, the server reports success again; if not, the commit is attempted. Re-running the transaction body instead risks applying the work twice.

saying these in an interview costs you the question

  • Sets write concern on individual writes inside the transaction
  • Thinks each write is acknowledged separately during the transaction
  • Believes a transaction can read from a secondary
  • Assumes majority read concern alone guarantees majority durability
  • Treats a commit timeout as a definite failure

context