Skip to content
EVAL — Server-Side Lua Scripting
step 1/3

Reading — step 1 of 3

Server-Side Scripting

~2 min readScripting, Replication & Streams

EVAL — Server-Side Lua Scripting

Redis embeds a Lua interpreter. You ship a script, Redis runs it atomically, and you get back the result. This is Redis's most powerful primitive — every other command can be composed inside a script.

The Two Killer Use Cases

1. Atomic compound operations. Without scripts you'd need WATCH/MULTI/EXEC and a retry loop. With EVAL, a 5-step read-modify-write happens atomically with zero round trips.

2. Reducing network round-trips. Loops that fire 100 Redis commands become a single EVAL.

The Wire Format

EVAL <script> <numkeys> <key1>...<keyN> <arg1>...<argM>
  • script — the Lua source code
  • numkeys — how many of the following args are keys
  • The split between keys and args matters for Redis Cluster routing (keys must hash to the same slot)

Inside the script, two globals are bound:

  • KEYS — an array of the keys (1-indexed, Lua style)
  • ARGV — an array of the other args

redis.call

Inside Lua, redis.call(cmd, arg1, ...) invokes a Redis command. It returns the result as a Lua value, type-mapped:

Redis typeLua type
Integer (:N)number
Simple string (+OK)table {ok = "OK"}
Bulk string ($len)string
Null bulk ($-1)false
Array (*N)table (1-indexed)
Error (-ERR)raises Lua error

When Lua returns a value, Redis maps it back to a RESP type:

Lua returnRESP output
numberInteger :N\r\n
stringBulk string $len\r\n...\r\n
nil / falseNull $-1\r\n
{ok = "X"}Simple string +X\r\n
{err = "msg"}Error -msg\r\n
array tableArray *N\r\n...

A Real Atomic INCR-with-Cap

This is the canonical example — capped counter, atomic:

local v = tonumber(redis.call('GET', KEYS[1])) or 0
if v >= tonumber(ARGV[1]) then return 0 end
return redis.call('INCR', KEYS[1])

Five operations, zero races, one round trip. Try expressing that with WATCH/MULTI/EXEC — it's a retry loop. With EVAL it's straight-line code.

EVALSHA — Script Caching

Sending the script every time is wasteful. SCRIPT LOAD <script> returns the SHA-1 of the script and caches it server-side. Then EVALSHA <sha1> <numkeys> ... runs the cached script. Real clients automatically do EVALSHA-then-fall-back-to-EVAL.

What You'll Build

A small dispatcher that recognizes a handful of common script shapes and routes them to your existing handlers. You don't need a full Lua interpreter — you need to understand the contract between scripts and the command runtime.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…