skip to content

Redis 7.0 introduced Redis Functions alongside the older EVAL/EVALSHA Lua scripting. What is a Redis Function library, and how does it differ from a script executed with EVAL?

level: middleimportance: must knowfreq 45%

answer

  1. #!lua name=lib + redis.register_function
  2. FUNCTION LOAD once, FCALL by name
  3. Library is dataset: RDB, AOF, replicas
  4. EVAL cache is ephemeral → NOSCRIPT
  5. Same sandbox, same blocking atomicity

basics

~20 s

A Function library is named Lua code loaded once with FUNCTION LOAD and stored in the dataset itself — persisted to RDB/AOF, replicated, surviving restart. You invoke it by name with FCALL. EVAL scripts are anonymous, cached only, and identified by SHA1.

solid answer

~50 s

With EVAL the client ships the script body every time; Redis keeps it in a **script cache** keyed by SHA1 so later calls can use EVALSHA. That cache is not part of the dataset: `SCRIPT FLUSH`, a restart, or a failover empties it, so clients must keep the source and handle `NOSCRIPT`. Scripts are anonymous — no name, no version, no way to inventory what is installed. Redis Functions replace that with a **library**: one Lua file starting with `#!lua name=mylib` that calls `redis.register_function` for each named callback. `FUNCTION LOAD` installs it; the library becomes part of the database — written to RDB, propagated through AOF, replicated to replicas, and still there after restart. You call it with `FCALL fname numkeys key… arg…`, and `FUNCTION LIST/DUMP/RESTORE/DELETE/FLUSH/STATS` manage it. Execution semantics are unchanged: same Lua sandbox, same atomic run that blocks the server, same key-declaration and determinism rules.

code

lua · 12 lines
lua
#!lua name=counters

local function bump(keys, args)
  local n = redis.call('INCRBY', keys[1], args[1])
  if tonumber(n) > tonumber(args[2]) then
    redis.call('SET', keys[1], args[2])
    return tonumber(args[2])
  end
  return n
end

redis.register_function('bump_capped', bump)

go deeper

for a junior

Know that Functions are named server-side Lua loaded once with FUNCTION LOAD and called with FCALL, versus EVAL where you send the script each time.

for a middle

Explain the storage difference — library is part of the dataset (RDB/AOF/replication) while the EVAL script cache is ephemeral — and that execution semantics and atomicity are unchanged.

for a senior

Frame it operationally: no NOSCRIPT fallback, deployable and inventoriable code, flags like no-writes enabling FCALL_RO, and the fact that a slow function is still a server-wide stall.

for a principal

Discuss it as a code-ownership boundary: what logic legitimately belongs inside the datastore, how you version and roll back library deployments across a fleet, and the client/tooling support you are betting on.

