A distributed transaction coordinates operations across multiple documents, collections, databases, or shards so they commit or roll back together. MongoDB’s single-document atomicity often avoids this need when related data is embedded, but multidocument transactions support cases requiring cross-resource consistency. section 8.4
Use distributed transactions deliberately:
- Prefer embedded documents and arrays when they keep related updates within one document. section 8.4 section 8.4.3
- Use the Callback API, whose higher-level transaction method starts, commits, and retries transactions after transient errors. section 8.4.3
- Keep transactions short, indexed, and within the 60-second default timeout and 1,000-document modification limit. section 8.5
- Expect additional performance overhead across multiple shards, and configure suitable read and write concerns. section 8.5
Account transaction workflow
A Node.js application connects with MongoClient, starts a session, sets snapshot reads, majority writes, and primary reads, then runs account, customer, and transaction updates inside session.withTransaction. Replace the connection string, save the script as transaction.js, and run it with an account ID. section 8.4.3
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)
Inspect the complete Node.js implementation, including session setup, transaction options, collection access, and callback structure. section 8.4.3



