This guide covers essential MongoDB transaction concepts commonly asked in technical interviews. Each question includes detailed answers and practical examples.
MongoDB transactions allow you to execute multiple operations as a single unit of work, ensuring data consistency across multiple documents and collections. Key benefits include:
Transaction implementation in MongoDB:
// Start a session
const session = client.startSession();
try {
// Start transaction
session.startTransaction();
// Perform operations
await db.users.insertOne(
{ name: "John", balance: 100 },
{ session }
);
await db.accounts.updateOne(
{ userId: "123" },
{ $inc: { balance: 100 } },
{ session }
);
// Commit transaction
await session.commitTransaction();
} catch (error) {
// Abort transaction on error
await session.abortTransaction();
throw error;
} finally {
// End session
session.endSession();
}
// Transaction with options
const session = client.startSession({
causalConsistency: true,
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" }
});
try {
session.startTransaction({
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" }
});
// Complex operations
await db.orders.insertOne({
userId: "123",
items: ["item1", "item2"],
total: 200
}, { session });
await db.inventory.updateMany(
{ itemId: { $in: ["item1", "item2"] } },
{ $inc: { quantity: -1 } },
{ session }
);
await db.users.updateOne(
{ _id: "123" },
{ $inc: { orderCount: 1 } },
{ session }
);
await session.commitTransaction();
} catch (error) {
await session.abortTransaction();
throw error;
} finally {
session.endSession();
}
Transaction error handling and retry logic:
async function executeTransaction(retries = 3) {
const session = client.startSession();
for (let i = 0; i < retries; i++) {
try {
session.startTransaction();
// Transaction operations
await db.users.updateOne(
{ _id: "123" },
{ $inc: { balance: -100 } },
{ session }
);
await db.orders.insertOne({
userId: "123",
amount: 100
}, { session });
await session.commitTransaction();
return true;
} catch (error) {
await session.abortTransaction();
if (error.hasErrorLabel("TransientTransactionError")) {
// Retry on transient errors
continue;
}
if (error.hasErrorLabel("UnknownTransactionCommitResult")) {
// Check commit result
const result = await session.commitTransaction();
if (result) return true;
continue;
}
throw error;
} finally {
session.endSession();
}
}
throw new Error("Transaction failed after retries");
}
async function withRetry(operation, maxRetries = 3) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
lastError = error;
if (error.hasErrorLabel("TransientTransactionError")) {
// Exponential backoff
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, i) * 100)
);
continue;
}
throw error;
}
}
throw lastError;
}
// Usage
await withRetry(async () => {
const session = client.startSession();
try {
session.startTransaction();
// Transaction operations
await session.commitTransaction();
} catch (error) {
await session.abortTransaction();
throw error;
} finally {
session.endSession();
}
});
Transaction performance optimization:
// Optimize transaction settings
const session = client.startSession({
causalConsistency: false, // Disable if not needed
readConcern: { level: "local" }, // Use appropriate level
writeConcern: { w: 1 } // Adjust based on requirements
});
// Batch operations
async function batchTransaction(operations) {
const session = client.startSession();
try {
session.startTransaction();
// Execute operations in parallel
await Promise.all(operations.map(op =>
db[op.collection][op.method](
op.query,
op.update,
{ session }
)
));
await session.commitTransaction();
} catch (error) {
await session.abortTransaction();
throw error;
} finally {
session.endSession();
}
}
// Monitor transaction performance
async function monitorTransactions() {
const metrics = await db.adminCommand({
serverStatus: 1
});
// Check transaction metrics
const transactionMetrics = metrics.transactions;
console.log({
activeTransactions: transactionMetrics.currentActive,
committedTransactions: transactionMetrics.committed,
abortedTransactions: transactionMetrics.aborted,
averageTransactionTime: transactionMetrics.averageTransactionTime
});
// Tune transaction settings
if (transactionMetrics.averageTransactionTime > 1000) {
// Optimize transaction settings
await db.adminCommand({
setParameter: 1,
transactionLifetimeLimitSeconds: 60
});
}
}
Follow these transaction best practices:
// Transaction wrapper
async function withTransaction(operation) {
const session = client.startSession();
try {
session.startTransaction({
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" }
});
const result = await operation(session);
await session.commitTransaction();
return result;
} catch (error) {
await session.abortTransaction();
throw error;
} finally {
session.endSession();
}
}
// Usage example
await withTransaction(async (session) => {
// Transaction operations
const user = await db.users.findOne(
{ _id: "123" },
{ session }
);
if (!user) {
throw new Error("User not found");
}
await db.orders.insertOne({
userId: user._id,
amount: 100
}, { session });
return user;
});
// Transaction manager
class TransactionManager {
constructor(client) {
this.client = client;
}
async execute(operation, options = {}) {
const session = this.client.startSession(options);
try {
session.startTransaction(options);
const result = await operation(session);
await session.commitTransaction();
return result;
} catch (error) {
await session.abortTransaction();
throw error;
} finally {
session.endSession();
}
}
async executeWithRetry(operation, maxRetries = 3) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
return await this.execute(operation);
} catch (error) {
lastError = error;
if (error.hasErrorLabel("TransientTransactionError")) {
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, i) * 100)
);
continue;
}
throw error;
}
}
throw lastError;
}
}