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.

Intermediate110–175 minutesReplica-set consistency/latency labMongoDB 8.3.8 · mongosh 2.10.0 · PyMongo 4.17.0Last reviewed: September 2026

Learning objectives

01

Define write concern as an acknowledgment contract rather than a generic consistency switch.

02

Compare w:1, w:"majority", numeric w, j:true, and wtimeout on the same AtlasMart invariant.

03

Explain the MongoDB 8.0+ distinction between durable majority oplog acknowledgment and secondary document application.

04

Observe write-concern errors without assuming that the primary-side write was rolled back.

05

Relate durability choices to failover, tail latency, topology, and idempotent application recovery.

Reproducible lab baseline

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.
MongoDB 8.0+ behavior

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.

isolated three-member replica-set setup (l1)
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

majority calculation, journaling default, and AtlasMart fixture
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

measure w:1, majority, and journal acknowledgement
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);
Measure distributions, not one sample

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.

controlled write-concern timeout and state reconciliation
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.");
Wrong retry instinct

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?

cleanup / full reset
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

  1. What does w:1 prove?
  2. What changed for majority acknowledgement in MongoDB 8.0?
  3. Does wtimeout undo the write?
  4. Why is j:true not equivalent to majority?
  5. 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

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.