## Where EVAL leaves you Before Redis 7.0 the only way to run server-side logic was `EVAL`: the client sends the entire Lua source, the number of keys, the key names and the extra arguments, and Redis runs the script atomically. Redis then keeps that script body in a **script cache** keyed by the SHA1 digest of the source, so subsequent calls can send just the digest with `EVALSHA` and save bandwidth. The crucial property is that this cache is *not part of the data*. It is never written to the RDB snapshot, it is not stored in the AOF as a cache, it is emptied by `SCRIPT FLUSH`, by a server restart, and it is empty on a freshly promoted replica after a failover. So every EVALSHA-based client has to keep the script text in the application anyway and implement a fallback: catch the `NOSCRIPT` error, reload with `SCRIPT LOAD` (or fall back to plain `EVAL`), and retry. Scripts are also **anonymous** — a SHA1 is not a name, carries no version, and there is no way to ask a server "which pieces of application logic are installed on you?" ## What a Function library is Redis 7.0 added **Functions**. Instead of an anonymous blob you write a **library**: a single Lua source file whose first line is a shebang naming the engine and the library, e.g. `#!lua name=ratelimit`. The body registers one or more named callbacks: ``` redis.register_function('take_token', function(keys, args) … end) ``` Each callback receives two Lua tables — the keys the caller declared and the remaining arguments — instead of the global `KEYS`/`ARGV` tables scripts use. You install the library once with `FUNCTION LOAD <code>` (or `FUNCTION LOAD REPLACE <code>` to upgrade in place) and call a function by name with `FCALL take_token 1 user:42 10`. ## Load once, live in the database The defining difference is **where the code lives**. A loaded library is part of the server state: - it is serialized into RDB snapshots and therefore survives restart; - loading it is propagated to replicas and to the AOF, so replicas have it too, and a promoted replica keeps working; - `FUNCTION DUMP` gives you a binary payload of all libraries and `FUNCTION RESTORE` loads it into another server — a way to seed a new node or a new cluster; - `FUNCTION LIST [LIBRARYNAME x] [WITHCODE]` inventories what is installed, `FUNCTION STATS` reports the currently running function and the engines, `FUNCTION DELETE`/`FUNCTION FLUSH` remove libraries, and `FUNCTION KILL` stops a runaway read-only invocation. Because of this, applications stop carrying script text and stop handling `NOSCRIPT`. Installing logic becomes a deployment step ("load the library on every primary") rather than a per-connection concern. ## Naming, flags and structure Functions have real names, so a library can expose a small API (`take_token`, `refund_token`) and you can version by library name (`ratelimit_v2`) and cut over. At registration you can attach **flags** — most importantly `no-writes`, which marks a function as read-only and makes it callable through `FCALL_RO` and on replicas; others include `allow-oom`, `allow-stale` and `no-cluster`. Scripts got comparable shebang flags later, but with Functions the declaration is part of the registered API. ## What does not change Functions are not a different execution model. They run on the same embedded Lua 5.1 engine, in the same sandbox, on the same single command-processing thread. A function executes **atomically**: no other client command interleaves, and while it runs the server is blocked — a slow function is a latency incident exactly as a slow script is. You must still declare every key you touch as a key argument, in Cluster all those keys must hash to one slot, and effects (the write commands the function performed) are what gets replicated and written to the AOF. Functions are actually stricter about globals: the library's global table is protected, so you cannot create globals at call time and cannot smuggle state between invocations — all state must live in Redis keys. ## Common gotchas - `FUNCTION LOAD` fails with an error if the library name already exists; upgrades need `REPLACE`. - A library is loaded as a whole; you cannot append one function to an existing library, and deleting the library removes all its functions. - Function names must be unique across libraries on the server. - Older clients and some managed/proxy layers may not speak `FCALL`; check support before committing. - Functions do not make code safe: a bug still runs with full privileges and can still block the server.

  • If Functions are stored in the dataset, what happens to them when a replica is promoted after a failover?
    They are already there. Loading a library is replicated to replicas, so the promoted node has the same libraries and clients can keep calling FCALL immediately. With EVALSHA the promoted node's script cache is typically empty, so the first EVALSHA returns NOSCRIPT until a client reloads the source.
  • How would you upgrade a library that is already loaded, and how do you avoid breaking in-flight callers?
    Use FUNCTION LOAD REPLACE with the new source; a plain FUNCTION LOAD errors because the name exists. To avoid breaking callers, either keep the function's contract identical, or version the library name (ratelimit_v2 with new function names), deploy the new library, switch application traffic, then FUNCTION DELETE the old one.
  • Do Functions remove the need to keep the code in your application repository?
    No. The server holds the running copy, but the source is still application code that needs review, tests and version control. FUNCTION LIST WITHCODE can show what is deployed, which is useful for drift detection, but the repository stays the source of truth and the load step belongs in your deployment pipeline.

EVALSHA is like pasting a script into a remote shell each session and hoping it is still in history; a Function library is installing a package on the box, with a name and a version, that survives reboot.

saying these in an interview costs you the question

  • Claiming Functions run outside the single-threaded command loop or don't block the server
  • Saying EVALSHA scripts are persisted or replicated like data — the script cache is ephemeral
  • Thinking FCALL can address keys in different hash slots in Cluster because the code is server-side
  • Believing a library can hold state in Lua globals between calls
  • Assuming a plain FUNCTION LOAD silently upgrades an existing library

context