ACID Compliance & Data Integrity

🔒 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

🏦 Real-Time Bank Transfer Simulator
Savings Account
$1000
Account: ACC001
Checking Account
$500
Account: ACC002
Total Balance
$1500
Combined

💻 Live Console: MongoDB Transaction Code

Watch real MongoDB transaction commands execute step-by-step

MongoDB Transaction Shell
// Click a demo button above to see MongoDB transactions in action!
// Watch how transactions ensure data consistency

🔬 Understanding ACID Properties

ACID is the foundation of database transactions. Let's break it down with real examples!

⚛️
Atomicity

"All or Nothing" - Either ALL operations succeed, or NONE do

Example:
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();
}
🔒
Consistency

"Rules Are Maintained" - Database goes from one valid state to another

Example:
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!
🔀
Isolation

"No Interference" - Concurrent transactions don't see each other's partial changes

Example:
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' }
});
💾
Durability

"Permanent Once Committed" - Completed transactions survive crashes/restarts

Example:
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

Application Client 1. Start Session Session Transaction 2. Operations MongoDB Database Pending Changes ✓ Update Account A ✓ Update Account B ✓ Insert Log Entry Waiting for commit... 3. Commit Transaction ✓ SUCCESS Data Saved! t=0s t=0.5s t=1.5s t=2.5s t=3s

🛒 Real-World Scenario: E-Commerce Order Processing

Let's see how transactions ensure data integrity in a complex multi-step process

🛍️ Customer Orders $250 Worth of Products
Without Transaction (❌ DANGEROUS!):
Step 1: Deduct $250 from customer's wallet ✅
{ wallet: 1000 } → { wallet: 750 }
Step 2: Reduce inventory by 5 items ✅
{ stock: 100 } → { stock: 95 }
Step 3: Create order record... 💥 SERVER CRASH!
Operation failed! Connection lost!
😱 DISASTER: Customer lost $250, inventory decreased, but NO ORDER EXISTS!
Money gone + Products gone + No record = Complete mess!
✅ Same Order With Transaction (SAFE!)
With Transaction (✅ PROTECTED!):
Step 0: Start Transaction 🔒
session.startTransaction()
Step 1: Deduct $250 (temporary) ✅
updateOne({ _id: userId }, { $inc: { wallet: -250 } }, { session })
Step 2: Reduce inventory (temporary) ✅
updateOne({ _id: productId }, { $inc: { stock: -5 } }, { session })
Step 3: Create order... 💥 SERVER CRASH!
Error detected!
Step 4: Automatic Rollback 🔄
session.abortTransaction() // MongoDB does this automatically!
😊 PERFECT: All changes reverted! Customer wallet: $1000, Inventory: 100 items
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 Processing Simulator
Customer Wallet
$1000
Available Balance
Product Inventory
100 units
Laptop Pro X1
Orders Created
0
Total Orders

📦 Order Details:

  • Product: Laptop Pro X1
  • Quantity: 5 units
  • Price per unit: $50
  • Total: $250
E-Commerce Transaction Code
// Click "Show Full Code" to see the complete implementation

🎯 Common Transaction Patterns

Real-world patterns you'll use every day

💸
Money Transfer

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();
}
📦
Inventory Update

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 });
});
👤
User Registration

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 });
});
🔄
Cascade Updates

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

⚡
Transactions Can Fail for Many Reasons
  • 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

💥 Error Simulation Lab
Account A
$500
Initial: $500
Account B
$300
Initial: $300
Errors Caught
0
Total Rollbacks

🎯 Test Scenarios:

Each scenario will attempt to transfer $200 from A to B, but will fail at different stages:

Error Handling Console
// Run error scenarios above to see detailed logs

⚙️ Read Concerns & Write Concerns

Fine-tune transaction behavior for your specific requirements

📖
Read Concerns

Control what data you read during transactions

snapshot (Recommended)
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.
local
Read the most recent data on this replica. Faster but may read data that could be rolled back.
majority
Read data acknowledged by a majority of replica set members. Slightly slower but more durable.
session.startTransaction({
  readConcern: { 
    level: 'snapshot' 
  }
});
✍️
Write Concerns

Control write acknowledgment requirements

majority (Recommended)
Wait for write to be acknowledged by a majority of nodes. Ensures durability even if nodes fail.
1
Wait for acknowledgment from only the primary. Faster but less durable.
{ w: 3, j: true }
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

✅
DO's
  • 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'Ts
  • 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
🚀
Performance Optimization Tips
Minimize Transaction Scope: Only include operations that truly need ACID guarantees. Pre-fetch data before starting the transaction.
Use Indexes: Ensure all queries in transactions use indexes. Run explain() to verify query plans.
Batch Operations: Use bulkWrite() instead of multiple individual writes when updating many documents.
Connection Pooling: Configure appropriate pool sizes (min: 10, max: 100 for typical apps) to handle concurrent transactions.
Monitor WiredTiger Cache: Ensure MongoDB has sufficient RAM. Transactions can fail if cache pressure is too high.

🏗️ Production Architecture Patterns

How successful companies use transactions at scale

