Carry causal time through an AtlasMart workflow so a dependent secondary read waits for the write it must observe.
Causally Consistent Sessions, Read-Your-Writes, Monotonic Reads, and Cluster Time
Contrast a plain stale secondary read with a causally consistent session that carries operation/cluster time across the dependency.
Learning objectives
Define causal consistency and its four guarantees: read-your-writes, monotonic reads, monotonic writes, and writes-follow-reads.
Observe operationTime and
$clusterTime in a PyMongo client session.
Prove that a causal secondary read can wait for a deliberately delayed member to reach the operation it depends on.
Contrast a plain secondary read with a causally consistent majority read/write session.
Explain session scope/threading requirements and why causal ordering is not global linearizability.
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 27109–27111.
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. Member C is tagged and delayed by 20
seconds while made non-voting/priority-0. The delay creates a
visible dependency gap that a causal session can close with
afterClusterTime. 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 dependency: a user confirms stock, then immediately opens the order page
Causal consistency preserves order among operations that have a causal dependency. MongoDB describes four guarantees: read your writes, monotonic reads, monotonic writes, and writes follow reads. It is a session-scoped ordering model—not a claim that every client observes one global real-time order.
| Guarantee | AtlasMart meaning |
|---|---|
| Read your writes | After this session confirms an order, its later read must not show the pre-confirmation version. |
| Monotonic reads | A later session read must not go backward to a state older than one it already observed. |
| Monotonic writes | Dependent writes from the session preserve their causal sequence. |
| Writes follow reads | A write that depends on a read is ordered after the state that read observed. |
MongoDB guarantees the full causal-consistency set under
failures when associated reads use majority read
concern and writes use majority write concern.
Weaker combinations can appear to work while the topology is
healthy but do not preserve the complete guarantees in all
failure scenarios.
docker rm -f atlasmart-mongo-ch15-l4-a atlasmart-mongo-ch15-l4-b atlasmart-mongo-ch15-l4-c 2>/dev/null || truedocker network rm atlasmart-ch15-l4-net 2>/dev/null || truedocker volume rm atlasmart-mongo-ch15-l4-a-data atlasmart-mongo-ch15-l4-b-data atlasmart-mongo-ch15-l4-c-data 2>/dev/null || truedocker network create atlasmart-ch15-l4-netdocker run -d --name atlasmart-mongo-ch15-l4-a --network atlasmart-ch15-l4-net -p 127.0.0.1:27109:27017 -v atlasmart-mongo-ch15-l4-a-data:/data/db mongodb/mongodb-community-server:8.3.8-ubuntu2204-slim --replSet atlasmart-rs15-l4 --bind_ip_alldocker run -d --name atlasmart-mongo-ch15-l4-b --network atlasmart-ch15-l4-net -p 127.0.0.1:27110:27017 -v atlasmart-mongo-ch15-l4-b-data:/data/db mongodb/mongodb-community-server:8.3.8-ubuntu2204-slim --replSet atlasmart-rs15-l4 --bind_ip_alldocker run -d --name atlasmart-mongo-ch15-l4-c --network atlasmart-ch15-l4-net -p 127.0.0.1:27111:27017 -v atlasmart-mongo-ch15-l4-c-data:/data/db mongodb/mongodb-community-server:8.3.8-ubuntu2204-slim --replSet atlasmart-rs15-l4 --bind_ip_alluntil mongosh "mongodb://127.0.0.1:27109/admin?directConnection=true" --quiet --eval 'quit(db.runCommand({ping:1}).ok===1?0:1)'; do sleep 1; donemongosh "mongodb://127.0.0.1:27109/admin?directConnection=true" --quiet --eval 'rs.initiate({_id:"atlasmart-rs15-l4",members:[ {_id:0,host:"atlasmart-mongo-ch15-l4-a:27017"}, {_id:1,host:"atlasmart-mongo-ch15-l4-b:27017"}, {_id:2,host:"atlasmart-mongo-ch15-l4-c:27017"}]})'until mongosh "mongodb://127.0.0.1:27109/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:27109/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:27109/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. Create a 20-second lagging causal-read target
let cfg=rs.conf();const c=cfg.members.find(m=>m.host.startsWith("atlasmart-mongo-ch15-l4-c:"));c.tags={region:"west",purpose:"causal"};c.priority=0; c.votes=0; c.secondaryDelaySecs=20;const b=cfg.members.find(m=>m.host.startsWith("atlasmart-mongo-ch15-l4-b:"));b.tags={region:"east",purpose:"majority"};rs.reconfig(cfg);const app=db.getSiblingDB("atlasmart");app.causal_inventory.drop();app.causal_inventory.insertOne({_id:"sku-drone",state:"available",version:1},{writeConcern:{w:"majority",wtimeout:5000}});printjson(rs.conf().members.map(m=>({host:m.host,tags:m.tags,delay:m.secondaryDelaySecs||0,votes:m.votes})));
3. First prove the stale-read problem without a causal session
from pymongo import MongoClientfrom pymongo.read_preferences import Secondaryfrom pymongo.read_concern import ReadConcernfrom pymongo.write_concern import WriteConcernURI="mongodb://127.0.0.1:27109,127.0.0.1:27110,127.0.0.1:27111/?replicaSet=atlasmart-rs15-l4"client=MongoClient(URI,serverSelectionTimeoutMS=5000)base=client["atlasmart"]["causal_inventory"]writer=base.with_options(write_concern=WriteConcern("majority",wtimeout=5000))west=base.with_options(read_preference=Secondary([{"region":"west"}]),read_concern=ReadConcern("majority"))writer.update_one({"_id":"sku-drone","version":1},{"$set":{"state":"reserved","version":2}})print("plain secondary read:", west.find_one({"_id":"sku-drone"}))client.close()
Because west member C applies writes 20 seconds late, the immediate plain majority read can still return version 1. That is not a violation of majority read concern; version 1 is majority-committed. The missing guarantee is the dependency between this caller’s write and later secondary read.
4. Repeat inside one causally consistent session
import timefrom pymongo import MongoClientfrom pymongo.read_preferences import Secondaryfrom pymongo.read_concern import ReadConcernfrom pymongo.write_concern import WriteConcernURI="mongodb://127.0.0.1:27109,127.0.0.1:27110,127.0.0.1:27111/?replicaSet=atlasmart-rs15-l4"client=MongoClient(URI,serverSelectionTimeoutMS=30000)base=client["atlasmart"]["causal_inventory"]coll=base.with_options( read_preference=Secondary([{"region":"west"}]), read_concern=ReadConcern("majority"), write_concern=WriteConcern("majority",wtimeout=5000),)with client.start_session(causal_consistency=True) as session: print("before", session.operation_time, session.cluster_time) coll.update_one({"_id":"sku-drone","version":2},{"$set":{"state":"picked","version":3}},session=session) print("after write operation_time", session.operation_time) started=time.perf_counter() doc=coll.find_one({"_id":"sku-drone"},session=session) elapsed=time.perf_counter()-started print("causal read",doc,"elapsed_seconds",round(elapsed,3)) print("after read", session.operation_time, session.cluster_time)client.close()
The driver carries the session’s operation time and cluster
time. For the dependent read, it can send
afterClusterTime; the delayed secondary must
advance to at least that causal point before satisfying the
read. Measure the wait rather than asserting exactly 20
seconds—server selection, heartbeat timing, and application
delay all contribute.
5. Session boundaries and misuse
from pymongo import MongoClientfrom pymongo.read_concern import ReadConcernfrom pymongo.write_concern import WriteConcernfrom pymongo.read_preferences import SecondaryPreferredclient=MongoClient("mongodb://127.0.0.1:27109,127.0.0.1:27110,127.0.0.1:27111/?replicaSet=atlasmart-rs15-l4")coll=client["atlasmart"]["causal_inventory"].with_options( read_preference=SecondaryPreferred(), read_concern=ReadConcern("local"), write_concern=WriteConcern(w=1),)with client.start_session(causal_consistency=True) as session: print("causal flag:", session.options.causal_consistency) print("This combination is not the documented majority/majority recipe for full causal guarantees under failover.")client.close()
Setting causal_consistency=True does not
magically strengthen arbitrary read/write concerns. Under
failover/partition scenarios, local +
w:1 cannot guarantee the full causal-consistency
set. Also, use a client session from only the
MongoClient that created it and serialize
operations on that session rather than sharing it concurrently
across threads.
Production judgment. Causal sessions are useful when a workflow reads from secondaries but must preserve its own dependency chain. The price is that a read may wait for the selected member to catch up, making replication lag part of tail latency. Observe session/operation time, replication lag, server selection, and p95/p99 causal read latency. Do not use causal consistency as a substitute for a transaction when multiple writes must be atomic together.
Bridge. Lesson 5 turns these primitives into operation-specific policy instead of a single global “strong consistency” setting.
docker rm -f atlasmart-mongo-ch15-l4-a atlasmart-mongo-ch15-l4-b atlasmart-mongo-ch15-l4-c 2>/dev/null || truedocker volume rm atlasmart-mongo-ch15-l4-a-data atlasmart-mongo-ch15-l4-b-data atlasmart-mongo-ch15-l4-c-data 2>/dev/null || truedocker network rm atlasmart-ch15-l4-net 2>/dev/null || true
Check your understanding
- What are the four causal guarantees?
- Why can a causal secondary read wait?
- Does causal consistency mean every client observes one global real-time order?
- Which concern pairing gives the documented durable causal guarantees?
- Can one ClientSession be used with a different MongoClient?
Review the answers
1. Read-your-writes, monotonic reads, monotonic writes, and writes-follow-reads.
2. The selected secondary may need to advance to the session’s afterClusterTime before returning the dependent read.
3. No. It preserves causal dependencies for the session; linearizability is a different stronger real-time property.
4. Majority read concern and majority write concern.
5. No. Use it only with the client that created it, and do not run concurrent session operations across threads.
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.