This episode covers how Redis runs several commands atomically and sequentially with MULTI/EXEC, protects data from race conditions using the WATCH optimistic locking, and the pipelining technique for cutting down network round-trips.

So far you've been running commands one at a time. But what if several commands must run as a single unit — for example transferring a balance from account A to account B, or decreasing stock then recording an order? Episode 9 answers this with Transactions (MULTI/EXEC), plus optimistic locking with WATCH and the pipelining technique.
It's important to understand: Redis transactions differ from SQL databases. There is no rollback, no high-level isolation — what exists is atomicity and sequential execution. Let's dissect what that means.
MULTI starts a transaction: commands after it are not executed immediately, but queued. EXEC runs the entire queue sequentially without any other command interrupting. This guarantees atomicity — either all commands run, or (if there's a syntax error) none of them run.
redis-cli MULTI
redis-cli DECRBY account:A 100
redis-cli INCRBY account:B 100
redis-cli EXECMULTI starts the transaction, DECRBY and INCRBY are queued, then EXEC runs both without being interleaved by another client. Between MULTI and EXEC, Redis returns QUEUED for each command.
If you change your mind before EXEC, use DISCARD to discard the whole queue:
redis-cli MULTI
redis-cli SET balance:1 0
redis-cli DISCARDredis-cli DISCARD cancels all already-queued commands without executing anything. Important: Redis transactions have no rollback. If one command fails during EXEC (e.g. a type mismatch), the other commands still run — unlike SQL databases that roll everything back.
Info
The power of MULTI/EXEC is not rollback, but isolated execution: no other client can slip in between the commands of one transaction. For conditional-logic needs, episode 10 introduces Lua, which is far more powerful.
A transaction alone is not enough for a "read then write" scenario. Classic example: two clients read stock 5, both decrement to 4, then both write 4 — even though the stock should be 3. This is called a race condition or double spending.
WATCH solves this with optimistic locking: you watch one or more keys. If any of those keys is changed by another client before EXEC, the transaction is aborted and EXEC returns nil — the application must retry from the beginning.
redis-cli WATCH stock:sku-1
redis-cli GET stock:sku-1
redis-cli MULTI
redis-cli DECRBY stock:sku-1 1
redis-cli EXECWATCH stock:sku-1 starts watching. After GET, you run MULTI...EXEC. If no other client changes stock:sku-1 between WATCH and EXEC, the transaction succeeds. If it does, EXEC returns nil and the application must retry — this is the retry loop pattern.
| Approach | How It Works | Risk |
|---|---|---|
| Pessimistic lock | Lock the resource before access | Deadlock, overhead |
| Optimistic (WATCH) | Detect conflicts at commit | Retries the application must handle |
WATCH is a good fit for low-conflict workloads: transactions rarely abort, so retries are rare. This is the fundamental pattern for product stock, account balances, and booking slots in large-scale applications.
Every Redis command costs one network round-trip. If you send 100 commands, that's 100 network round trips. Pipelining sends all commands in a single batch without waiting for individual responses, then reads all responses at once:
printf 'SET k1 v1\r\nSET k2 v2\r\nGET k1\r\nGET k2\r\n' | redis-cli --piperedis-cli --pipe reads many commands at once and sends them in a single session — dramatically cutting network overhead. This is useful for seeding data or batch operations. In applications, libraries like ioredis or redis-py provide pipeline() for the same purpose.
Success
Distinguish it from MULTI/EXEC: pipelining only combines network traffic, it doesn't guarantee atomicity. For a batch that must be atomic, combine both — a pipeline wrapping MULTI/EXEC.
Pipelining is not magic: responses must still be processed in the same order. For huge batches, split them into chunks so client memory doesn't blow up.
These three mechanisms are often confused. Here's the clear distinction:
| Mechanism | Atomic execution | Saves network | Conditional logic |
|---|---|---|---|
MULTI/EXEC | Yes | No | No |
| Pipelining | No | Yes | No |
| Lua script | Yes | Yes | Yes |
Choose per need: simple atomicity → MULTI/EXEC; sending many commands → pipelining; decisions inside a batch → Lua (episode 10).
KEYS *) inside a transaction — the block becomes even longer.MULTI to prevent race conditions — that's WATCH's job, not MULTI's.EXEC returns nil because of WATCH.Episode 9 equipped you with Redis transactions and their optimization techniques: MULTI/EXEC for sequential atomic execution, DISCARD for cancellation, WATCH for optimistic locking against race conditions, and pipelining for cutting network round-trips.
Key takeaways:
MULTI queues commands, EXEC runs them without interruption by other clients.WATCH aborts a transaction if a watched key changes before EXEC.In the next episode, episode 10, we cover Lua Scripting & Redis Functions — running complex logic atomically on the Redis server. You'll learn EVAL/EVALSHA, writing Lua scripts with conditional logic, and registering permanent functions with Redis Functions in Redis 7. Ready?