Lua Scripting
Why Lua scripting?
Section titled “Why Lua scripting?”Transactions handle grouped writes, but they cannot express conditional logic. If you need to read a value, inspect it, and write back only under certain conditions — all atomically — you need a Lua script.
Redis has a built-in Lua 5.1 interpreter. A script runs entirely on the server: no network round-trips between steps, and no other command can execute while the script is running.
EVAL — running a Lua script
Section titled “EVAL — running a Lua script”The EVAL command takes a Lua script string, the number of keys it will access, any key names, and any additional arguments:
EVAL script numkeys [key [key ...]] [arg [arg ...]]Inside the script, keys are accessed via KEYS[1], KEYS[2], … and arguments via ARGV[1], ARGV[2], …
127.0.0.1:6379> EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 mykey "hello"OK127.0.0.1:6379> GET mykey"hello"redis.call(command, ...) executes a Redis command from within Lua. If the command raises an error, redis.call propagates it as a Lua error (which becomes a Redis error to the client). Use redis.pcall if you want to catch the error in Lua instead.
A practical INCR example
Section titled “A practical INCR example”The simplest atomic read-modify-write: increment a counter.
127.0.0.1:6379> EVAL "return redis.call('INCR', KEYS[1])" 1 counter(integer) 1127.0.0.1:6379> EVAL "return redis.call('INCR', KEYS[1])" 1 counter(integer) 2127.0.0.1:6379> GET counter"2"EVAL "return redis.call('INCR', KEYS[1])" 1 counter
EVAL "return redis.call('INCR', KEYS[1])" 1 counter
GET counterConditional logic in Lua
Section titled “Conditional logic in Lua”Here is a pattern that sets a key only if the current value is below a threshold — something impossible to do atomically with plain MULTI/EXEC:
-- rate-limiter: increment counter; return 1 if under limit, 0 if overlocal current = redis.call('INCR', KEYS[1])if current == 1 then redis.call('EXPIRE', KEYS[1], ARGV[1])endif current <= tonumber(ARGV[2]) then return 1else return 0end127.0.0.1:6379> EVAL "local c=redis.call('INCR',KEYS[1]) if c==1 then redis.call('EXPIRE',KEYS[1],ARGV[1]) end if c<=tonumber(ARGV[2]) then return 1 else return 0 end" 1 ratelimit:user1 60 5(integer) 1SCRIPT LOAD and EVALSHA
Section titled “SCRIPT LOAD and EVALSHA”Re-sending the full script text every request wastes bandwidth. SCRIPT LOAD uploads the script once and returns a SHA1 hash. You then invoke it with EVALSHA:
127.0.0.1:6379> SCRIPT LOAD "return redis.call('INCR', KEYS[1])""2068d9c36e28e0f50d70bedb00173c36e5f4f39d"127.0.0.1:6379> EVALSHA 2068d9c36e28e0f50d70bedb00173c36e5f4f39d 1 counter(integer) 3The script is cached in Redis memory for the lifetime of the server (or until SCRIPT FLUSH). If the script is not found — e.g., after a server restart — EVALSHA returns a NOSCRIPT error and your client should fall back to EVAL to re-upload it.
SCRIPT LOAD "return redis.call('INCR', KEYS[1])"
EVALSHA 2068d9c36e28e0f50d70bedb00173c36e5f4f39d 1 counter
GET counter