🔒 MongoDB Transactions
Master multi-document transactions with live examples, interactive demos, and real-time visualizations
💡 What Are Transactions? (In 30 Seconds)
Imagine you're transferring $500 from your savings to your checking account at an ATM...
❌ BAD (Without Transaction):
Step 1: Subtract $500 from savings ✅
Step 2: Power outage! ⚡💥
Step 3: Add $500 to checking ❌ (never happened!)
Result: You lost $500! Money disappeared! 😱
✅ GOOD (With Transaction):
Step 1: Start transaction 🔒
Step 2: Subtract $500 from savings (temporary)
Step 3: Power outage! ⚡💥
Step 4: Transaction automatically rolls back 🔄
Result: Your money is safe! Nothing changed! 😊
That's what transactions do! They make sure that EITHER all changes happen successfully, OR nothing happens at all. No partial updates. No lost data. No corruption.
💰 Live Demo: Bank Transfer (Watch It Work!)
Click the buttons below to see transactions in action
💻 Live Console: MongoDB Transaction Code
Watch real MongoDB transaction commands execute step-by-step
// Watch how transactions ensure data consistency
🔬 Understanding ACID Properties
ACID is the foundation of database transactions. Let's break it down with real examples!
"All or Nothing" - Either ALL operations succeed, or NONE do
Transfer $100: Debit Account A + Credit Account B
❌ If debit fails → Credit is NOT applied
❌ If credit fails → Debit is rolled back
✅ Both succeed → Transfer complete!
// Transaction guarantees atomicity
session.startTransaction();
try {
// All operations as a unit
await accounts.updateOne(
{ _id: "A" },
{ $inc: { balance: -100 } }
);
await accounts.updateOne(
{ _id: "B" },
{ $inc: { balance: 100 } }
);
await session.commitTransaction();
} catch (error) {
// Automatically rolls back both!
await session.abortTransaction();
}
"Rules Are Maintained" - Database goes from one valid state to another
Rule: Total money in system = $1000
Before: A=$600, B=$400 (Total=$1000) ✅
Transfer: A-$100, B+$100
After: A=$500, B=$500 (Total=$1000) ✅
Rule maintained throughout!
// Consistency checks
const total = await accounts.aggregate([
{ $group: {
_id: null,
total: { $sum: "$balance" }
}}
]);
// total[0].total === 1000 always!
"No Interference" - Concurrent transactions don't see each other's partial changes
Transaction 1: Transfer $50 from A→B
Transaction 2: Read balance of A
Transaction 2 sees EITHER old or new value,
but NEVER a partial/inconsistent state!
// Isolation levels ensure this
// snapshot isolation (default)
session.startTransaction({
readConcern: { level: 'snapshot' },
writeConcern: { w: 'majority' }
});
"Permanent Once Committed" - Completed transactions survive crashes/restarts
Transaction commits at 3:00 PM
Server crashes at 3:01 PM ⚡💥
Server restarts at 3:05 PM
✅ Transaction changes are still there!
Data is safe on disk!
// Durability with write concern
await session.commitTransaction();
// Once this returns, data is
// permanently stored (even if
// server crashes immediately after)
🎬 How Transactions Work (Animated Flow)
Watch the complete transaction lifecycle with visual animations
🛒 Real-World Scenario: E-Commerce Order Processing
Let's see how transactions ensure data integrity in a complex multi-step process
{ wallet: 1000 } → { wallet: 750 }
{ stock: 100 } → { stock: 95 }
Operation failed! Connection lost!
Money gone + Products gone + No record = Complete mess!
session.startTransaction()
updateOne({ _id: userId }, { $inc: { wallet: -250 } }, { session })
updateOne({ _id: productId }, { $inc: { stock: -5 } }, { session })
Error detected!
session.abortTransaction() // MongoDB does this automatically!
It's like the transaction never happened! Data is consistent!
🎮 Interactive Demo: E-Commerce Transaction
Simulate a real e-commerce order with or without transaction protection
📦 Order Details:
- Product: Laptop Pro X1
- Quantity: 5 units
- Price per unit: $50
- Total: $250
🎯 Common Transaction Patterns
Real-world patterns you'll use every day
Transfer funds between accounts with guaranteed consistency
const session = client.startSession();
try {
await session.withTransaction(async () => {
// Debit source
await accounts.updateOne(
{ _id: sourceId },
{ $inc: { balance: -amount } },
{ session }
);
// Credit destination
await accounts.updateOne(
{ _id: destId },
{ $inc: { balance: amount } },
{ session }
);
// Log transaction
await logs.insertOne({
from: sourceId,
to: destId,
amount,
timestamp: new Date()
}, { session });
});
} finally {
await session.endSession();
}
Reserve inventory and create order atomically
await session.withTransaction(async () => {
// Check stock
const product = await products.findOne(
{ _id: productId },
{ session }
);
if (product.stock < quantity) {
throw new Error('Out of stock!');
}
// Reduce inventory
await products.updateOne(
{ _id: productId },
{ $inc: { stock: -quantity } },
{ session }
);
// Create order
await orders.insertOne({
productId,
quantity,
status: 'pending',
createdAt: new Date()
}, { session });
});
Create user, profile, and initial settings together
await session.withTransaction(async () => {
// Create user
const result = await users.insertOne({
email,
password: hashedPassword,
createdAt: new Date()
}, { session });
const userId = result.insertedId;
// Create profile
await profiles.insertOne({
userId,
firstName,
lastName,
avatar: 'default.png'
}, { session });
// Create settings
await settings.insertOne({
userId,
theme: 'light',
notifications: true
}, { session });
});
Update related documents consistently
await session.withTransaction(async () => {
// Update main document
await posts.updateOne(
{ _id: postId },
{
$set: {
status: 'published',
publishedAt: new Date()
}
},
{ session }
);
// Update author stats
await users.updateOne(
{ _id: authorId },
{ $inc: { publishedPosts: 1 } },
{ session }
);
// Notify subscribers
await notifications.insertMany(
subscribers.map(sub => ({
userId: sub,
type: 'new_post',
postId,
createdAt: new Date()
})),
{ session }
);
});
⚠️ Error Handling & Rollback
Understanding how MongoDB handles transaction failures
- Network issues: Connection lost during operation
- Write conflicts: Two transactions modifying same document
- Validation errors: Data doesn't meet schema requirements
- Timeout: Transaction took too long (default 60s)
- Application errors: Your code throws an exception
MongoDB automatically rolls back ALL changes when ANY error occurs!
🛡️ Robust Error Handling Patterns
// Pattern 1: Try-Catch with Automatic Rollback
const session = client.startSession();
try {
await session.withTransaction(async () => {
// Your operations here
await collection1.updateOne({ ... }, { ... }, { session });
await collection2.insertOne({ ... }, { session });
// If ANY operation fails, MongoDB rolls back automatically
}, {
readConcern: { level: 'snapshot' },
writeConcern: { w: 'majority' },
readPreference: 'primary'
});
console.log('✅ Transaction committed successfully!');
} catch (error) {
console.error('❌ Transaction failed:', error.message);
// No need to manually abort - already rolled back!
} finally {
await session.endSession();
}
// Pattern 2: Manual Transaction Control with Retry Logic
async function transferMoneyWithRetry(fromId, toId, amount, maxRetries = 3) {
const session = client.startSession();
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
await session.startTransaction();
// Debit account
const debitResult = await accounts.updateOne(
{ _id: fromId, balance: { $gte: amount } },
{ $inc: { balance: -amount } },
{ session }
);
if (debitResult.matchedCount === 0) {
throw new Error('Insufficient funds or account not found');
}
// Credit account
await accounts.updateOne(
{ _id: toId },
{ $inc: { balance: amount } },
{ session }
);
// Commit
await session.commitTransaction();
console.log(`✅ Transfer successful on attempt ${attempt}`);
return { success: true };
} catch (error) {
await session.abortTransaction();
console.log(`⚠️ Attempt ${attempt} failed: ${error.message}`);
// Retry on transient errors
if (error.hasErrorLabel('TransientTransactionError') && attempt < maxRetries) {
console.log(`🔄 Retrying... (${attempt}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); // Exponential backoff
continue;
}
// Don't retry on non-transient errors
return { success: false, error: error.message };
} finally {
if (attempt === maxRetries) {
await session.endSession();
}
}
}
}
🔴 Live Demo: Transaction Failures & Rollback
Watch what happens when transactions encounter errors
🎯 Test Scenarios:
Each scenario will attempt to transfer $200 from A to B, but will fail at different stages:
⚙️ Read Concerns & Write Concerns
Fine-tune transaction behavior for your specific requirements
Control what data you read during transactions
Read from a consistent snapshot of data at the start of the transaction. Ensures you see all writes that were majority-committed before the transaction started.
Read the most recent data on this replica. Faster but may read data that could be rolled back.
Read data acknowledged by a majority of replica set members. Slightly slower but more durable.
session.startTransaction({
readConcern: {
level: 'snapshot'
}
});
Control write acknowledgment requirements
Wait for write to be acknowledged by a majority of nodes. Ensures durability even if nodes fail.
Wait for acknowledgment from only the primary. Faster but less durable.
Wait for 3 nodes + journal write. Maximum durability for critical data.
session.startTransaction({
writeConcern: {
w: 'majority',
j: true,
wtimeout: 5000
}
});
✨ Transaction Best Practices
Production-ready guidelines for building reliable applications
- Keep transactions short: Under 60 seconds (MongoDB's default timeout)
- Use withTransaction(): It handles retries and rollback automatically
- Set appropriate concerns: Use snapshot + majority for financial operations
- Handle TransientTransactionError: Implement retry logic with exponential backoff
- Index wisely: Ensure queries in transactions use indexes to avoid slowdowns
- Always end sessions: Use try-finally to prevent session leaks
- Test failure scenarios: Simulate crashes, timeouts, and validation errors
- Monitor transaction metrics: Track abort rates and durations
- Don't create large transactions: Avoid updating thousands of documents in one transaction
- Don't forget session parameter: All operations must include { session }
- Don't use transactions unnecessarily: Single document updates are already atomic
- Don't ignore error labels: Check for TransientTransactionError to know if retry is safe
- Don't perform heavy operations: Avoid complex aggregations or large data processing
- Don't mix transaction types: Don't combine regular ops with transaction ops
- Don't use outdated drivers: Use MongoDB driver 4.0+ for full transaction support
- Don't skip testing: Test rollback scenarios thoroughly before production
explain() to verify query plans.
bulkWrite() instead of multiple individual writes
when updating many documents.
🏗️ Production Architecture Patterns
How successful companies use transactions at scale
- Update subscription plan in
subscriptionscollection - Calculate and record prorated charges in
billingcollection - Update user's access level in
userscollection - Log audit trail in
audit_logscollection
await session.withTransaction(async () => {
// 1. Update subscription
await subscriptions.updateOne(
{ userId },
{
$set: {
plan: 'premium',
upgradeDate: new Date(),
nextBillingDate: calculateNextBilling()
}
},
{ session }
);
// 2. Calculate prorated charge
const proratedAmount = calculateProration(oldPlan, newPlan);
await billing.insertOne({
userId,
type: 'upgrade',
amount: proratedAmount,
status: 'pending',
createdAt: new Date()
}, { session });
// 3. Update user access
await users.updateOne(
{ _id: userId },
{ $set: { accessLevel: 'premium', features: premiumFeatures } },
{ session }
);
// 4. Audit log
await auditLogs.insertOne({
userId,
action: 'subscription_upgrade',
from: oldPlan,
to: 'premium',
timestamp: new Date()
}, { session });
});
- Mark ride as completed in
ridescollection - Deduct payment from rider's wallet
- Credit driver's earnings
- Update driver's statistics (total rides, earnings)
- Generate invoice record
await session.withTransaction(async () => {
const ride = await rides.findOne({ _id: rideId }, { session });
const fare = calculateFare(ride.distance, ride.duration);
// 1. Complete ride
await rides.updateOne(
{ _id: rideId },
{
$set: {
status: 'completed',
fare,
completedAt: new Date()
}
},
{ session }
);
// 2. Charge rider
await wallets.updateOne(
{ userId: ride.riderId, balance: { $gte: fare } },
{ $inc: { balance: -fare } },
{ session }
);
// 3. Pay driver
const driverEarning = fare * 0.8; // 80% to driver
await wallets.updateOne(
{ userId: ride.driverId },
{ $inc: { balance: driverEarning } },
{ session }
);
// 4. Update driver stats
await drivers.updateOne(
{ _id: ride.driverId },
{
$inc: {
totalRides: 1,
totalEarnings: driverEarning
}
},
{ session }
);
// 5. Generate invoice
await invoices.insertOne({
rideId,
riderId: ride.riderId,
driverId: ride.driverId,
amount: fare,
driverEarning,
platformFee: fare * 0.2,
createdAt: new Date()
}, { session });
});
💼 Interview Questions & Answers
Common questions about MongoDB transactions with detailed answers
📋 Quick Reference Cheat Sheet
Basic Transaction (Recommended)
const session = client.startSession();
try {
await session.withTransaction(async () => {
// Your operations here
await collection.updateOne({ ... }, { ... }, { session });
await collection.insertOne({ ... }, { session });
});
console.log('✅ Transaction committed');
} catch (error) {
console.error('❌ Transaction failed:', error);
} finally {
await session.endSession();
}
Manual Control with Retry
const session = client.startSession();
try {
await session.startTransaction({
readConcern: { level: 'snapshot' },
writeConcern: { w: 'majority' }
});
// Operations
await collection.updateOne({ ... }, { ... }, { session });
// Commit
await session.commitTransaction();
} catch (error) {
await session.abortTransaction();
throw error;
} finally {
await session.endSession();
}
Transaction Options
const transactionOptions = {
readConcern: { level: 'snapshot' },
writeConcern: {
w: 'majority',
j: true,
wtimeout: 5000
},
readPreference: 'primary',
maxCommitTimeMS: 30000
};
await session.withTransaction(
async () => { /* operations */ },
transactionOptions
);
Common Patterns
// Check before update
const doc = await collection.findOne({ _id: id }, { session });
if (!doc) throw new Error('Not found');
// Conditional update
const result = await collection.updateOne(
{ _id: id, balance: { $gte: amount } },
{ $inc: { balance: -amount } },
{ session }
);
if (result.matchedCount === 0) {
throw new Error('Insufficient balance');
}
// Bulk operations
await collection.bulkWrite([
{ updateOne: { filter: {...}, update: {...} } },
{ insertOne: { document: {...} } }
], { session });
🔧 Common Issues & Solutions
Cause: You're running MongoDB in standalone mode, not replica set mode.
Solution: Convert to replica set or start MongoDB with replica set mode:
// Start MongoDB as single-node replica set
mongod --replSet rs0 --port 27017
// Then in mongo shell
rs.initiate()
Cause: Transaction took longer than 60 seconds (default timeout).
Solution: Make transaction shorter or increase timeout:
// Increase timeout to 2 minutes
await session.startTransaction({
maxCommitTimeMS: 120000
});
Cause: Temporary issue (network glitch, write conflict, primary election).
Solution: Safe to retry the entire transaction:
if (error.hasErrorLabel('TransientTransactionError')) {
console.log('Retrying transaction...');
// Retry logic here
}
🎬 Animated Flow: How Transactions Work
Watch the complete lifecycle of a MongoDB transaction
🌍 Real-World Use Cases: When Do You NEED Transactions?
Problem: A customer buys 3 items. You need to:
// E-commerce order with transaction
const session = client.startSession();
session.startTransaction();
try {
// 1. Reduce inventory
await products.updateMany(
{ _id: { $in: orderItems } },
{ $inc: { stock: -1 } },
{ session }
);
// 2. Create order
const order = await orders.insertOne({
customerId: userId,
items: orderItems,
total: orderTotal,
status: 'pending'
}, { session });
// 3. Charge payment (external API call)
const paymentResult = await stripeAPI.charge({
amount: orderTotal,
customerId: userId
});
if (!paymentResult.success) {
throw new Error('Payment failed');
}
// 4. Update customer history
await customers.updateOne(
{ _id: userId },
{ $push: { orders: order.insertedId } },
{ session }
);
// ALL succeeded! Commit everything
await session.commitTransaction();
} catch (error) {
// ANY step failed! Rollback EVERYTHING
await session.abortTransaction();
console.error('Order failed:', error);
} finally {
session.endSession();
}
Problem: Transfer $500 from Alice to Bob
// Banking transfer with transaction
async function transferMoney(fromAccount, toAccount, amount) {
const session = client.startSession();
session.startTransaction({
readConcern: { level: 'snapshot' },
writeConcern: { w: 'majority' },
readPreference: 'primary'
});
try {
// 1. Check sufficient balance
const sender = await accounts.findOne(
{ _id: fromAccount },
{ session }
);
if (sender.balance < amount) {
throw new Error('Insufficient funds');
}
// 2. Debit sender
await accounts.updateOne(
{ _id: fromAccount },
{ $inc: { balance: -amount } },
{ session }
);
// 3. Credit receiver
await accounts.updateOne(
{ _id: toAccount },
{ $inc: { balance: amount } },
{ session }
);
// 4. Audit log
await transactions.insertOne({
from: fromAccount,
to: toAccount,
amount: amount,
timestamp: new Date(),
status: 'completed'
}, { session });
// Everything successful!
await session.commitTransaction();
return { success: true, message: 'Transfer completed' };
} catch (error) {
// Anything failed - rollback all changes
await session.abortTransaction();
return { success: false, error: error.message };
} finally {
session.endSession();
}
}
Problem: User books 2 tickets for a concert
• User A and User B both see 2 seats available
• Both click "Book" at the same time
• Database processes both requests
• Result: Double booking! Same seats sold twice! 🎭😱
// Ticket booking with transaction
async function bookTickets(eventId, userId, seatIds) {
const session = client.startSession();
session.startTransaction();
try {
// 1. Check seats are available (with lock)
const seats = await seats.find(
{
_id: { $in: seatIds },
status: 'available',
eventId: eventId
},
{ session }
).toArray();
if (seats.length !== seatIds.length) {
throw new Error('Some seats are no longer available');
}
// 2. Reserve seats (this locks them)
const reserveResult = await seats.updateMany(
{ _id: { $in: seatIds } },
{
$set: {
status: 'reserved',
reservedBy: userId,
reservedAt: new Date()
}
},
{ session }
);
// 3. Create booking
const booking = await bookings.insertOne({
userId: userId,
eventId: eventId,
seats: seatIds,
totalPrice: seats.length * seats[0].price,
status: 'confirmed',
bookedAt: new Date()
}, { session });
// 4. Update user's bookings
await users.updateOne(
{ _id: userId },
{ $push: { bookings: booking.insertedId } },
{ session }
);
// All done! Commit
await session.commitTransaction();
// Send email AFTER commit (not part of transaction)
await sendConfirmationEmail(userId, booking.insertedId);
return { success: true, bookingId: booking.insertedId };
} catch (error) {
await session.abortTransaction();
return { success: false, error: error.message };
} finally {
session.endSession();
}
}
🔄 Interactive: Transaction State Machine
Click through the different states a transaction can be in
No transaction is currently active. Operations execute normally without transactional guarantees.
🔥 Advanced Live Demo: Multi-Collection Transaction
Watch a real transaction update multiple collections atomically
// Collections: products, orders, customers, inventory_log
// Click "Run Complete Demo" to see it in action!
⚖️ Side-by-Side: With vs Without Transactions
// ⚠️ DANGEROUS: No transaction
async function processOrder(order) {
// Step 1: Reduce inventory
await products.updateOne(
{ _id: order.productId },
{ $inc: { stock: -order.qty } }
);
// ✅ Inventory reduced
// Step 2: Create order
await orders.insertOne({
productId: order.productId,
quantity: order.qty,
status: 'pending'
});
// ✅ Order created
// Step 3: Charge payment
const payment = await chargeCard(
order.cardNumber,
order.total
);
// ❌ CRASHES HERE! Payment failed!
// Step 4: Never executes
await orders.updateOne(
{ _id: order._id },
{ $set: { status: 'paid' } }
);
// ❌ Never happens!
}
// RESULT: 😱 DISASTER!
// ✅ Inventory is reduced
// ✅ Order is created
// ❌ Payment failed
// ❌ Order never marked as paid
//
// You lost inventory!
// Customer got free product!
// Database is inconsistent!
// ✅ SAFE: With transaction
async function processOrder(order) {
const session = client.startSession();
session.startTransaction();
try {
// Step 1: Reduce inventory
await products.updateOne(
{ _id: order.productId },
{ $inc: { stock: -order.qty } },
{ session } // ← Part of transaction
);
// Step 2: Create order
await orders.insertOne({
productId: order.productId,
quantity: order.qty,
status: 'pending'
}, { session }); // ← Part of transaction
// Step 3: Charge payment
const payment = await chargeCard(
order.cardNumber,
order.total
);
// ❌ CRASHES HERE! Payment failed!
// This throws error, jumps to catch
// Step 4: Would execute if success
await orders.updateOne(
{ _id: order._id },
{ $set: { status: 'paid' } },
{ session }
);
await session.commitTransaction();
} catch (error) {
// 🔄 AUTOMATIC ROLLBACK!
await session.abortTransaction();
} finally {
session.endSession();
}
}
// RESULT: ✅ PERFECT!
// 🔄 Inventory reduction ROLLED BACK
// 🔄 Order creation ROLLED BACK
// ❌ Payment failed (caught)
// ✅ Database unchanged
//
// Nothing lost!
// Everything consistent!
// Safe to retry!
🛡️ Error Handling & Best Practices
🎯 Complete Transaction Template (Production-Ready)
async function performTransaction() {
const session = client.startSession();
// Configure transaction options
const transactionOptions = {
readPreference: 'primary',
readConcern: { level: 'local' },
writeConcern: { w: 'majority' }
};
try {
session.startTransaction(transactionOptions);
// =====================================
// YOUR OPERATIONS GO HERE
// =====================================
// Example: Multiple operations
await collection1.updateOne(
{ _id: 'doc1' },
{ $set: { field: 'value' } },
{ session }
);
await collection2.insertOne(
{ data: 'new document' },
{ session }
);
await collection3.deleteMany(
{ status: 'archived' },
{ session }
);
// =====================================
// Commit if everything succeeded
await session.commitTransaction();
console.log('✅ Transaction committed successfully');
return { success: true };
} catch (error) {
// Abort transaction on any error
await session.abortTransaction();
console.error('❌ Transaction aborted:', error);
// Check if it's a transient error (can retry)
if (error.hasErrorLabel('TransientTransactionError')) {
console.log('🔄 Transient error - can retry');
// Implement retry logic here
}
// Check if commit can be retried
if (error.hasErrorLabel('UnknownTransactionCommitResult')) {
console.log('🔄 Unknown commit result - can retry commit');
// Retry commit
}
return { success: false, error: error.message };
} finally {
// Always end session
await session.endSession();
}
}
// WITH RETRY LOGIC (Production-grade)
async function performTransactionWithRetry(maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`Attempt ${attempt} of ${maxRetries}`);
const result = await performTransaction();
if (result.success) {
return result;
}
} catch (error) {
// Check if we should retry
if (error.hasErrorLabel('TransientTransactionError')) {
if (attempt < maxRetries) {
console.log(`Retrying... (${attempt}/${maxRetries})`);
// Exponential backoff
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 100)
);
continue;
}
}
// Non-transient error or max retries reached
throw error;
}
}
}
🔬 Understanding Isolation Levels
MongoDB uses snapshot isolation - let's see what that means
What is Snapshot Isolation?
Each transaction sees a consistent snapshot of the database at the time the transaction started. Changes made by other transactions are not visible until your transaction commits.
Example Timeline:
count = 10
A sees:
count = 10 (snapshot created)
B sees:
count = 10 (snapshot created)
A's view:
count = 11 (not committed yet)B's view:
count = 10 (sees snapshot, not A's changes)Actual DB:
count = 10 (A not committed)
A's view:
count = 11 ✅B's view:
count = 10 (still sees original snapshot)Actual DB:
count = 11 ✅ (A committed)
B's view:
count = 11 (reads from snapshot: 10 + 1)Actual DB:
count = 11
❌ WRITE CONFLICT! B's snapshot is stale!
MongoDB aborts transaction B
B must retry with fresh snapshot
⚡ Performance & Optimization Tips
- Keep transactions short - Aim for < 1 second duration
- Limit operations - Fewer than 1,000 documents per transaction
- Use indexes - Speed up reads and writes in transactions
- Set timeouts - Prevent hung transactions
- Implement retries - Handle transient errors gracefully
- Use write concern majority - Ensure durability
- Batch related operations - Group logical units together
// ✅ Good: Short, focused transaction
const session = client.startSession();
const opts = {
maxCommitTimeMS: 5000, // 5 second timeout
writeConcern: { w: 'majority' }
};
session.startTransaction(opts);
try {
// Only essential operations
await orders.insertOne(order, { session });
await inventory.updateOne(
{ _id: productId },
{ $inc: { stock: -qty } },
{ session }
);
await session.commitTransaction();
} catch (e) {
await session.abortTransaction();
throw e;
} finally {
session.endSession();
}
- ✗ Long-running transactions (> 60 seconds)
- ✗ Modifying 10,000+ documents in one transaction
- ✗ Making external API calls inside transactions
- ✗ Heavy computation inside transactions
- ✗ Reading large datasets during transaction
- ✗ No retry logic for transient failures
- ✗ Ignoring write conflicts
// ❌ Bad: Transaction doing too much
const session = client.startSession();
session.startTransaction();
try {
// ❌ External API call - slow & unreliable
await fetch('https://api.payment.com/charge');
// ❌ Reading 100,000 documents
const docs = await huge.find({}).toArray();
// ❌ Heavy processing
const processed = docs.map(complexCalculation);
// ❌ Modifying 50,000 documents
await bulk.updateMany(
{},
{ $set: { processed: true } }
);
// Transaction will likely timeout!
await session.commitTransaction();
} catch (e) {
await session.abortTransaction();
} finally {
session.endSession();
}
🤔 When NOT to Use Transactions
Transactions add overhead. Don't use them when simpler solutions work:
❌ Don't Need Transaction
// Single document update - already atomic!
await users.updateOne(
{ _id: userId },
{ $inc: { loginCount: 1 } }
);
// MongoDB guarantees atomicity for
// single document operations.
// No transaction needed!
// Array operations - already atomic!
await posts.updateOne(
{ _id: postId },
{ $push: { comments: newComment } }
);
// This is atomic by default
// Embedded document update - atomic!
await orders.updateOne(
{ _id: orderId },
{
$set: {
'shipping.status': 'shipped',
'shipping.trackingNumber': '12345'
}
}
);
✅ Need Transaction
// Multiple documents in one collection
const session = client.startSession();
session.startTransaction();
await users.updateOne(
{ _id: senderId },
{ $inc: { balance: -100 } },
{ session }
);
await users.updateOne(
{ _id: receiverId },
{ $inc: { balance: 100 } },
{ session }
);
await session.commitTransaction();
// Multiple collections
session.startTransaction();
await orders.insertOne(order, { session });
await inventory.updateMany(
{ _id: { $in: productIds } },
{ $inc: { stock: -1 } },
{ session }
);
await customers.updateOne(
{ _id: customerId },
{ $push: { orders: orderId } },
{ session }
);
await session.commitTransaction();
🐛 Common Errors & How to Fix Them
Error: TransactionTooLargeForCache
Cause: Transaction is trying to modify too many documents (> 16MB of changes)
Solution:
// ❌ Bad: Trying to update 100,000 documents
await collection.updateMany(
{},
{ $set: { processed: true } },
{ session }
);
// ✅ Good: Break into smaller batches
const batchSize = 1000;
const cursor = collection.find({});
let batch = [];
for await (const doc of cursor) {
batch.push(doc._id);
if (batch.length >= batchSize) {
// Process batch in transaction
const session = client.startSession();
session.startTransaction();
await collection.updateMany(
{ _id: { $in: batch } },
{ $set: { processed: true } },
{ session }
);
await session.commitTransaction();
session.endSession();
batch = []; // Reset batch
}
}
Error: WriteConflict
Cause: Two transactions trying to modify the same document simultaneously
Solution: Implement retry logic
// ✅ Proper retry handling
async function transferWithRetry(from, to, amount, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const session = client.startSession();
try {
session.startTransaction();
await accounts.updateOne(
{ _id: from },
{ $inc: { balance: -amount } },
{ session }
);
await accounts.updateOne(
{ _id: to },
{ $inc: { balance: amount } },
{ session }
);
await session.commitTransaction();
return { success: true }; // Success!
} catch (error) {
await session.abortTransaction();
if (error.code === 112 && attempt < maxRetries - 1) {
// WriteConflict - retry with exponential backoff
await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 100));
continue;
}
throw error; // Non-retryable error
} finally {
session.endSession();
}
}
throw new Error('Max retries exceeded');
}
Error: TransactionTimeLimitExceeded
Cause: Transaction took longer than 60 seconds (default limit)
Solution: Make transaction faster or increase timeout
// Option 1: Make transaction faster
// - Add indexes to speed up queries
// - Reduce number of operations
// - Remove non-essential work
// Option 2: Increase timeout (if justified)
session.startTransaction({
maxCommitTimeMS: 120000 // 2 minutes
});
// Option 3: Break into smaller transactions
// Instead of one huge transaction:
const docs = await findAllDocs(); // Outside transaction
const session = client.startSession();
session.startTransaction();
// Only the critical updates inside
await processOnlyCriticalUpdates(docs, session);
await session.commitTransaction();
🎯 Interview Question and answer
Answer:
Great question! Here's the key difference:
MongoDB Official Docs: Reference manual - assumes you already know what you're looking for. Great for experienced developers, overwhelming for beginners.
myPathshala Tutorial: Learning-focused - starts with stories, builds concepts progressively, includes practice problems, interview prep, and real projects. Designed specifically for Indian CS students preparing for placements.
Think of it this way: Official docs are like a dictionary. Our tutorial is like a complete course with a teacher who explains everything step by step!
Answer:
For absolute beginners: Yes! Follow the order: Section 1 → 2 → 3 → 4 → 5 → 6 → 7. Each section builds on previous knowledge.
If you already know MongoDB basics: Skip to Section 6 (Intermediate) or Section 7 (Advanced). Use Section 13 (Interview Prep) anytime.
If you want hands-on practice: Jump to Section 11 (Projects) after completing basics (Sections 1-5).
Pro tip: Cheatsheets (Section 14) are useful at ANY point - bookmark them for quick reference!
Answer:
Estimated Time Breakdown:
- Sections 1-5 (Basics): 15-20 hours
- Sections 6-7 (Intermediate & Advanced): 20-25 hours
- Section 10 (Programming): 10-12 hours
- Section 11 (Projects): 25-30 hours
- Section 12-13 (Practice & Interview): 15-20 hours
Total: 85-107 hours (roughly 2-3 months if studying 1-2 hours daily)
Fast track (just basics + interview prep): 30-40 hours (2-3 weeks intensive)
Answer:
Absolutely! This tutorial covers all topics needed for MongoDB Associate Developer certification:
- ✅ CRUD operations and query operators
- ✅ Aggregation framework
- ✅ Indexing strategies
- ✅ Data modeling and schema design
- ✅ Replication and sharding basics
- ✅ Transactions and ACID guarantees
Additional prep needed: Practice on MongoDB University (free courses) and take official practice exams. Our tutorial gives you the foundation; certification requires hands-on MongoDB Atlas experience.
Answer:
The projects (Section 11) are designed as learning projects but with production-ready patterns:
What they include:
- ✅ Real schema designs used in production
- ✅ Proper indexing strategies
- ✅ Error handling patterns
- ✅ Data validation
- ✅ Query optimization techniques
What you need to add for production:
- Authentication & authorization
- Rate limiting
- Comprehensive logging
- Monitoring & alerting
- Backup strategies
Bottom line: Perfect for your portfolio and interviews. Add security/ops features to make them truly production-ready!