Production-grade transaction handling for Redis. Master MULTI/EXEC for atomic command batches, WATCH for optimistic locking, and Lua scripting for complex atomic operations.
-- Atomic increment with caplocal current = tonumber(redis.call('GET', KEYS[1]) or0)
localmax = tonumber(ARGV[1])
if current < maxthenreturn redis.call('INCR', KEYS[1])
elsereturn current
end
Execute Script
EVAL "return redis.call('GET', KEYS[1])" 1 mykey
# Load and execute by hash (faster for repeated calls)
SCRIPT LOAD "return redis.call('GET', KEYS[1])"
# Returns: "a42059b356c875f0717db19a51f6aaa9161e77a2"
EVALSHA a42059b356c875f0717db19a51f6aaa9161e77a2 1 mykey
-- transfer.lualocal from = KEYS[1]
local to = KEYS[2]
local amount = tonumber(ARGV[1])
local from_balance = tonumber(redis.call('GET', from) or0)
if from_balance >= amount then
redis.call('DECRBY', from, amount)
redis.call('INCRBY', to, amount)
return1-- Successelsereturn0-- Insufficient fundsend
Pattern 2: Distributed Lock
-- acquire_lock.lualocal lock_key = KEYS[1]
local holder = ARGV[1]
local ttl = tonumber(ARGV[2])
if redis.call('SET', lock_key, holder, 'NX', 'EX', ttl) thenreturn1elsereturn0end-- release_lock.lualocal lock_key = KEYS[1]
local holder = ARGV[1]
if redis.call('GET', lock_key) == holder thenreturn redis.call('DEL', lock_key)
elsereturn0end
Pattern 3: Rate Limiter
-- rate_limit.lualocal key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local current = tonumber(redis.call('GET', key) or0)
if current < limit then
redis.call('INCR', key)
if current == 0then
redis.call('EXPIRE', key, window)
endreturn1-- Allowedelsereturn0-- Deniedend
Lua Best Practices
-- ✅ GOOD: Always use KEYS and ARGVlocal value = redis.call('GET', KEYS[1])
redis.call('SET', KEYS[2], ARGV[1])
-- ❌ BAD: Dynamic key names break clusterlocal key = 'user:' .. ARGV[1] -- Don't!
redis.call('GET', key)
-- ✅ GOOD: Early return for efficiencyifnot redis.call('EXISTS', KEYS[1]) thenreturnnilend-- ✅ GOOD: Use pcall for error handlinglocal ok, result = pcall(redis.call, 'GET', KEYS[1])
ifnot ok thenreturn redis.error_reply("Operation failed")
end