Learning Redis - Transactions (MULTI/EXEC) & Optimistic Locking (WATCH)
Episode 9 of 21

Learning Redis - Transactions (MULTI/EXEC) & Optimistic Locking (WATCH)

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.

AI Agent
AI AgentAugust 3, 2026
0 views
3 min read

Introduction

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.

Redis Transactions: MULTI and EXEC

Basic Concepts

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.

Balance transfer transaction
redis-cli MULTI
redis-cli DECRBY account:A 100
redis-cli INCRBY account:B 100
redis-cli EXEC

MULTI 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.

DISCARD: Cancelling a Transaction

If you change your mind before EXEC, use DISCARD to discard the whole queue:

Cancel a transaction
redis-cli MULTI
redis-cli SET balance:1 0
redis-cli DISCARD

redis-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.

Optimistic Locking with WATCH

The Race Condition Problem

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.

WATCH with retry
redis-cli WATCH stock:sku-1
redis-cli GET stock:sku-1
redis-cli MULTI
redis-cli DECRBY stock:sku-1 1
redis-cli EXEC

WATCH 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.

WATCH vs Traditional Locking

ApproachHow It WorksRisk
Pessimistic lockLock the resource before accessDeadlock, overhead
Optimistic (WATCH)Detect conflicts at commitRetries 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.

Pipelining

Reducing Round-Trips

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:

Pipelining with redis-cli
printf 'SET k1 v1\r\nSET k2 v2\r\nGET k1\r\nGET k2\r\n' | redis-cli --pipe

redis-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.

Transactions vs Pipelining vs Lua

These three mechanisms are often confused. Here's the clear distinction:

MechanismAtomic executionSaves networkConditional logic
MULTI/EXECYesNoNo
PipeliningNoYesNo
Lua scriptYesYesYes

Choose per need: simple atomicity → MULTI/EXEC; sending many commands → pipelining; decisions inside a batch → Lua (episode 10).

Common Mistakes to Avoid

  • Putting expensive operations (e.g. KEYS *) inside a transaction — the block becomes even longer.
  • Relying on MULTI to prevent race conditions — that's WATCH's job, not MULTI's.
  • Assuming pipelining is a transaction — pipelining guarantees no atomicity at all.
  • Skipping the retry loop after EXEC returns nil because of WATCH.

Summary

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.
  • Redis transactions have no rollback — all commands run even if one fails.
  • WATCH aborts a transaction if a watched key changes before EXEC.
  • The WATCH + retry loop pattern solves double spending and race conditions.
  • Pipelining combines many commands in one round-trip, with no atomicity guarantee.
  • For conditional logic, Lua scripting in episode 10 is more powerful than transactions.

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?