Connect transaction-level read concern, write concern, primary read preference, snapshot visibility, and retry labels to observable behavior on a replica set.
Read Concern, Write Concern, Retry Semantics, and Snapshot Behavior Inside Transactions
Separate read concern, write concern, and primary routing; then prove snapshot visibility while another client commits a newer value.
Learning objectives
Separate read concern, write concern, and read preference instead of treating them as one consistency switch.
Prove repeatable snapshot visibility inside a transaction while another client commits a newer value.
Explain why snapshot and
majority guarantees inside transactions depend
on a successful w:"majority" commit.
State why transaction reads must route to the primary.
Connect TransientTransactionError and
UnknownTransactionCommitResult to different
retry scopes.
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 a driver example is used. Transactions
require a replica set or sharded cluster, so the mandatory local
lab runs a disposable replica set published only on loopback
beginning at 127.0.0.1:27079. Authentication and
TLS are disabled only for this isolated learning topology.
Feature Compatibility Version (FCV) is observed and never
changed. Unless a section explicitly overrides them, read/write
concern and read preference use the transaction/client defaults
described beside the example. Atlas, Search, Vector Search, KMS,
and Enterprise Advanced are not mandatory. This lesson uses a
three-member replica set so w:"majority" represents
acknowledgement by an actual voting majority. The client
connects directly to the designated primary for a deterministic
local lab; that direct connection is not a production failover
configuration. The product commands were not executed in this
generation environment because Docker, mongod, mongosh, and
PyMongo are unavailable here; measured output must be recorded
on the learner's machine.
1. Three different controls: visibility, acknowledgement, and routing
Read concern controls the consistency/isolation
properties of reads. Transactions support local,
majority, and snapshot.
Write concern controls acknowledgement at
transaction commit; individual write statements inside a
transaction must not specify their own write concern.
Read preference controls which replica-set
member serves reads; transactions that contain reads must use
primary.
The terms are related but not interchangeable. A transaction
using read concern snapshot obtains its documented
majority-committed snapshot guarantee only when it commits with
write concern majority. On sharded clusters,
snapshot is the transaction read concern that
synchronizes the snapshot across shards.
docker network rm atlasmart-ch13-l3-net 2>/dev/null || truedocker rm -f atlasmart-ch13-l3-n1 2>/dev/null || truedocker volume rm atlasmart-ch13-l3-n1-data 2>/dev/null || truedocker rm -f atlasmart-ch13-l3-n2 2>/dev/null || truedocker volume rm atlasmart-ch13-l3-n2-data 2>/dev/null || truedocker rm -f atlasmart-ch13-l3-n3 2>/dev/null || truedocker volume rm atlasmart-ch13-l3-n3-data 2>/dev/null || truedocker network create atlasmart-ch13-l3-netdocker run -d --name atlasmart-ch13-l3-n1 --network atlasmart-ch13-l3-net -p 127.0.0.1:27079:27017 -v atlasmart-ch13-l3-n1-data:/data/db mongodb/mongodb-community-server:8.3.8-ubuntu2204-slim --replSet atlasmart-rs13-l3 --bind_ip_alldocker run -d --name atlasmart-ch13-l3-n2 --network atlasmart-ch13-l3-net -p 127.0.0.1:27080:27017 -v atlasmart-ch13-l3-n2-data:/data/db mongodb/mongodb-community-server:8.3.8-ubuntu2204-slim --replSet atlasmart-rs13-l3 --bind_ip_alldocker run -d --name atlasmart-ch13-l3-n3 --network atlasmart-ch13-l3-net -p 127.0.0.1:27081:27017 -v atlasmart-ch13-l3-n3-data:/data/db mongodb/mongodb-community-server:8.3.8-ubuntu2204-slim --replSet atlasmart-rs13-l3 --bind_ip_alluntil mongosh "mongodb://127.0.0.1:27079/admin?directConnection=true" --quiet --eval 'quit(db.runCommand({ping:1}).ok === 1 ? 0 : 1)'; do sleep 1; donemongosh "mongodb://127.0.0.1:27079/admin?directConnection=true" --quiet --eval 'rs.initiate({_id:"atlasmart-rs13-l3",members:[{_id:0,host:"atlasmart-ch13-l3-n1:27017",priority:2},{_id:1,host:"atlasmart-ch13-l3-n2:27017"},{_id:2,host:"atlasmart-ch13-l3-n3:27017"}]})' until mongosh "mongodb://127.0.0.1:27079/admin?directConnection=true&replicaSet=atlasmart-rs13-l3" --quiet --eval 'quit(db.hello().isWritablePrimary ? 0 : 1)'; do sleep 1; doneuntil mongosh "mongodb://127.0.0.1:27079/admin?directConnection=true&replicaSet=atlasmart-rs13-l3" --quiet --eval 'const m=rs.status().members; quit(m.filter(x=>x.stateStr==="SECONDARY").length===2 ? 0 : 1)'; do sleep 1; donemongosh "mongodb://127.0.0.1:27079/admin?directConnection=true&replicaSet=atlasmart-rs13-l3" --quiet --eval 'printjson({server:db.version(),setName:db.hello().setName,states:rs.status().members.map(m=>({name:m.name,state:m.stateStr}))}); printjson(db.runCommand({getParameter:1,featureCompatibilityVersion:1}))'
2. Seed one account and inspect defaults/topology
const c=db.accounts_ch13_l3;c.drop();c.insertOne({_id:"acct-1",tenantId:"tenant-a",balanceCents:5000,version:1});printjson(c.findOne({_id:"acct-1"}));printjson(db.getSiblingDB("admin").runCommand({getDefaultRWConcern:1}));printjson(db.hello());print("transaction rules:");print("read concerns: local | majority | snapshot");print("transaction reads must use primary read preference");print("transaction write concern is applied at commit, not per statement");
The three-node lab is still not a production durability benchmark: all members run on one host and likely one physical failure domain. It only makes the acknowledgement mechanism observable.
3. Prove snapshot behavior with two clients
The reader starts a transaction using snapshot.
After its first read establishes the transaction snapshot, a
second client increments the account outside that transaction.
The second read inside the transaction should return the same
pre-update value as the first read. After the read-only
transaction commits, an ordinary outside read sees the newer
committed value.
from pymongo import MongoClientfrom pymongo.read_concern import ReadConcernfrom pymongo.write_concern import WriteConcernfrom pymongo.read_preferences import ReadPreferenceuri = "mongodb://127.0.0.1:27079/?directConnection=true&replicaSet=atlasmart-rs13-l3"reader = MongoClient(uri)writer = MongoClient(uri)coll_r = reader.atlasmart.accounts_ch13_l3coll_w = writer.atlasmart.accounts_ch13_l3with reader.start_session() as session: session.start_transaction( read_concern=ReadConcern("snapshot"), write_concern=WriteConcern("majority"), read_preference=ReadPreference.PRIMARY, ) first = coll_r.find_one({"_id": "acct-1"}, session=session) outside = coll_w.update_one( {"_id": "acct-1"}, {"$inc": {"balanceCents": 700, "version": 1}}, ) second = coll_r.find_one({"_id": "acct-1"}, session=session) print({"first": first, "outside_modified": outside.modified_count, "second": second}) session.commit_transaction()print({"after_commit_outside": coll_w.find_one({"_id": "acct-1"})})reader.close(); writer.close()
Matching first and second values
demonstrates snapshot visibility for this transaction. The
final outside value demonstrates that a newer committed
version existed concurrently. This lab does not prove
geographic durability, cross-shard behavior, or failover
recovery because all replica-set members are local.
4. Retry labels map to different uncertainty
| Signal | Meaning for retry scope | Application consequence |
|---|---|---|
TransientTransactionError |
The transaction as a whole can be retried. | The body may execute again; external side effects must be idempotent/deduplicated. |
UnknownTransactionCommitResult |
The client cannot determine the commit result. | Retry commit; do not assume the transaction failed. |
TransactionTooLargeForCache |
Large transaction/caching pressure failure; server behavior changed in 6.2. | Do not expect callback retry to make an oversized transaction safe; shrink/redesign it. |
The driver callback API incorporates the standard retry rules. A lower-level/core API gives you more control but makes you responsible for correctly inspecting error labels.
5. Production judgment
Use snapshot when one transaction must reason over
a stable multi-document view; do not enable it reflexively for
every request. Long snapshots retain older versions and can
increase storage/cache pressure. Pair the read guarantee with a
commit write concern that provides the durability property you
actually intend. A w:1 commit can be rolled back
during failover and does not give the same guarantees to
transaction-level majority/snapshot
reads.
On a sharded cluster, majority read concern does
not synchronize one snapshot across shards;
snapshot does. Even after a multi-shard commit,
some outside reads with weaker concerns need not wait for all
shards to expose the transaction simultaneously. Record topology
and concern settings beside every consistency claim.
Bridge. Lesson 4 asks how long a transaction may run, how it waits for locks, what operations are restricted, and how multi-shard coordination expands the failure surface.
docker rm -f atlasmart-ch13-l3-n1docker rm -f atlasmart-ch13-l3-n2docker rm -f atlasmart-ch13-l3-n3docker volume rm atlasmart-ch13-l3-n1-datadocker volume rm atlasmart-ch13-l3-n2-datadocker volume rm atlasmart-ch13-l3-n3-datadocker network rm atlasmart-ch13-l3-net
Check your understanding
- Which transaction read concern synchronizes a snapshot across shards?
- Can individual writes inside a transaction set their own write concern?
- What read preference must a transaction with reads use?
- Why can an in-transaction read be stale relative to a newer outside commit?
-
Why is
w:"majority"important with transactionsnapshot?
Review the answers
1. snapshot.
2. No. Transaction write concern is applied at commit; per-operation write concern inside a transaction is not the model.
3. primary.
4. The transaction can continue reading from its established snapshot.
5. The documented majority-committed snapshot guarantee depends on committing the transaction with majority write concern.
Authoritative references
- MongoDB 8.3 release notes — Current 8.3 release line and patch-sensitive server behavior.
- Atomicity and transactions — Single-document atomicity and guidance to minimize unnecessary distributed transactions.
- Transactions — Sessions, transaction read/write concern, read preference, and transaction semantics.
- Drivers API for transactions — Callback versus core APIs and retry labels for transient transactions and ambiguous commits.
- Transaction production considerations — Runtime, locking, cache, DDL, conflicts, and operational constraints.
- Sharded transaction considerations — Cross-shard snapshot semantics, commit coordination, migrations, and outside reads.
- Transactions and operations — Operations permitted and prohibited inside transactions.
- $currentOp — Session/transaction observability including lsid, txnNumber, timing, and sharded coordinators.
- PyMongo transactions — PyMongo session, with_transaction, retry, and callback behavior.
- PyMongo release notes — Current PyMongo 4.17 behavior and session APIs.
- mongosh release notes — Current mongosh 2.10.0 release baseline.
- Read concern snapshot — Snapshot visibility and majority-commit requirements.
- Read preference — Primary routing requirement for transactions that perform reads.