Multi-Key Atomic Transactions in Redis
In this lesson, you will learn how to ensure multiple interdependent cache updates are applied as a single, indivisible unit using Redis Transactions.
When working with complex cached data—like a user's profile and their associated permissions—you often need to update several keys simultaneously. If a network flicker or process crash occurs halfway through these updates, your cache enters an inconsistent state, where the profile reflects the old data but the permissions reflect the new, potentially leading to security gaps or application errors.
Redis handles this via the MULTI, EXEC, and DISCARD command sequence. In Node.js, when using a client like ioredis, we wrap these operations in a "transaction block." When you call multi(), Redis buffers your commands instead of executing them immediately. Once you call exec(), Redis pushes all those commands to the server to be executed sequentially, uninterrupted by other client requests.
The Transaction Workflow
Here is how you handle an interdependent update for a user balance and their transaction ledger:
const pipeline = redis.multi();
// Queue up multiple dependent operations
pipeline.decrby('user:101:balance', 50);
pipeline.lpush('user:101:ledger', 'TXN_ID_552');
pipeline.set('user:101:last_updated', Date.now());
// Atomic execution
const results = await pipeline.exec();
if (results === null) {
// The transaction failed (e.g., due to a watched key change)
console.error("Transaction aborted.");
}
By wrapping these in a transaction, you guarantee that either all keys are updated or none are. This is vital when the integrity of your cache relies on the relationship between multiple keys; you never want a user to have their balance deducted without the corresponding entry appearing in their transaction history.
It is important to remember that Redis transactions are not "roll-backable" in the traditional SQL sense if a command fails due to a syntax error or wrong data type (as these are programming errors), but they do provide atomicity against concurrency issues, ensuring that no other client can interject commands while your batch is processing.
Key Takeaway: Use multi() and exec() to group interdependent cache updates, ensuring your cache maintains internal consistency even in the face of concurrent operations or system interruptions.