139: MongoDB Consistency — Atomicity, Transactions, Sessions, Read Concern, Write Concern, Read Preference, and Retryable Operations
Learning objectives
You will learn to:
- understand single-document atomicity;
- use conditional atomic updates;
- understand sessions;
- use multi-document transactions;
- choose transaction boundaries;
- understand read concern;
- understand write concern;
- understand read preference;
- understand causal consistency at a practical level;
- understand retryable reads/writes;
- design idempotent application behavior;
- avoid treating transactions as a substitute for good schema design.
Single-document atomicity
MongoDB writes to one document atomically.
Example:
db.accounts.updateOne(
{
_id: accountId,
balancePaise: {
$gte: 5000
}
},
{
$inc: {
balancePaise: -5000,
version: 1
}
}
)
The predicate and update are evaluated atomically for that document.
This can enforce:
do not reduce balance below zero
without a read-then-write race.
Read-modify-write race
Unsafe pattern:
read balance = 100 request A subtracts 30 request B subtracts 50 A writes 70 B writes 50
One update is lost.
Use atomic update operators/predicate:
{
$inc: {
balance: -amount
}
}
with condition.
Or optimistic version:
filter:
{
_id,
version: expected
}
update:
{
$set: patch,
$inc: {
version: 1
}
}
If matched count 0:
conflict
When one document is enough
If order + line items + totals are embedded:
mark line served update order total/status
can often be one atomic document update.
If you normalized into many collections, you may need transaction.
This is why modeling and transaction design are connected.
Multi-document transaction
Use when one business invariant spans multiple documents/collections.
Example transfer:
debit account A credit account B insert transfer record
should commit together.
Transactions support ACID semantics across involved documents on supported deployments.
Session
Transactions run in client session.
Node driver example is lesson 144.
Shell concept:
const session = db.getMongo().startSession()
session.startTransaction()
try {
...
session.commitTransaction()
} catch (error) {
session.abortTransaction()
throw error
} finally {
session.endSession()
}
Exact shell APIs can vary; driver API is primary application interface.
Transaction design
Keep transactions:
- short;
- focused;
- bounded;
- minimal documents;
- no user waiting inside;
- no slow external API calls.
Do not:
start Mongo transaction → call payment API for 20 seconds → wait user confirmation → commit
External systems cannot participate in Mongo transaction.
Use saga/outbox/idempotency architecture for cross-system consistency.
Transaction retry
Transactions can encounter transient errors requiring retry.
Official drivers provide helpers such as withTransaction that handle certain retry semantics.
Use driver-recommended API.
Do not write naive infinite retry.
Keep operation idempotent where possible.
Transaction performance
Transactions add:
- coordination;
- snapshot state;
- oplog/replication pressure;
- memory/storage;
- contention.
Do not wrap every CRUD request in transaction “for safety.”
Single-document atomicity is cheaper.
Snapshot isolation concept
Transaction reads see a consistent snapshot according to transaction/read concern semantics.
Concurrent changes may cause conflicts/retries.
Understand that “transaction” does not mean no concurrency; it means controlled consistency/isolation semantics.
Read concern
Read concern controls consistency/isolation guarantees of reads.
Common levels include concepts such as:
local available majority linearizable snapshot
Availability depends on deployment/operation.
Do not memorize names without semantics.
local
Can return latest data on node without guaranteeing majority replication.
May see data later rolled back in rare failover scenarios.
majority
Returns data acknowledged as committed to majority, according to replication semantics.
Often chosen when reading durable committed data matters.
Has latency/availability trade-offs.
linearizable
Strong single-document read semantics in specific supported contexts, with higher cost/constraints.
Use only when requirement demands.
snapshot
Used for snapshot-consistent reads/transactions under supported scenarios.
Write concern
Controls acknowledgment requirement for writes.
Examples:
{
w: 1
}
acknowledge by primary.
{
w: "majority"
}
wait for majority acknowledgment.
Optional:
j: true wtimeout
depending requirements/version.
Do not assume acknowledged write = globally visible to every read preference instantly.
Durability trade-off
Higher write concern can increase durability and latency.
For critical financial-like data, stronger acknowledgment may be appropriate.
For disposable telemetry, different policy could fit.
Choose from business durability requirement.
Write concern timeout
If required acknowledgment not achieved in time, client can receive timeout even if write may have been applied on some nodes.
Application must handle ambiguous outcomes carefully.
This is why idempotency keys/version checks matter.
Read preference
Controls which replica-set member can serve reads.
Common modes:
primary primaryPreferred secondary secondaryPreferred nearest
Primary gives freshest primary view.
Secondary can reduce primary read load or serve regional reads but may be stale.
Do not use secondary reads for authorization/critical read-after-write behavior without understanding lag/consistency.
Stale secondary
Flow:
write primary immediate read secondary → old value
Possible depending lag/read concern.
If user expects immediate confirmation, choose appropriate read path.
Causal consistency
Sessions can support causal relationships so later operations observe prior operations in expected order under supported settings.
Useful for:
write then read in same logical session
without strongest global consistency everywhere.
Drivers manage metadata.
Retryable writes
Driver may automatically retry selected single-document writes after transient network/primary errors.
This improves reliability while avoiding duplicate effects because server tracks retryable operation identity in supported sessions.
Do not assume arbitrary multi-step application workflow is automatically retry-safe.
Retryable reads
Selected reads can retry after transient errors.
A retry can increase latency during failover but improve success.
Use driver defaults/recommendations.
Ambiguous outcome
Imagine:
client sends create server commits network drops before response
Client does not know if create happened.
If retry creates another record, duplicate.
Solutions:
- client-generated stable operation/resource ID;
- unique external ID;
- idempotency key;
- retryable-write semantics where applicable.
Unique index + idempotency
Webhook event:
{
"eventId": "evt_123"
}
Create processing record with unique index:
{
eventId: 1
}
unique
If webhook retries, duplicate key tells you already processed/claimed.
Still design transaction around side effects.
Transaction and outbox
Need:
update order publish event
Mongo transaction cannot atomically publish to Kafka/external broker.
Outbox pattern:
Transaction writes:
order update outbox event document
Then worker publishes outbox reliably and marks sent.
This connects DB atomicity to external messaging.
Change streams alternative
Change streams can observe committed changes, but delivery/resume semantics and business event shape need design.
Do not assume raw database change stream equals durable business-event outbox for every requirement.
Advanced lesson covers.
Isolation and counters
Atomic counter:
findOneAndUpdate(
{ _id: "invoice" },
{ $inc: { next: 1 } },
{ returnDocument: "after" }
)
One hot counter can become contention bottleneck at high scale.
For strict invoice sequences, domain/legal requirements may demand serialization; design capacity intentionally.
Distributed transactions and sharding
Transactions work in sharded clusters with additional coordination cost.
If every request touches many shards, shard key/data model may be poor.
Sharding lesson goes deeper.
Read/write concern defaults
Driver/cluster defaults evolve.
Do not copy ancient options from tutorials.
Use explicit concerns for operations where business semantics differ from defaults and document why.
Error labels
Mongo driver errors can include labels such as transient transaction concepts.
Use official driver helpers rather than string matching error messages.
Failure clinic
- transaction around every request;
- long external API inside transaction;
- read from secondary expecting immediate write;
- weaker write concern for critical data without decision;
- retry loop on unknown outcome creates duplicates;
- application pre-check uniqueness without unique index;
- transaction used to compensate for bad embedding;
- no idempotency on webhook/payment create;
- raw change stream treated as business outbox automatically.
Exercises
- Build atomic decrement with predicate.
- Implement versioned update.
- Model transfer requiring transaction.
- Identify workflow that should avoid transaction by embedding.
- Compare w:1 versus majority business trade-off.
- Simulate read-after-write from secondary conceptually.
- Design idempotent webhook handling.
- Design outbox transaction.
- Explain ambiguous create outcome.
- Create consistency matrix for payment, profile, analytics event.
Mastery checklist
Explain:
- single-document atomicity;
- conditional updates;
- sessions;
- transactions;
- read concern;
- write concern;
- read preference;
- causal consistency;
- retryable reads/writes;
- ambiguous outcomes;
- idempotency;
- outbox.
Official references
- https://www.mongodb.com/docs/manual/core/transactions/
- https://www.mongodb.com/docs/manual/reference/read-concern/
- https://www.mongodb.com/docs/manual/reference/write-concern/
- https://www.mongodb.com/docs/manual/core/read-preference/
- https://www.mongodb.com/docs/manual/core/retryable-writes/
- https://roadmap.sh/mongodb
