redis/redis
Scripting
Active contributors: antirez, Meir Shpilraien, Oran Agra, debing.sun.
Redis ships a Lua-based programmability layer with two front-ends:
EVAL/EVALSHA— ad-hoc scripts uploaded by the client.FCALL/FCALL_RO— calls into pre-loaded functions organised in libraries.
Both run on the same Lua interpreter (Lua 5.1 from deps/lua/). Scripts are atomic: the whole script executes on the main thread without interleaving any other commands.
Source layout
| File | Role |
|---|---|
src/eval.c |
The classic EVAL/EVALSHA family. Holds the script cache (SHA1 → source). |
src/script.c |
Shared infrastructure used by both EVAL and FUNCTION: timeout enforcement, replication mode, blocked-client integration. |
src/script.h |
Shared types and flags (scriptRunCtx, SCRIPT_* flags). |
src/script_lua.c |
The Lua-specific call-into-Redis bridge: redis.call, redis.pcall, redis.error_reply, redis.log, etc. |
src/script_lua.h |
Lua bridge API. |
src/functions.c |
The function/library subsystem: FUNCTION LOAD/LIST/DELETE/STATS/DUMP/RESTORE. |
src/functions.h |
Function metadata types. |
src/function_lua.c |
The Lua engine for functions (registers redis.register_function, parses libraries). |
deps/lua/ |
The bundled Lua 5.1 interpreter. |
EVAL: ad-hoc scripts
EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 mykey myvalueThe flow:
- The script body is hashed (SHA1) and cached.
EVALSHA <sha>lets the client refer to a cached script without re-uploading it.- The Lua interpreter is reused across calls (
server.luaglobal state). redis.call(cmd, ...)synthesises a fakeclient(server.script_caller), dispatches the command via the normalprocessCommandpath, captures the reply, and converts it to a Lua value.- The script's return value is converted back to RESP and sent to the calling client.
Reply conversion
| Lua | RESP |
|---|---|
nil |
RESP nil bulk |
| Number | Integer (truncated) |
| String | Bulk |
| Boolean true | :1 integer |
| Boolean false | nil |
| Table (array) | Array |
Table with err field |
RESP error |
Table with ok field |
RESP simple string |
redis.status_reply(s), redis.error_reply(s) |
Helpers to produce the above explicitly. |
For RESP3, the engine also supports tables with map, set, big_number, double, verbatim_string markers.
Atomicity
While a script runs, no other client can execute commands. There is no preemption; the only way to break out of an infinite loop is SCRIPT KILL (which only works if the script hasn't yet performed any writes — Redis won't kill a script that is partway through a multi-key mutation).
busy-reply-threshold (in milliseconds) controls when the server starts replying -BUSY to other clients while a script is running. The default is 5000 ms.
Replication
Scripts replicate as scripts by default — the master sends the EVAL itself to replicas. With script-effects-replication (a runtime setting via redis.replicate_commands() and redis.set_repl()), the master can replicate the effects command-by-command instead, which is safer with non-deterministic scripts.
SCRIPT EXISTS, SCRIPT LOAD, SCRIPT FLUSH, SCRIPT KILL are the script-management commands.
Functions: the modern way
FUNCTION LOAD accepts a library — a chunk of Lua that registers one or more functions:
#!lua name=mylib
redis.register_function('mysum', function(keys, args)
local total = 0
for i,v in ipairs(args) do total = total + tonumber(v) end
return total
end)
redis.register_function{
function_name = 'myget_ro',
callback = function(keys, args) return redis.call('GET', keys[1]) end,
flags = {'no-writes'}
}The shebang #!lua selects the engine; only Lua is implemented today, but the design allows other engines.
Functions persist across restarts (RDB-encoded in the AUX section). They have a stable name, support flags (no-writes, allow-oom, allow-stale, no-cluster), and are called via FCALL <name> <numkeys> <keys> <args>.
Compared to EVAL:
- Functions are loaded explicitly and reused. There's no "script cache" surprise.
- A library is a deployment artefact: you can
FUNCTION DUMPandFUNCTION RESTOREit. - Per-function flags are declared at registration, not inferred at runtime.
FCALL_RO is a write-rejecting variant — callable on replicas.
Sandbox
The Lua sandbox is restrictive:
- No filesystem access.
- No network access.
- No global table mutation (the
string/table/mathlibraries are sealed). - A small Redis-specific API:
redis.call,redis.pcall,redis.status_reply,redis.error_reply,redis.sha1hex,redis.log,redis.replicate_commands,redis.set_repl,redis.breakpoint,redis.debug,redis.setresp,redis.REPL_ALL/.../redis.REPL_NONE. - A subset of the Lua standard library (
base,table,string,math, pluscjson,cmsgpack,bitop,structfrom the bundled extensions).
The sandbox setup is in src/script_lua.c.
Script timeout
lua-time-limit (default 5000 ms) sets when a script is "slow". Past this, other clients receive -BUSY and can issue SCRIPT KILL (or SHUTDOWN NOSAVE). The handler is wired to a per-script flag checked from a Lua hook installed via lua_sethook.
Where to start modifying
- Add a
redis.<func>— register it inluaRegisterRedisAPIinsrc/script_lua.c. - Tighten the sandbox —
luaCreateBaseLibStatestrips and rebuilds the standard libraries. - Add a function flag — extend the
scriptFlagenum insrc/script.hand the parser insrc/function_lua.c. - Implement a non-Lua engine — register an engine via the
engineImplAPI insrc/functions.c. The structure is engine-agnostic by design.
Related pages
- Modules — modules can also expose new commands callable from scripts via
RedisModule_Call. - redis-server — the script subsystem is initialised from
main()viascriptingInitandfunctionsInit.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.