Redis Transactions
What MULTI/EXEC guarantees, what it does not, and how WATCH provides optimistic concurrency.
MULTI starts a command queue; EXEC runs the queued commands as one isolated unit. No other
client's commands interleave.
MULTI
INCR shop:orders:count
LPUSH shop:orders:recent 4711
EXECWhat it guarantees
- Isolation. The queued commands execute consecutively with nothing in between.
- All-or-nothing execution. Either every command runs, or none does — if
EXECis never reached, or if a queued command was rejected at queue time.
What it does not guarantee
Errors detected at queue time (unknown command, wrong arity) abort the whole transaction at
EXEC. Errors detected at execution time do not.
WATCH: optimistic concurrency
WATCH makes EXEC fail if a watched key changed since the watch began. This is the compare-and-set
primitive Redis offers.
def transfer_credit(r, src, dst, amount):
while True:
try:
with r.pipeline() as pipe:
pipe.watch(src)
balance = int(pipe.get(src) or 0)
if balance < amount:
pipe.unwatch()
return False
pipe.multi()
pipe.decrby(src, amount)
pipe.incrby(dst, amount)
pipe.execute() # raises WatchError if src changed
return True
except redis.WatchError:
continue # someone else won; retryThe retry loop is mandatory. A WatchError is the protocol working, not an exceptional condition,
and code that treats it as fatal will lose writes under contention.
When a Lua script is better
A Lua script executes atomically on the server, so it replaces the watch-retry cycle with a single round trip and no contention loop. Prefer it when the logic is small and self-contained:
EVAL "if tonumber(redis.call('GET', KEYS[1])) >= tonumber(ARGV[1]) then
redis.call('DECRBY', KEYS[1], ARGV[1])
redis.call('INCRBY', KEYS[2], ARGV[1])
return 1
else
return 0
end" 2 shop:credit:1001 shop:credit:1002 50See Lua Scripts for the constraints that come with this.
In Redis Cluster
All keys in a transaction must map to the same hash slot, which in practice means using a hash tag:
{shop:user:1001}:credit
{shop:user:1001}:historyA transaction spanning slots is rejected. There is no cross-slot transaction in Redis Cluster.