Make write acknowledgement measurable: primary acceptance, journal persistence, majority oplog durability, timeout ambiguity, and retry safety.
Write Concern w, j, wtimeout, Majority Acknowledgment, and Durability Expectations
Run one stock-reservation update under multiple write concerns and reconcile the difference between acknowledgement, durability, and application state.
Learning objectives
Define write concern as an acknowledgment contract rather than a generic consistency switch.
Compare w:1, w:"majority", numeric
w, j:true, and
wtimeout on the same AtlasMart invariant.
Explain the MongoDB 8.0+ distinction between durable majority oplog acknowledgment and secondary document application.
Observe write-concern errors without assuming that the primary-side write was rolled back.
Relate durability choices to failover, tail latency, topology, and idempotent application recovery.
This lesson pins MongoDB Community Server
8.3.8 with
mongodb/mongodb-community-server:8.3.8-ubuntu2204-slim, mongosh 2.10.0, and PyMongo
4.17.0 where the driver is used. The mandatory
topology is a disposable three-member replica set on one Docker
host, with loopback-published diagnostic ports 27100–27102.
Members communicate over a lesson-specific Docker bridge network
by container DNS name. Authentication and TLS are disabled only
for this isolated local learning topology. Feature Compatibility
Version (FCV) is observed and never changed. Unless the
experiment states otherwise, reads target the primary and writes
use an explicitly named concern rather than assuming a global
default. Run only one Chapter 15 topology at a time and budget
roughly 3–4 GB of free RAM plus disk headroom. Local member tags
such as east/west demonstrate routing
semantics only; they do not simulate real WAN latency or failure
domains. Atlas, Search, Vector Search, KMS, and Enterprise
Advanced are not mandatory. Product commands were not executed
in this generation environment because Docker, mongod, mongosh,
and PyMongo are unavailable here; expected invariants are
documentation-derived and measured values must be recorded on
the learner’s machine.
1. AtlasMart invariant: an accepted reservation must survive the failure model we claim
AtlasMart reserves stock before it confirms an order. The application cares about two different questions: did the primary accept the update? and how far must that update propagate before the client receives success? MongoDB answers the second question with write concern. It is an acknowledgement contract attached to a write operation; it is not a read-isolation level, a read-routing policy, or a promise that unrelated external side effects are exactly once.
| Setting | Acknowledgement requested | Key non-guarantee |
|---|---|---|
w:1 |
Primary has accepted the write. | The write can roll back if it has not reached another member before failover. |
w:"majority" |
Calculated majority of data-bearing voting members durably writes the oplog entry. | Does not imply every secondary has already applied the application document. |
j:true |
Requested acknowledging members have written to the on-disk journal. | By itself, journal acknowledgement does not prevent rollback after primary failover. |
wtimeout |
Bounds how long the client waits for the requested propagation. | A timeout does not undo a write that already succeeded on the primary. |
Starting in MongoDB 8.0, a w:"majority" write
returns after a majority of data-bearing voting members
durably write the oplog entry; secondaries then apply that
history asynchronously. An immediate read from a secondary can
therefore still lag the application-visible change. This is
why Chapter 15 later needs causal consistency instead of
treating “majority” as a magic freshness switch.
docker rm -f atlasmart-mongo-ch15-l1-a atlasmart-mongo-ch15-l1-b atlasmart-mongo-ch15-l1-c 2>/dev/null || truedocker network rm atlasmart-ch15-l1-net 2>/dev/null || truedocker volume rm atlasmart-mongo-ch15-l1-a-data atlasmart-mongo-ch15-l1-b-data atlasmart-mongo-ch15-l1-c-data 2>/dev/null || truedocker network create atlasmart-ch15-l1-netdocker run -d --name atlasmart-mongo-ch15-l1-a --network atlasmart-ch15-l1-net -p 127.0.0.1:27100:27017 -v atlasmart-mongo-ch15-l1-a-data:/data/db mongodb/mongodb-community-server:8.3.8-ubuntu2204-slim --replSet atlasmart-rs15-l1 --bind_ip_alldocker run -d --name atlasmart-mongo-ch15-l1-b --network atlasmart-ch15-l1-net -p 127.0.0.1:27101:27017 -v atlasmart-mongo-ch15-l1-b-data:/data/db mongodb/mongodb-community-server:8.3.8-ubuntu2204-slim --replSet atlasmart-rs15-l1 --bind_ip_alldocker run -d --name atlasmart-mongo-ch15-l1-c --network atlasmart-ch15-l1-net -p 127.0.0.1:27102:27017 -v atlasmart-mongo-ch15-l1-c-data:/data/db mongodb/mongodb-community-server:8.3.8-ubuntu2204-slim --replSet atlasmart-rs15-l1 --bind_ip_alluntil mongosh "mongodb://127.0.0.1:27100/admin?directConnection=true" --quiet --eval 'quit(db.runCommand({ping:1}).ok===1?0:1)'; do sleep 1; donemongosh "mongodb://127.0.0.1:27100/admin?directConnection=true" --quiet --eval 'rs.initiate({_id:"atlasmart-rs15-l1",members:[ {_id:0,host:"atlasmart-mongo-ch15-l1-a:27017"}, {_id:1,host:"atlasmart-mongo-ch15-l1-b:27017"}, {_id:2,host:"atlasmart-mongo-ch15-l1-c:27017"}]})'until mongosh "mongodb://127.0.0.1:27100/admin?directConnection=true" --quiet --eval 'quit(db.hello().isWritablePrimary?0:1)'; do sleep 1; donefor i in $(seq 1 60); do STATES=$(mongosh "mongodb://127.0.0.1:27100/admin?directConnection=true" --quiet --eval 'const s=rs.status(); print(s.members.map(m=>m.stateStr).sort().join(","))' || true) [ "$STATES" = "PRIMARY,SECONDARY,SECONDARY" ] && break sleep 1donemongosh "mongodb://127.0.0.1:27100/admin?directConnection=true" --quiet --eval 'printjson({server:db.version(),hello:db.hello(),fcv:db.runCommand({getParameter:1,featureCompatibilityVersion:1}).featureCompatibilityVersion,status:rs.status().members.map(m=>({name:m.name,stateStr:m.stateStr}))})'
2. Inspect the topology-dependent defaults before changing anything
const admin = db.getSiblingDB("admin");const s = rs.status();const cfg = rs.conf();printjson({ writeMajorityCount: s.writeMajorityCount, votingMembersCount: s.votingMembersCount, writeConcernMajorityJournalDefault: cfg.writeConcernMajorityJournalDefault, members: s.members.map(m => ({name:m.name,stateStr:m.stateStr}))});const app = db.getSiblingDB("atlasmart");app.reservation_concerns.drop();app.reservation_concerns.insertOne({_id:"sku-camera",available:10,reserved:0,policyVersion:1});printjson(app.reservation_concerns.findOne({_id:"sku-camera"}));
The implicit default is topology-sensitive, especially in deployments with arbiters. Production code that depends on a durability property should name the intended write concern or establish a deliberately governed cluster-wide default; do not infer correctness from whatever default happens to exist.
3. Run the same update under different acknowledgement contracts
const app = db.getSiblingDB("atlasmart");function timed(label, wc, inc) { const start = Date.now(); const r = app.runCommand({ update: "reservation_concerns", updates: [{q:{_id:"sku-camera"},u:{$inc:{reserved:inc}}}], writeConcern: wc }); printjson({label,elapsedMs:Date.now()-start,result:r,doc:app.reservation_concerns.findOne({_id:"sku-camera"})});}timed("w1", {w:1}, 1);timed("majority", {w:"majority",wtimeout:5000}, 1);timed("w1+journal", {w:1,j:true}, 1);timed("majority+journal", {w:"majority",j:true,wtimeout:5000}, 1);
A single elapsed time proves almost nothing about production tail latency. Repeat representative operations under load and record p50/p95/p99, replication lag, disk/journal pressure, and write-concern error rate. Stronger acknowledgement can add wait time, but the actual cost depends on topology and storage—not folklore.
4. Deliberately request an impossible concern: timeout is not rollback
A three-member replica set cannot satisfy w:4. This
is a safe way to prove that wtimeout limits waiting
for acknowledgement but does not reverse the primary mutation.
const app = db.getSiblingDB("atlasmart");const before = app.reservation_concerns.findOne({_id:"sku-camera"});const r = app.runCommand({ update:"reservation_concerns", updates:[{q:{_id:"sku-camera"},u:{$inc:{reserved:1}}}], writeConcern:{w:4,wtimeout:1500}});const after = app.reservation_concerns.findOne({_id:"sku-camera"});printjson({before,result:r,after});print("If writeConcernError is present and reserved still increased, the write succeeded locally but requested acknowledgement did not complete in time.");
Blindly resending a non-idempotent update such as
$inc after a write-concern timeout can double the
business effect, because the original mutation may already
exist. Reconcile by operation ID or current state, or design
an idempotent conditional mutation before retrying.
5. Failure semantics and what majority does not mean
| Claim | Accurate? | Reason |
|---|---|---|
“w:1 means durable across failover.” |
No | It only requires primary acknowledgement; an unreplicated write can roll back. |
“j:true means no rollback.” |
No | Journaling protects a member from process/storage crash scenarios; it is not replica-set majority commitment. |
“w:"majority" means every secondary already
serves the update.”
|
No | In 8.0+, majority durable oplog acknowledgement can precede secondary application. |
“wtimeout means the write failed.” |
No | It means the requested acknowledgement was not achieved before the deadline; reconcile state. |
| “Majority implies linearizable reads.” | No | Linearizable is a separate primary-only read concern with stronger real-time ordering semantics. |
Production judgment. Choose write concern from the invariant and failure model: inventory/payment state commonly deserves majority durability, while derived telemetry may tolerate weaker acknowledgement. Include timeout budgets so degraded deployments fail in a controlled way, but pair timeouts with idempotent operation IDs and reconciliation. Monitor replication lag, write-concern errors, journal/disk latency, election frequency, and p95/p99 write latency. Stronger settings do not repair an over-broad transaction or poor data model.
Bridge. Write concern controls acknowledgement. Lesson 2 asks a different question: which committed or uncommitted version is a read allowed to observe?
docker rm -f atlasmart-mongo-ch15-l1-a atlasmart-mongo-ch15-l1-b atlasmart-mongo-ch15-l1-c 2>/dev/null || truedocker volume rm atlasmart-mongo-ch15-l1-a-data atlasmart-mongo-ch15-l1-b-data atlasmart-mongo-ch15-l1-c-data 2>/dev/null || truedocker network rm atlasmart-ch15-l1-net 2>/dev/null || true
Check your understanding
- What does w:1 prove?
- What changed for majority acknowledgement in MongoDB 8.0?
- Does wtimeout undo the write?
- Why is j:true not equivalent to majority?
- Why can retrying $inc after ambiguous acknowledgement be dangerous?
Review the answers
1. The primary accepted the write; it does not prove replication to another member or protection from rollback.
2. A majority of data-bearing voting members must durably write the oplog entry before acknowledgement; document application on secondaries can follow asynchronously.
3. No. A write-concern timeout can occur after the primary-side mutation succeeded.
4. Journal durability on acknowledging members is a different dimension from replication to a voting majority.
5. The original increment may already exist, so replay can apply the business effect twice.
Authoritative references
- MongoDB 8.3 release notes — Current server release line and patch-sensitive behavior.
-
Write Concern
—
w,j,wtimeout, majority acknowledgment, and journaling behavior. - Read Concern — Supported read-concern levels, operations, and transaction/session compatibility.
- Read Concern majority — Majority-commit visibility and rollback guarantees.
- Read Concern linearizable — Primary-only real-time ordering semantics and latency implications.
- Read Concern snapshot — Point-in-time majority-committed reads and snapshot-history limits.
- Read Preference — Primary/secondary routing modes and stale-read consequences.
- Server Selection Algorithm — Eligibility, latency windows, and per-operation member selection.
- Read Preference Tag Sets — Ordered tag-set matching for replica-set reads.
- maxStalenessSeconds — Coarse secondary-staleness filtering and the 90-second minimum.
- Causal Consistency and Concerns — Read-your-writes, monotonic reads/writes, and writes-follow-reads.
- Read Isolation, Consistency, and Recency — Session guarantees, visibility, and operation-time behavior.
- PyMongo CRUD configuration — Driver read preference, read concern, write concern, and tags.
- PyMongo sessions and causal consistency — ClientSession behavior and causal consistency.
- PyMongo release notes — Current 4.17 driver baseline.
- mongosh release notes — Current 2.10.0 shell baseline.