MongoDB Transaction Evolution: WiredTiger, Snapshot Isolation, and Schema Design — Part 3
"MongoDB has no transactions" was true in 2013. In 2026, MongoDB supports WiredTiger-based MVCC, multi-document ACID transactions, and distributed transactions on sharded clusters. This post covers the explicit session and Read/Write Concern model that sets it apart from PostgreSQL's BEGIN/COMMIT, the differences in MVCC implementation, and the schema design philosophy that reduces the need for transactions in the first place.
Series — Transactions in PostgreSQL and MongoDB: What's the Difference?
- Part 1 — Transaction Fundamentals and ACID Principles
- Part 2 — PostgreSQL Transactions Deep Dive (MVCC, Isolation Levels, Deadlocks, VACUUM)
- Part 3 — MongoDB Transaction Evolution and Multi-Document Transactions (current)
- Part 4 — Real-world Comparison — Performance, Scalability, and Which DB to Choose?
Table of Contents
- Introduction — "MongoDB Has No Transactions" Is Ancient History
- A History of MongoDB Transactions: Version-by-Version Evolution
- WiredTiger: The Heart of MongoDB's ACID
- 3-1. WiredTiger and MVCC
- 3-2. Snapshot Isolation — How It Differs from PostgreSQL
- Multi-Document Transactions in Practice
- 4-1. Basic Transaction Structure
- 4-2. Read Concern / Write Concern
- 4-3. Transaction Constraints and Limitations
- MongoDB's Real Strength: Schema Design Over Transactions
- 5-1. Single-Document Atomicity
- 5-2. Embedding vs Referencing
- Oplog: The Foundation of MongoDB Durability
- PostgreSQL MVCC vs MongoDB MVCC
- Part 3 Summary
- Practical application notes
1. Introduction — "MongoDB Has No Transactions" Is Ancient History
Around 2013, any developer learning MongoDB would hear this warning:
"MongoDB doesn't support transactions. If you need consistency across multiple documents, you have to handle that at the application level."
That was then. In 2026, MongoDB fully supports multi-document ACID transactions, distributed transactions on sharded clusters, and Snapshot Isolation. It has evolved from a simple NoSQL document store into a database with enterprise-grade consistency guarantees.
But that raises an important question:
"Now that MongoDB supports transactions, can I just use it like PostgreSQL?"
The answer is closer to "no." MongoDB transactions differ from PostgreSQL in implementation, philosophy, and recommended usage patterns. This part examines how MongoDB implements transactions, and when and how to use them properly.
2. A History of MongoDB Transactions: Version-by-Version Evolution
MongoDB's transaction support didn't happen overnight. It is the result of a storage engine replacement and years of incremental feature expansion.
| Version | Release Year | Key Transaction Changes |
|---|---|---|
| ~3.x | ~2017 | Single-document atomicity only. No multi-document transactions |
| 4.0 | 2018 | Multi-document ACID transactions introduced for Replica Set environments |
| 4.2 | 2019 | Transaction support extended to Sharded Clusters |
| 4.4 | 2020 | Collection and index creation allowed inside transactions |
| 5.0+ | 2021~ | Time-series collections, enhanced cluster-level snapshot reads |
| 6.0+ | 2022~ | Queryable Encryption, Change Streams improvements |
| 7.0+ | 2023~ | Atlas Vector Search integration, large transaction processing improvements |
| 8.0 / Atlas 2025 | 2024-2025 | Bulk write performance improvements, AI-native query integration |
The Pivotal Moment: WiredTiger
The most significant event in MongoDB's transaction history is the adoption of the WiredTiger storage engine as the default (v3.2). The older MMAP engine used collection-level locks, but WiredTiger introduced document-level concurrency control and MVCC, making true ACID-based multi-document transactions possible.
3. WiredTiger: The Heart of MongoDB's ACID
3-1. WiredTiger and MVCC
WiredTiger is the storage engine MongoDB chose to enable transaction support, and it has MVCC (Multi-Version Concurrency Control) built in.
Conceptually, WiredTiger's approach to data changes is similar to PostgreSQL's MVCC — it creates a new version rather than overwriting existing data, and each transaction reads data based on its own snapshot timestamp.
When Transaction A starts:
→ WiredTiger assigns snapshot timestamp T1
→ Throughout the transaction, data is read consistently from snapshot T1
→ Other transactions' commits do not alter A's snapshot
However, there is a critical difference from PostgreSQL MVCC. WiredTiger stores old versions not inside the table file but in a separate History Store. While PostgreSQL leaves Dead Tuples inside the table file and cleans them up with VACUUM, WiredTiger manages the History Store automatically.
PostgreSQL requires a separate VACUUM process, but MongoDB (WiredTiger) does not need a dedicated cleanup process. This is a meaningful operational difference.
3-2. Snapshot Isolation — How It Differs from PostgreSQL
MongoDB's multi-document transactions provide Snapshot Isolation. In SQL standard terms, this is closest to REPEATABLE READ.
MongoDB transaction isolation (Read Concern: snapshot):
Dirty Reads: prevented
Non-Repeatable Reads: prevented
Phantom Reads: prevented (snapshot-based)
Write Skew: possible (not Serializable)
PostgreSQL supports SERIALIZABLE isolation (using SSI), but MongoDB's highest isolation level is Snapshot Isolation. This matters in financial domains where Write Skew anomalies are critical.
Write Skew example: Two transactions simultaneously read the same data and modify different documents, breaking a logical constraint. For instance, a constraint says "at least one doctor must be on call." Both doctors simultaneously see the other is available and decide to cancel their shift — leaving zero doctors on call.
4. Multi-Document Transactions in Practice
4-1. Basic Transaction Structure
MongoDB transactions must always be initiated through a Session. This is the biggest usage difference from PostgreSQL. PostgreSQL starts a transaction with a single BEGIN, but MongoDB requires explicitly creating and managing a client session.
Node.js (MongoDB Driver) example
const { MongoClient } = require('mongodb');
const client = new MongoClient(uri);
async function transferFunds(fromId, toId, amount) {
// 1. Start session (required!)
const session = client.startSession();
try {
// 2. Start transaction
session.startTransaction({
readConcern: { level: 'snapshot' },
writeConcern: { w: 'majority' }
});
const accounts = client.db('bank').collection('accounts');
// 3. Debit
await accounts.updateOne(
{ _id: fromId, balance: { $gte: amount } },
{ $inc: { balance: -amount } },
{ session } // session must be passed to every operation!
);
// 4. Credit
await accounts.updateOne(
{ _id: toId },
{ $inc: { balance: amount } },
{ session }
);
// 5. Commit
await session.commitTransaction();
} catch (error) {
// 6. Roll back on failure
await session.abortTransaction();
throw error;
} finally {
// 7. End session (prevents memory leaks!)
await session.endSession();
}
}
Python (PyMongo) — safer with the with statement
from pymongo import MongoClient
client = MongoClient(uri)
def transfer_funds(from_id, to_id, amount):
with client.start_session() as session:
with session.start_transaction():
accounts = client['bank']['accounts']
accounts.update_one(
{'_id': from_id, 'balance': {'$gte': amount}},
{'$inc': {'balance': -amount}},
session=session
)
accounts.update_one(
{'_id': to_id},
{'$inc': {'balance': amount}},
session=session
)
# Normal exit: auto commitTransaction
# Exception: auto abortTransaction
4-2. Read Concern / Write Concern
Unlike PostgreSQL's isolation levels, MongoDB controls consistency through two axes: Read Concern and Write Concern. This design reflects MongoDB's architecture, which is built around distributed Replica Set environments.
Read Concern — "Which version of data should I read?"
| Read Concern | Description | Recommended for transactions |
|---|---|---|
local | Latest data on the local node (default, can be rolled back) | Not recommended |
majority | Only reads data committed on a majority of nodes | Conditional |
snapshot | Consistent snapshot from transaction start | Recommended |
Write Concern — "How many nodes must acknowledge a write for it to succeed?"
| Write Concern | Description | Characteristics |
|---|---|---|
w: 1 | Write succeeds after Primary acknowledges | Fast but rollback risk |
w: majority | Succeeds after majority replication (recommended) | Strong durability, higher latency |
w: 0 | No acknowledgment wait (Fire and Forget) | Maximum throughput, data loss risk |
// Safest transaction configuration
session.startTransaction({
readConcern: { level: 'snapshot' }, // consistent snapshot reads
writeConcern: { w: 'majority' } // durability across majority of nodes
});
Key difference from PostgreSQL: A PostgreSQL
COMMITimmediately writes to WAL, guaranteeing durability on a single-node basis. In MongoDB, onlyw: majorityprovides the equivalent durability guarantee from the perspective of the entire replica cluster.
4-3. Transaction Constraints and Limitations
MongoDB transactions are powerful, but come with important constraints to be aware of.
Environment requirements
- Transactions do not work on Standalone servers
- Replica Set is required (single-node Replica Set recommended even for development)
- Sharded Cluster requires MongoDB 4.2 or later
Time limit
// Default transaction timeout: 60 seconds
// Automatically aborted when exceeded (aborted by cleanup process)
// Can be adjusted, but increasing it worsens WiredTiger cache pressure
db.adminCommand({
setParameter: 1,
transactionLifetimeLimitSeconds: 30 // default is 60
})
WiredTiger cache pressure
While a transaction is active, WiredTiger must hold the snapshot from the transaction's start time in memory. The longer the transaction runs — or the more concurrent transactions are open — the more uncommitted writes accumulate in cache, potentially degrading the entire cluster's performance.
MongoDB official recommendations:
- Minimize the number of documents modified within a transaction
- Aim to complete transactions within 1 second when possible
- Never run large batch operations inside a transaction
Feature restrictions
// Operations not supported inside transactions:
// - $graphLookup (against sharded collections)
// - createCollection (cross-shard writes in sharded clusters)
// - Changing shard keys
// - Some index build operations
5. MongoDB's Real Strength: Schema Design Over Transactions
This is where the most important perspective shift is needed.
"If you find yourself needing a transaction in MongoDB, first revisit your schema design."
This is the core message of MongoDB's official documentation and design philosophy. MongoDB is built so that a well-designed single-document structure can eliminate the need for most transactions from the start.
5-1. Single-Document Atomicity
Every operation on a single MongoDB document is always atomic. This is a foundational principle that hasn't changed since before version 4.0 — and won't change after.
// This single update always executes atomically (no transaction needed)
await orders.updateOne(
{ _id: orderId },
{
$set: { status: 'shipped' },
$push: { history: { action: 'shipped', timestamp: new Date() } },
$inc: { shipmentCount: 1 }
}
);
// All three changes ($set, $push, $inc) execute as one atomic operation
5-2. Embedding vs Referencing — Designing Away the Need for Transactions
The core principle of MongoDB schema design is: "store data that is read together, together."
Poor design (directly porting RDB structure)
Collection: orders Collection: order_items Collection: payments
{ _id: "ORD001" } -> { orderId: "ORD001" } + { orderId: "ORD001" }
{ orderId: "ORD001" }
Updating order + items + payment simultaneously in this structure requires a multi-document transaction.
Good design (MongoDB embedding)
// All related data embedded in a single orders document
{
_id: "ORD001",
userId: "USR123",
status: "confirmed",
items: [
{ productId: "P001", name: "Laptop", qty: 1, price: 1200 },
{ productId: "P002", name: "Mouse", qty: 2, price: 35 }
],
payment: {
method: "card",
amount: 1270,
paidAt: ISODate("2026-04-22T09:00:00Z")
},
history: [
{ action: "created", at: ISODate("2026-04-22T08:55:00Z") },
{ action: "confirmed", at: ISODate("2026-04-22T09:00:00Z") }
]
}
With this structure, updating order status, payment details, and appending history can all be done atomically with a single updateOne — no transaction needed.
When to embed vs when to reference
| Situation | Recommended approach |
|---|---|
| Data always read together | Embedding |
| Data queried independently | Referencing |
| 1:N where N is small (order items) | Embedding |
| 1:N where N grows unbounded (post comments) | Referencing |
| Risk of exceeding 16MB document limit | Referencing |
| Reads dominate and duplication is acceptable | Embedding + denormalization |
6. Oplog: The Foundation of MongoDB Durability
Just as PostgreSQL uses WAL (Write-Ahead Log) for durability, MongoDB relies on Oplog (Operations Log).
Oplog's role from a transaction perspective:
- All write operations within a transaction are recorded as one or more Oplog entries at commit time
- Before MongoDB 4.2: the entire transaction was bound by the single 16MB BSON document limit
- From MongoDB 4.2: multiple Oplog entries are created as needed, effectively removing the size limit
// Oplog entry structure at transaction commit (conceptual)
{
op: "tx",
ts: Timestamp(1682000000, 1),
lsid: { id: UUID("...") }, // session ID
txnNumber: NumberLong(1), // transaction number
applyOps: [ // all operations in the transaction
{ op: "u", ns: "bank.accounts", ... },
{ op: "u", ns: "bank.accounts", ... }
]
}
7. PostgreSQL MVCC vs MongoDB MVCC
Both databases use MVCC, but differ significantly in implementation and operational behavior.
| Comparison | PostgreSQL | MongoDB (WiredTiger) |
|---|---|---|
| Old version storage | Dead Tuples inside table file | Separate History Store |
| Old version cleanup | VACUUM (manual / AutoVacuum) | Automatic (no separate process needed) |
| Maximum isolation level | Serializable (SSI) | Snapshot Isolation |
| Write Skew prevention | Yes (SERIALIZABLE) | No |
| Transaction start | BEGIN; | Create session + startTransaction() |
| Isolation control | Isolation Level (SQL standard) | Read Concern + Write Concern |
| Distributed transactions | Requires extensions (Citus, etc.) | Native support from 4.2 |
| Write conflict handling | Lock wait (Blocking) | Immediate abort + retry (Non-blocking) |
| Operational complexity | AutoVacuum tuning required | Relatively simpler |
The difference in write conflict handling is particularly significant in production. PostgreSQL holds a lock and waits on conflict; MongoDB immediately detects the conflict and throws a retriable exception.
// MongoDB write conflict retry pattern
async function runWithRetry(txnFunc, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const session = client.startSession();
try {
session.startTransaction();
await txnFunc(session);
await session.commitTransaction();
return; // success
} catch (err) {
await session.abortTransaction();
// TransientTransactionError can be retried
if (err.errorLabels?.includes('TransientTransactionError') && attempt < maxRetries - 1) {
continue;
}
throw err;
} finally {
session.endSession();
}
}
}
8. Part 3 Summary
Here's the full picture of MongoDB's transaction journey:
Key takeaways:
- History: Single-document atomicity → 4.0 Replica Set multi-document transactions → 4.2 distributed transactions → enterprise-grade completeness today
- WiredTiger: History Store-based MVCC. No VACUUM needed unlike PostgreSQL
- Isolation level: Snapshot Isolation (equivalent to REPEATABLE READ). Serializable is not supported
- Read/Write Concern: MongoDB's native consistency control axes for distributed environments
- Transaction constraints: 60-second time limit, Replica Set required, WiredTiger cache pressure to watch
- Schema design first: Reducing the need for transactions through well-designed embedding is core MongoDB philosophy
Part 4 directly compares PostgreSQL and MongoDB on performance, scalability, and real-world use cases, and offers a clear decision guide for choosing between them.
References
- MongoDB Documentation: Transactions
- MongoDB Documentation: Production Considerations
- MongoDB Blog: Multi-Document ACID Transactions GA
- DZone: Isolation Level for MongoDB Multi-Document Transactions
- DEV.to: MongoDB Read/Write vs PostgreSQL Synchronous Replication
- Medium: Understanding MongoDB Transactions Atomicity
- MongoDB: 6 Rules of Thumb for Schema Design
- Mafiree: MongoDB Transactions Comprehensive Guide (2026)