Deadlock in E-commerce Solution by Lock Ordering
Deadlock Problem: Ordering Products in Different Sequences
In an e-commerce application, when multiple users are placing orders for multiple products, deadlocks can occur if the transactions lock product rows in different sequences.
Scenario:
- Transaction A locks Product 1 first, then Product 2.
- Transaction B locks Product 2 first, then Product 1.
This leads to a deadlock situation:
- Transaction A holds the lock on Product 1 and waits for Product 2.
- Transaction B holds the lock on Product 2 and waits for Product 1.
- Neither transaction can proceed, and they are stuck in a cyclic wait, causing a deadlock.
Problem Code Example:
Here’s the problematic code where each transaction locks products in the order provided:
async function orderProducts(req, res) {
const { userId, productIds } = req.body; // Array of product IDs
const transaction = await sequelize.transaction();
try {
// Lock the products in the order provided by the user
for (const productId of productIds) {
await Inventory.findOne({
where: { productId },
lock: transaction.LOCK.UPDATE,
transaction,
});
}
// Process the order for each product
for (const productId of productIds) {
await Order.create({
userId,
productId,
quantity: 1,
}, { transaction });
}
// Commit the transaction
await transaction.commit();
res.json({ message: 'Order completed successfully' });
} catch (error) {
await transaction.rollback();
res.status(500).json({ error: error.message });
}
}In this code, if Transaction A processes productIds as [1, 2] and Transaction B processes productIds as [2, 1], a deadlock will occur because they are locking the same resources (products) in a different order.
Solution: Sorting Products by ID to Prevent Deadlock
To solve this issue, we ensure that all transactions always lock the products in a consistent order — by sorting the products by their IDs before locking them. This prevents deadlocks because both transactions will try to lock the products in the same sequence, avoiding cyclic waiting.
Solution Code:
// Helper function to sort product IDs
function sortProductsById(productIds) {
return productIds.sort((a, b) => a - b); // Sort in ascending order
}
async function orderProducts(req, res) {
const { userId, productIds } = req.body; // Array of product IDs
// Always lock products in ascending order of their IDs
const sortedProductIds = sortProductsById(productIds);
const transaction = await sequelize.transaction();
try {
// Lock the products based on sorted IDs
for (const productId of sortedProductIds) {
await Inventory.findOne({
where: { productId },
lock: transaction.LOCK.UPDATE,
transaction,
});
}
// Process the order for each product
for (const productId of sortedProductIds) {
await Order.create({
userId,
productId,
quantity: 1,
}, { transaction });
}
// Commit the transaction
await transaction.commit();
res.json({ message: 'Order completed successfully' });
} catch (error) {
await transaction.rollback();
res.status(500).json({ error: error.message });
}
}How the Solution Works:
- Sorting Products: The
sortProductsByIdfunction ensures that the product IDs are always sorted in ascending order before locking them. This means all transactions will lock Product 1 first, then Product 2, avoiding any inconsistent locking order. - Locking Consistency: Whether it’s Transaction A or Transaction B, they will now lock products in the same sequence, avoiding cyclic waiting between locks.
- Order Processing: After locking all products in a consistent order, the order processing happens without any risk of deadlock.
Why This Solves the Problem:
By always locking resources in a consistent order, you eliminate the possibility of transactions waiting on each other in different orders. This ensures that:
- Transaction A and Transaction B will always lock Product 1 first, then Product 2, ensuring no cyclic wait and no deadlock.
In real-world applications, this technique can be applied wherever multiple resources need to be locked within a transaction.
