Part 6 · 1 chapters · ~8 min

Lua, Functions, Transactions and Pipelining

Pipelining to cut round trips (measured), MULTI/EXEC transactions and what they do not guarantee, WATCH for optimistic locking, Lua scripts and Redis Functions for atomic logic, script costs and blocking, and choosing between them.

8

Atomic logic and fewer round trips

code
# pipelining: send many commands, read replies once (measured: GET 172k/s → 1.81M/s with -P 16)

# MULTI/EXEC: queued commands run together, no interleaving; but no rollback if one fails
MULTI
DECRBY wallet:7 500
INCRBY wallet:9 500
EXEC

# WATCH: optimistic check-and-set
WATCH wallet:7
GET wallet:7            # client checks the value...
MULTI
DECRBY wallet:7 500
EXEC                    # nil if wallet:7 changed after WATCH: retry

-- Lua: the check and the update atomically, in one round trip
-- EVAL script 1 wallet:7 500
local bal = tonumber(redis.call('GET', KEYS[1]) or '0')
if bal < tonumber(ARGV[1]) then return -1 end
return redis.call('DECRBY', KEYS[1], ARGV[1])
toolatomicconditional logicuse
pipelinenonothroughput for independent commands
MULTI/EXECyes, no interleavingno (only WATCH-based retry)a fixed set of writes together
Lua / Functionsyesyescheck-then-act (rate limiters, locks, counters with limits)

Scripts run on the single command thread: keep them short, since a slow script blocks every client. Redis Functions (7+) are named, persisted libraries of Lua code, loaded once instead of sent with each call. And remember: Redis is not your ledger; balances belong in a database with constraints (Ledgers course).