Thursday, June 4, 2026
All posts
Lv.2 BeginnerMongoDB
16 min readLv.2 Beginner
SeriesTransactions in PostgreSQL and MongoDB: What's the Difference? · Part 3View series hub

MongoDB Transaction Evolution: WiredTiger, Snapshot Isolation, and Schema Design — Part 3

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

  1. Introduction — "MongoDB Has No Transactions" Is Ancient History
  2. A History of MongoDB Transactions: Version-by-Version Evolution
  3. WiredTiger: The Heart of MongoDB's ACID
    • 3-1. WiredTiger and MVCC
    • 3-2. Snapshot Isolation — How It Differs from PostgreSQL
  4. Multi-Document Transactions in Practice
    • 4-1. Basic Transaction Structure
    • 4-2. Read Concern / Write Concern
    • 4-3. Transaction Constraints and Limitations
  5. MongoDB's Real Strength: Schema Design Over Transactions
    • 5-1. Single-Document Atomicity
    • 5-2. Embedding vs Referencing
  6. Oplog: The Foundation of MongoDB Durability
  7. PostgreSQL MVCC vs MongoDB MVCC
  8. Part 3 Summary
  9. 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.

VersionRelease YearKey Transaction Changes
~3.x~2017Single-document atomicity only. No multi-document transactions
4.02018Multi-document ACID transactions introduced for Replica Set environments
4.22019Transaction support extended to Sharded Clusters
4.42020Collection 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 20252024-2025Bulk 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 ConcernDescriptionRecommended for transactions
localLatest data on the local node (default, can be rolled back)Not recommended
majorityOnly reads data committed on a majority of nodesConditional
snapshotConsistent snapshot from transaction startRecommended

Write Concern — "How many nodes must acknowledge a write for it to succeed?"

Write ConcernDescriptionCharacteristics
w: 1Write succeeds after Primary acknowledgesFast but rollback risk
w: majoritySucceeds after majority replication (recommended)Strong durability, higher latency
w: 0No 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 COMMIT immediately writes to WAL, guaranteeing durability on a single-node basis. In MongoDB, only w: majority provides 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

SituationRecommended approach
Data always read togetherEmbedding
Data queried independentlyReferencing
1:N where N is small (order items)Embedding
1:N where N grows unbounded (post comments)Referencing
Risk of exceeding 16MB document limitReferencing
Reads dominate and duplication is acceptableEmbedding + 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.

ComparisonPostgreSQLMongoDB (WiredTiger)
Old version storageDead Tuples inside table fileSeparate History Store
Old version cleanupVACUUM (manual / AutoVacuum)Automatic (no separate process needed)
Maximum isolation levelSerializable (SSI)Snapshot Isolation
Write Skew preventionYes (SERIALIZABLE)No
Transaction startBEGIN;Create session + startTransaction()
Isolation controlIsolation Level (SQL standard)Read Concern + Write Concern
Distributed transactionsRequires extensions (Citus, etc.)Native support from 4.2
Write conflict handlingLock wait (Blocking)Immediate abort + retry (Non-blocking)
Operational complexityAutoVacuum tuning requiredRelatively 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)

Share This Article

Series Navigation

Transactions in PostgreSQL and MongoDB: What's the Difference?

Current part 3 · 4 published

Explore this topic·Start with featured series

한국어

Follow new posts via RSS

Use RSS to get new posts and series updates directly.

Open RSS Guide