Atomicity makes an operation behave as one indivisible unit: it either completes entirely or leaves no change behind. This prevents partial updates when an application encounters an error, crash, or network problem. section 8.0 section 8.3
MongoDB provides atomicity for every single-document insert, update, and delete. The operation preserves the document’s pretransaction state if it fails, while WiredTiger applies changes in memory before writing a successful result to disk. section 8.2
Choose the smallest atomic boundary that satisfies the application:
- Model related data with embedded documents or arrays when one document can contain the required relationship; this can avoid distributed transactions. section 8.4
- Use a multidocument transaction when operations must succeed together across documents, collections, databases, or shards. section 8.4
- Account for optimistic concurrency control: conflicts are detected through snapshots and version or timestamp changes, then may cause an abort, restart, wait, or retry. section 8.2
Travel booking
A booking transaction reserves a flight and a hotel together. If the hotel reservation fails after the flight succeeds, aborting the transaction also removes the flight reservation, preserving the previous database state. section 8.0
Listing 8.2 Executing a multidocument transaction with Node.js and Callback API
const { MongoClient } = require('mongodb')
// Replace with your actual connection string
const uri = "your_mongodb_connection_string"
async function run(accountId) {
const client = new MongoClient(uri)
try {
await client.connect()
const session = client.startSession()
const transactionOptions = {
readConcern: { level: 'snapshot' },
writeConcern: { w: 'majority' },
readPreference: 'primary'
}
await session.withTransaction(async () => {
const accounts = client.db('sample_analytics').collection('accounts')
const customers = client.db('sample_analytics').collection('customers')
const transactions = client.db('sample_analytics').collection('transactions')
const currentDate = new Date()
const account = await accounts.findOne(
{ account_id: parseInt(accountId) },
{ session }
)
if (!account) throw new Error('Account not found')
const accountsUpdateResult = await accounts.updateOne(
{ account_id: parseInt(accountId) },
{
$set: { limit: 12000, last_transaction_date: currentDate },
$inc: { transaction_count: 1 }
},
{ session }
)
const customersUpdateResult = await customers.updateMany(
{ accounts: { $in: [parseInt(accountId)] } },
{
$inc: { transaction_count: 1 },
$set: { last_transaction_date: currentDate }
},
{ session }
)
const transactionsInsertResult = await transactions.insertOne(
{
account_id: parseInt(accountId),
transaction_count: account.transaction_count + 1,
bucket_start_date: currentDate,
bucket_end_date: currentDate,
transactions: [
{
date: currentDate,
amount: 1500,
transaction_code: 'buy',
symbol: 'amzn',
price: '125.00',
total: '187500.00'
}
]
},
{ session }
)
console.log("Transaction committed.")
console.log("Accounts updated:", accountsUpdateResult.modifiedCount)
console.log("Customers updated:", customersUpdateResult.modifiedCount)
console.log("New transaction inserted:", transactionsInsertResult.insertedId)
}, transactionOptions)
session.endSession()
} catch (error) {
console.error("Transaction aborted due to error:", error)
} finally {
await client.close()
}
}
// Get accountId from command line arguments
const accountId = process.argv[2]
if (!accountId) {
console.error("Please provide an account ID as an argument.")
process.exit(1)
}
run(accountId).catch(console.dir)
Use the Node.js Callback API example to inspect a multidocument transaction that updates account and customer records and inserts a transaction record within one withTransaction callback. section 8.4.3












