8 Executing multi-document ACID transactions
Transactions group related reads and writes so they either all succeed or all fail, preventing partial updates. While MongoDB long relied on single-document atomicity to preserve integrity, it now supports multi-document ACID transactions, broadening its applicability to more complex workflows. Under the hood, the WiredTiger storage engine enables high concurrency and durability through MVCC snapshots, periodic checkpoints, and write-ahead journaling, complemented by compression and effective memory caching to balance performance, footprint, and resilience.
At the single-document level, operations are atomic and benefit from optimistic concurrency control that detects write conflicts and retries when necessary. The chapter revisits ACID—atomicity, consistency, isolation, durability—and explains when multi-document transactions are warranted, especially when relationships span multiple documents, collections, or shards. Although embedding data often removes the need for distributed transactions, MongoDB’s implementation provides end-to-end consistency across shards when such coordination is required.
Practically, MongoDB exposes two ways to run transactions: a lower-level core API and a higher-level callback API, with the latter recommended for its built-in commit/abort handling and retry logic for transient errors. All approaches rely on logical sessions to preserve causal ordering of operations, and the chapter demonstrates usage in mongosh as well as production-ready patterns with Node.js, Python, and Ruby. Key guidance includes modeling related data together to minimize transaction scope, ensuring indexed operations, splitting long-running work to avoid timeouts, choosing appropriate read/write concerns, limiting the number of modified documents, and accounting for multi-shard overhead; it also details operations that are not permitted within transactions, such as certain cross-shard collection creations, some DDL with non-local read concern, and non-CRUD administrative commands.
Summary
- Transactions in DBMS ensure sets of related reads and writes actions either fully succeed or fail as a unit, preventing partial updates.
- WiredTiger, MongoDB's default storage engine, enhances performance and scalability with row-oriented, column-oriented, and LSM storage configurations.
- Write-ahead logging and checkpoints in WiredTiger ensure data durability, with changes logged in a journal to prevent data loss if MongoDB exits unexpectedly.
- Compression in WiredTiger reduces storage needs for collections and indexes, using Snappy for collections and prefix compression for indexes, though it increases CPU usage.
- WiredTiger supports document-level concurrency for write operations, allowing multiple clients to modify documents simultaneously within the same collection.
- Optimistic Concurrency Control (OCC) in WiredTiger minimizes upfront locking and uses versioning to handle conflicts, enhancing performance when write conflicts are infrequent.
- MongoDB offers two APIs for managing ACID transactions: the core API, similar to relational databases, and the callback API, which automates processes.
- The core API requires manual handling of transaction operations, commits, and error resolution, lacking automatic retry mechanisms.
- MongoDB allows multi-document transactions within sharded clusters, spanning multiple shards.
- Organize your data so that related information is kept together. This boosts performance and frequently makes transactions unnecessary.
FAQ
What are multi-document ACID transactions in MongoDB, and when should I use them?
Multi-document transactions group multiple reads and writes so they either all succeed or all fail together. Use them when you must update related data across multiple documents, collections, databases, or shards atomically. In many cases, embedding related data in a single document avoids the need for distributed transactions by leveraging MongoDB’s single-document atomicity.How does MongoDB ensure atomicity for single-document operations?
All single-document operations (insert, update, delete) are atomic. With WiredTiger, changes are applied in memory and written to disk only after the operation fully succeeds. Document-level concurrency uses optimistic concurrency control: if a write conflict is detected, MongoDB retries the operation.What is WiredTiger and how does it support transactions?
WiredTiger is MongoDB’s default storage engine offering MVCC for high concurrency, compression to reduce storage and I/O, and in-memory caching. It provides ACID properties with standard isolation levels, durable checkpoints, and journaling to ensure that committed data survives crashes.What are snapshots, checkpoints, and snapshot history in WiredTiger?
- MVCC provides consistent, point-in-time snapshots for reads.- Checkpoints are written roughly every 60 seconds; the previous checkpoint remains valid until the metadata update makes the new one permanent.
- You can configure snapshot history retention with the minSnapshotHistoryWindowInSeconds parameter; history is stored in WiredTigerHS.wt and increasing retention uses more disk.
How does journaling contribute to durability?
WiredTiger uses write-ahead logging (the journal) to record modifications between checkpoints. If the server exits unexpectedly, MongoDB replays journal entries since the last checkpoint to avoid data loss. Journal entries are Snappy-compressed by default and can be configured via storage.wiredTiger.engineConfig.journalCompressor.What’s the difference between the Core API and the Callback API for transactions?
- Core API: you explicitly start and commit/abort transactions and implement your own retry/error handling. It does not automatically handle TransientTransactionError or UnknownTransactionCommitResult.- Callback API: runs your operations inside a managed transaction that starts, commits, or aborts automatically and includes built-in retry logic for common transient errors.
- Both require explicitly passing a logical session.
How do I run a transaction in mongosh and handle retries?
- Start a session and call session.startTransaction with readConcern: snapshot, writeConcern: majority, and readPreference: primary.- Perform your reads and writes associated with the session.
- Commit with session.commitTransaction(), or abort on error.
- Implement a retry loop if the error includes TransientTransactionError or UnknownTransactionCommitResult; create a fresh session for each attempt and end the session in a finally block.
How do drivers like Node.js, Python, and Ruby implement transactions with the callback API?
- Drivers provide a session-scoped helper (for example, withTransaction/with_transaction) that starts a transaction, runs your callback, and commits or aborts with built-in retry logic.- Typical flow: read the target document (e.g., an account), update related documents (accounts and customers), insert a new transaction record, then commit. On failure, the helper aborts and retries when appropriate. Each operation is executed with the session.
What are best practices and limits for multi-document transactions?
- Model related data together to minimize the need for transactions.- Keep transactions short; split long-running work to avoid the default ~60-second timeout (you can increase it if needed).
- Ensure all operations use indexes.
- Limit each transaction to modifying at most 1,000 documents.
- Set appropriate read/write concerns.
- Implement robust retry logic for transient errors.
- Expect additional overhead when transactions span multiple shards.
Which operations are not allowed inside transactions?
- Creating new collections in cross-shard write transactions (implicit creation on another shard is disallowed).- Explicitly creating collections or indexes when using a read concern other than "local" (for example, db.createCollection or db.collection.createIndexes).
- listCollections and listIndexes (and their helpers).
- Other non-CRUD, non-informational operations such as createUser, getParameter, count, etc.
MongoDB 8.0 in Action, Third Edition ebook for free