Open-Source Wikis

/

Redis

/

Systems

/

Scripting

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 myvalue

The flow:

  1. The script body is hashed (SHA1) and cached.
  2. EVALSHA <sha> lets the client refer to a cached script without re-uploading it.
  3. The Lua interpreter is reused across calls (server.lua global state).
  4. redis.call(cmd, ...) synthesises a fake client (server.script_caller), dispatches the command via the normal processCommand path, captures the reply, and converts it to a Lua value.
  5. 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 DUMP and FUNCTION RESTORE it.
  • 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/math libraries 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, plus cjson, cmsgpack, bitop, struct from 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 in luaRegisterRedisAPI in src/script_lua.c.
  • Tighten the sandboxluaCreateBaseLibState strips and rebuilds the standard libraries.
  • Add a function flag — extend the scriptFlag enum in src/script.h and the parser in src/function_lua.c.
  • Implement a non-Lua engine — register an engine via the engineImpl API in src/functions.c. The structure is engine-agnostic by design.
  • Modules — modules can also expose new commands callable from scripts via RedisModule_Call.
  • redis-server — the script subsystem is initialised from main() via scriptingInit and functionsInit.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Scripting – Redis wiki | Factory