You want to run a small piece of named server-side logic on Redis 7 using its Functions feature. What does the Lua library file have to contain, which command installs it, and how do you invoke one of its functions?
answer
- First line: #!lua name=mylib
- redis.register_function('name', cb)
- callback(keys, args), both 1-indexed
- redis-cli -x FUNCTION LOAD REPLACE < file.lua
- FCALL fname numkeys keys… args…
basics
~20 sThe file starts with a shebang naming the engine and library — #!lua name=mylib — and calls redis.register_function('fname', callback). Install it with FUNCTION LOAD, then run it with FCALL fname numkeys key… arg…. The callback receives (keys, args).
solid answer
~40 sA library is a single Lua file. Its **first line** must be `#!lua name=<libname>` — the engine plus a unique library name. The body defines callbacks and registers each one: ``` redis.register_function('greet', function(keys, args) … end) ``` The callback signature is `(keys, args)`: two Lua tables, 1-indexed, holding the key names the caller declared and the remaining arguments. Inside you use `redis.call`/`redis.pcall` exactly as in a script. Install with `FUNCTION LOAD <source>` — commonly `redis-cli -x FUNCTION LOAD REPLACE < lib.lua`, since `REPLACE` is what lets you re-load an existing library. The reply is the library name. Invoke with `FCALL greet 1 mykey extra` — function name, then how many of the following arguments are keys, then keys, then plain arguments. `FUNCTION LIST` shows what is installed, `FUNCTION DELETE <libname>` removes it.
code
lua · 15 lines#!lua name=inventory
local function reserve(keys, args)
local left = tonumber(redis.call('GET', keys[1]) or '0')
local want = tonumber(args[1])
if left < want then return redis.error_reply('OUT_OF_STOCK') end
return redis.call('DECRBY', keys[1], want)
end
local function peek(keys, args)
return redis.call('GET', keys[1])
end
redis.register_function('reserve', reserve)
redis.register_function{function_name='peek', callback=peek, flags={'no-writes'}}go deeper
Recall the four moving parts: shebang line, redis.register_function, FUNCTION LOAD, FCALL with numkeys — and that the callback gets (keys, args).
Add the management surface (FUNCTION LIST/DELETE/FLUSH), the REPLACE requirement, and the no-writes flag that unlocks FCALL_RO.
Treat loading as a deployment step performed against every primary, and mention testability of plain-parameter callbacks plus the protected-globals rule.
Position the library as a versioned interface: function names are a server-wide namespace, so naming and ownership conventions matter more than the syntax.
## The anatomy of a library file Redis Functions (7.0+) organize server-side code into **libraries**. A library is one source file, and Redis identifies it by a mandatory shebang on the very first line: ``` #!lua name=mylib ``` `lua` is the execution engine (the only one shipped today) and `name=mylib` is the library's unique name on this server. Loading fails immediately if the shebang is missing or malformed, or if the name is already taken and you did not pass `REPLACE`. After the shebang the file is ordinary Lua that runs **once, at load time**. Its job is to define callbacks and hand them to Redis: ``` local function greet(keys, args) return 'hello ' .. args[1] end redis.register_function('greet', greet) ``` `redis.register_function` can also be called with a table form that carries a description and flags: `redis.register_function{function_name='greet', callback=greet, flags={'no-writes'}}`. Registration happens at load; there is no way to add a function to a library later — you reload the whole library. ## The callback signature A function callback takes exactly two parameters: `keys` and `args`. Both are Lua tables indexed from 1. `keys` holds the key names the caller declared on the command line; `args` holds everything after them. This is the notable syntactic difference from EVAL scripts, which read the globals `KEYS` and `ARGV` instead — here they are ordinary parameters, which makes the code easier to unit-test and matches the library's protected-globals rule (you cannot create global variables at call time; anything you want to keep must live in a Redis key). Inside the callback you talk to Redis with `redis.call('GET', keys[1])` or the error-swallowing `redis.pcall`, and you return a value that Redis converts to a protocol reply — Lua number to integer, string to bulk string, table to array, `redis.error_reply`/`redis.status_reply` for explicit error and status replies. ## Loading it `FUNCTION LOAD [REPLACE] <library-code>` installs it. Because the code is one long argument, the usual shell form is: ``` redis-cli -x FUNCTION LOAD REPLACE < mylib.lua ``` where `-x` reads the last argument from stdin. The reply is the library name. Without `REPLACE`, loading a library whose name already exists is an error — this is deliberate, so an accidental re-deploy does not silently change behaviour. Because a loaded library is part of the dataset, this is a **one-time deployment action per primary**, not something the client does per connection: it is snapshotted to RDB, propagated to replicas and the AOF, and is still present after a restart. ## Calling it ``` FCALL <function-name> <numkeys> [key … ] [arg … ] ``` Note that you call the **function** name, not the library name — function names are unique across the whole server, which is why two libraries cannot register the same name. `numkeys` tells Redis how many of the following arguments are key names; those become the `keys` table, and the rest become `args`. Passing `0` is legal for a function that touches no keys. `FCALL_RO` is the read-only sibling: it is accepted only for functions registered with the `no-writes` flag and can therefore be routed to a replica. ## Managing what is installed - `FUNCTION LIST` — libraries, their engines and their registered functions; `WITHCODE` also returns the source, and `LIBRARYNAME x` filters. - `FUNCTION DELETE <libname>` — removes a library and all its functions. - `FUNCTION FLUSH` — removes every library on the server. - `FUNCTION STATS` — the currently running function (if any) and engine info. - `FUNCTION DUMP` / `FUNCTION RESTORE` — export and import all libraries as a binary payload. ## Beginner traps - Forgetting the shebang, or writing `#!lua name = mylib` with spaces — the load is rejected. - Calling `FCALL mylib …` (the library name) instead of the function name. - Getting `numkeys` wrong: too small and your key silently lands in `args`, so the function reads or writes nothing; too large and Redis errors or treats an argument as a key. - Assuming Lua indexes from 0 — `keys[1]` is the first key. - Trying to keep state in a global between calls; the globals table is protected and this raises an error.
- What happens if you pass the wrong numkeys to FCALL — say 0 when your function expects one key?Redis puts every argument into the args table and leaves keys empty, so the function typically fails or operates on nothing. Redis cannot detect the mistake because only the caller knows which arguments are keys. In Cluster the consequences are worse: the command may be routed to the wrong node since routing is computed from the declared keys.
- Why does the callback receive keys and args as parameters instead of using the KEYS and ARGV globals that EVAL scripts use?Function libraries run with a protected global table — code cannot create or mutate globals at call time — so per-invocation data has to arrive as parameters. It also makes the callbacks plain Lua functions that can be tested outside Redis, and it removes the temptation to stash state in globals between calls.
saying these in an interview costs you the question
- Calling FCALL with the library name instead of the function name
- Omitting or mistyping the #!lua name=… shebang
- Indexing keys[0]/args[0] as if Lua were zero-based
- Expecting a plain FUNCTION LOAD to overwrite an existing library
- Storing per-call state in Lua globals inside the library