Skip to content

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.

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"
OK
127.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.

The simplest atomic read-modify-write: increment a counter.

127.0.0.1:6379> EVAL "return redis.call('INCR', KEYS[1])" 1 counter
(integer) 1
127.0.0.1:6379> EVAL "return redis.call('INCR', KEYS[1])" 1 counter
(integer) 2
127.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 counter

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 over
local current = redis.call('INCR', KEYS[1])
if current == 1 then
redis.call('EXPIRE', KEYS[1], ARGV[1])
end
if current <= tonumber(ARGV[2]) then
return 1
else
return 0
end
127.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) 1

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) 3

The 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
Inside a Lua script, how do you access the first key passed to EVAL?
What does SCRIPT LOAD return?
What error does EVALSHA return when the script is not cached?
A Lua script run via EVAL is atomic because: