MongoDB - Atomic Operations
MongoDB - Atomic Operations
Section titled “MongoDB - Atomic Operations”Atomicity ensures that operations are either fully completed or not at all, preventing data inconsistencies. In MongoDB, operations on a single document are always atomic. This tutorial explores how to leverage this for reliable data updates and introduces modern multi-document transactions.
Strategy 1: Single-Document Atomicity via Embedded Documents
Section titled “Strategy 1: Single-Document Atomicity via Embedded Documents”For frequently updated, related data, the best practice is to embed that data within a single document. This leverages MongoDB’s native single-document atomicity. When you update the document, all its embedded fields are updated in one atomic operation.
Let’s model an e-commerce inventory scenario. We’ll track a product’s stock and its purchase history in one document.
// First, let's insert a product into our 'products' collectiondb.products.insertOne({ "_id": "SKU12345", "productName": "Quantum Laptop Pro", "category": "Electronics", "stock": { "total": 10, "available": 10 }, "purchaseHistory": []});// Returns: { "acknowledged": true, "insertedId": "SKU12345" }When a customer buys this laptop, we need to atomically decrease the available stock and record the purchase. This prevents a race condition where two customers might buy the last item simultaneously. We use findOneAndUpdate() for this, as it finds a document and updates it in a single, atomic server call.
// Atomically process a purchase if stock is availabledb.products.findOneAndUpdate( // Filter: Find the product by its ID and ensure stock is available { _id: "SKU12345", "stock.available": { $gt: 0 } }, // Update: Decrement available stock and add a new entry to purchase history { $inc: { "stock.available": -1 }, $push: { "purchaseHistory": { "customerId": "CUST987", "purchaseDate": new Date() } } }, // Options: Return the updated document to confirm the change { returnNewDocument: true });This single operation guarantees that stock is only decremented if it was available, and the purchase is only recorded if the stock was successfully decremented. The entire change is atomic.
Strategy 2: Multi-Document ACID Transactions
Section titled “Strategy 2: Multi-Document ACID Transactions”What if an operation requires updating multiple documents atomically? For example, transferring funds from one user’s account to another. Since MongoDB 4.0, you can use multi-document ACID transactions for these scenarios.
Transactions are handled through a session object. All operations within the transaction are staged, and then either committed (applied) or aborted (discarded) as a single atomic unit.
Here is a conceptual example of a bank transfer:
// Note: This requires a replica set configuration.
// 1. Start a client session.const session = db.getMongo().startSession();
// 2. Start a transaction.session.startTransaction();
const accountsCollection = session.getDatabase('bank').collection('accounts');
try { // 3. Perform the operations within the transaction. // Debit from sender's account accountsCollection.updateOne( { _id: 'accountA' }, { $inc: { balance: -100 } }, { session } );
// Credit to receiver's account accountsCollection.updateOne( { _id: 'accountB' }, { $inc: { balance: 100 } }, { session } );
// 4. Commit the transaction. session.commitTransaction(); console.log('Transaction successfully committed.');
} catch (error) { // 5. If any error occurs, abort the transaction. console.log('An error occurred. Aborting transaction.'); session.abortTransaction(); throw error;
} finally { // 6. End the session. session.endSession();}Best Practice
Section titled “Best Practice”Always prefer the single-document atomic model when your data structure allows for it. It is more performant and less complex than multi-document transactions. Use transactions only when atomicity across multiple documents is an absolute requirement.