🎬 Netflix: Billing & Subscription Management
Challenge: When a user upgrades their subscription, multiple operations must succeed together:
  • Update subscription plan in subscriptions collection
  • Calculate and record prorated charges in billing collection
  • Update user's access level in users collection
  • Log audit trail in audit_logs collection
Solution: MongoDB transaction ensures all-or-nothing semantics
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 });
});
Result: If payment processing fails (step 2), the entire transaction rolls back. User's plan, access level, and billing records remain consistent!
🚗 Uber: Ride Completion & Payment
Challenge: When a ride completes, multiple updates must be atomic:
  • Mark ride as completed in rides collection
  • Deduct payment from rider's wallet
  • Credit driver's earnings
  • Update driver's statistics (total rides, earnings)
  • Generate invoice record
Solution: MongoDB transaction coordinates all updates
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

Essential Transaction Code Templates

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

❌
Error: Transaction numbers are only allowed on replica sets

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()
⏱️
Error: Transaction exceeded the configured limit

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
});
⚠️
Error: TransientTransactionError

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

📱 Application Initiates Transaction session.startTransaction() 🔐 Session Created Transaction Context txnNumber: 1234 ⚙️ Execute Ops updateOne() insertOne() deleteOne() 🤔 Decision Point All operations successful? ✓ YES ✗ NO ✅ COMMIT Changes Permanent Data saved to disk! ❌ ROLLBACK All Changes Reverted Database unchanged!

🌍 Real-World Use Cases: When Do You NEED Transactions?

🛒 E-Commerce: Order Processing

Problem: A customer buys 3 items. You need to:

Step 1: Deduct inventory for 3 products
Step 2: Create order document
Step 3: Charge customer's payment method
Step 4: Update customer's order history
Without Transaction: If Step 3 (payment) fails, inventory is already deducted and order created! Customer gets free items or inventory is permanently wrong!
With Transaction: If payment fails, ALL steps rollback automatically. Inventory restored, no order created. Database stays consistent!
// 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();
}
🏦 Banking: Money Transfer

Problem: Transfer $500 from Alice to Bob

Step 1: Verify Alice has sufficient balance ($500+)
Step 2: Deduct $500 from Alice's account
Step 3: Add $500 to Bob's account
Step 4: Log transaction in audit trail
Nightmare Without Transaction: Alice's account is debited $500, server crashes before crediting Bob. Money disappears into thin air! 💸😱
Safe With Transaction: Either both accounts update, or neither does. Money can't disappear!
// 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();
  }
}
🎫 Booking System: Concert Tickets

Problem: User books 2 tickets for a concert

Step 1: Check if 2 seats are available
Step 2: Mark seats as "reserved"
Step 3: Create booking record
Step 4: Send confirmation email
Race Condition Without Transaction:
• 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! 🎭😱
Protected With Transaction: Transactions use locks. First user gets the seats, second user sees "sold out". No double bookings!
// 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

Transaction Lifecycle State Machine
NOT STARTED

No transaction is currently active. Operations execute normally without transactional guarantees.

NOT STARTED IN PROGRESS COMMITTED ABORTED start() commit() abort() new txn

🔥 Advanced Live Demo: Multi-Collection Transaction

Watch a real transaction update multiple collections atomically

Multi-Collection Transaction Demo
// Scenario: Process an order that affects 4 collections
// Collections: products, orders, customers, inventory_log
// Click "Run Complete Demo" to see it in action!

⚖️ Side-by-Side: With vs Without Transactions

❌
WITHOUT Transaction
// ⚠️ 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!
✅
WITH Transaction
// ✅ 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

Common Pitfall: Transactions can fail for many reasons! Always handle errors properly.

🎯 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

Snapshot Isolation Visualizer

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:

T = 0ms: Document value: count = 10
T = 100ms: Transaction A starts
A sees: count = 10 (snapshot created)
T = 200ms: Transaction B starts
B sees: count = 10 (snapshot created)
T = 300ms: Transaction A increments count
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)
T = 400ms: Transaction A commits!
A's view: count = 11 ✅
B's view: count = 10 (still sees original snapshot)
Actual DB: count = 11 ✅ (A committed)
T = 500ms: Transaction B increments count
B's view: count = 11 (reads from snapshot: 10 + 1)
Actual DB: count = 11
T = 600ms: Transaction B tries to commit...
❌ WRITE CONFLICT! B's snapshot is stale!
MongoDB aborts transaction B
B must retry with fresh snapshot
Key Insight: Snapshot isolation prevents "dirty reads" (reading uncommitted data) and "non-repeatable reads" (same read returning different values), but can cause write conflicts that require retries.

⚡ Performance & Optimization Tips

✅
DO: Good Practices
  • 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();
}
❌
DON'T: Bad Practices
  • ✗ 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

💡 Transaction Overhead: Choose Wisely!

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

Q1 How is this tutorial different from MongoDB official documentation? ▼

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!

Q2 Should I complete all 14 sections in order? ▼

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!

Q3 How long does it take to complete the entire tutorial? ▼

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)

Q4 Can I use this tutorial to prepare for MongoDB certification? ▼

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.

Q5 Are the projects production-ready or just tutorials? ▼

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!