| name | mongodb-transactions |
| version | 2.1.0 |
| description | Master MongoDB ACID transactions for multi-document operations. Learn session management, transaction mechanics, error handling, and production patterns. Guarantee data consistency across multiple operations. |
| sasmp_version | 1.3.0 |
| bonded_agent | 07-mongodb-application-development |
| bond_type | PRIMARY_BOND |
| capabilities | session-management, multi-document-transactions, retry-logic, error-recovery, consistency-guarantees |
| input_validation | [object Object] |
| output_format | [object Object] |
| error_handling | [object Object] |
| prerequisites | [object Object] |
| testing | [object Object] |
MongoDB Multi-Document Transactions
Guarantee consistency with ACID transactions across multiple operations.
Quick Start
Basic Transaction
const session = client.startSession()
try {
await session.withTransaction(async () => {
// All operations here are atomic
await users.insertOne({ name: 'John' }, { session })
await accounts.insertOne({ userId: 'xxx', balance: 100 }, { session })
// If any fails, ALL roll back
})
} catch (error) {
console.error('Transaction failed:', error)
} finally {
await session.endSession()
}
Real-World: Money Transfer
async function transferMoney(fromId, toId, amount) {
const session = client.startSession()
try {
await session.withTransaction(async () => {
// Deduct from account A
await accounts.updateOne(
{ _id: fromId },
{ $inc: { balance: -amount } },
{ session }
)
// Add to account B
await accounts.updateOne(
{ _id: toId },
{ $inc: { balance: amount } },
{ session }
)
// Both succeed, or both rollback - NO PARTIAL TRANSFERS!
})
} catch (error) {
// Transaction failed, money not transferred
console.error('Transfer failed:', error)
} finally {
await session.endSession()
}
}
Transaction Requirements
MongoDB Version
- MongoDB 4.0+: Single-document transactions (all versions)
- MongoDB 4.0: Multi-document transactions (replica sets)
- MongoDB 4.2+: Multi-document transactions (sharded clusters)
Deployment Type
- Replica Set: Required for transactions
- Sharded Cluster: MongoDB 4.2+
- Standalone: Single-document transactions only
- Atlas Free: No transactions (shared clusters)
Session Management
Create Session
const session = client.startSession()
// Configure session
const session = client.startSession({
defaultTransactionOptions: {
readConcern: { level: 'snapshot' },
writeConcern: { w: 'majority' },
readPreference: 'primary'
}
})
Session Lifecycle
// 1. Start session
const session = client.startSession()
// 2. Start transaction
session.startTransaction()
// 3. Execute operations with session
await collection.insertOne(doc, { session })
// 4. Commit or abort
await session.commitTransaction() // Success
await session.abortTransaction() // Rollback
// 5. End session
await session.endSession()
Transaction Options
Read Concern
// snapshot: See committed data at transaction start
// local: See latest data (default)
// majority: See data confirmed on majority
// linearizable: Latest confirmed data
await session.withTransaction(async () => {
// Operations
}, {
readConcern: { level: 'snapshot' }
})
Write Concern
// w: 1 (default): Acknowledged by primary
// w: 'majority': Acknowledged by majority
// w: N: Acknowledged by N replicas
await session.withTransaction(async () => {
// Operations
}, {
writeConcern: { w: 'majority' }
})
Read Preference
// primary: Read from primary only
// primaryPreferred: Primary, fallback to secondary
// secondary: Read from secondary
// secondaryPreferred: Secondary, fallback to primary
// nearest: Nearest server
{
readPreference: 'primary'
}
Error Handling
Handle Transaction Errors
async function robustTransaction() {
const session = client.startSession()
try {
await session.withTransaction(async () => {
// Operations
})
} catch (error) {
if (error.hasErrorLabel('TransientTransactionError')) {
// Temporary error - retry
return robustTransaction()
} else if (error.hasErrorLabel('UnknownTransactionCommitResult')) {
// Commit outcome unknown - check state
console.log('Commit outcome unknown')
} else {
// Fatal error
throw error
}
} finally {
await session.endSession()
}
}
Retry Logic
async function executeWithRetry(fn, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const session = client.startSession()
try {
await session.withTransaction(async () => {
await fn(session)
})
return // Success
} catch (error) {
if (error.hasErrorLabel('TransientTransactionError') && attempt < maxRetries) {
continue // Retry
} else {
throw error
}
} finally {
await session.endSession()
}
}
}
Real-World Examples
Order Placement with Inventory
async function placeOrder(userId, items) {
const session = client.startSession()
try {
await session.withTransaction(async () => {
// 1. Create order
const order = {
userId,
items,
status: 'pending',
createdAt: new Date()
}
const orderResult = await orders.insertOne(order, { session })
// 2. Update inventory
for (const item of items) {
const updated = await products.findOneAndUpdate(
{ _id: item.productId },
{ $inc: { stock: -item.quantity } },
{ session, returnDocument: 'after' }
)
// Check if stock went negative
if (updated.value.stock < 0) {
throw new Error('Insufficient inventory')
}
}
// 3. Deduct from user account
const userUpdate = await users.findOneAndUpdate(
{ _id: userId },
{ $inc: { balance: -calculateTotal(items) } },
{ session, returnDocument: 'after' }
)
if (userUpdate.value.balance < 0) {
throw new Error('Insufficient funds')
}
return orderResult.insertedId
})
} catch (error) {
// ALL operations rolled back if any fails
// Inventory stays same
// User balance unchanged
// Order not created
console.error('Order failed:', error.message)
throw error
} finally {
await session.endSession()
}
}
Account Reconciliation
async function reconcileAccounts(mainId, secondaryIds) {
const session = client.startSession()
try {
await session.withTransaction(async () => {
// Calculate total balance
const secondary = await accounts.find(
{ _id: { $in: secondaryIds } },
{ session }
).toArray()
const totalBalance = secondary.reduce((sum, acc) => sum + acc.balance, 0)
// Update main account
await accounts.updateOne(
{ _id: mainId },
{ $set: { balance: totalBalance } },
{ session }
)
// Delete secondary accounts
await accounts.deleteMany(
{ _id: { $in: secondaryIds } },
{ session }
)
// Log reconciliation
await reconciliationLog.insertOne({
mainId,
secondaryIds,
totalBalance,
timestamp: new Date()
}, { session })
// All succeed together, or all rollback
})
} finally {
await session.endSession()
}
}
Limitations & Considerations
Transaction Limits
- Maximum 16MB of write operations
- Cannot create/drop collections
- Cannot create/drop indexes
- Cannot alter collections
- Cannot write to system collections
Performance Impact
- Transactions have overhead
- Write operations slower (more coordination)
- Locking increases lock contention
- Not for every operation
Best Practices
✅ Transaction Best Practices:
- Keep short - Minimize lock time
- Retry transient errors - Network issues happen
- Order operations - Prevent deadlocks
- Use appropriate write concern - 'majority' for safety
- Monitor latency - Transactions add overhead
✅ When to Use:
- ✅ Money transfers
- ✅ Order processing
- ✅ Inventory management
- ✅ Account operations
- ✅ Any multi-collection atomic operations
❌ When NOT to Use:
- ❌ Single document operations (inherently atomic)
- ❌ Simple inserts/updates
- ❌ Performance-critical reads
- ❌ Batch operations (use bulk)
- ❌ If not on replica set
Next Steps
- Learn session basics - StartSession, endSession
- Write simple transaction - Insert and update
- Add error handling - Try-catch blocks
- Implement retry logic - Handle transient errors
- Monitor performance - Measure transaction time
Guarantee consistency with transactions! ✅