How Redis Functions differ from Lua scripts — functions persist across restarts in RDB/AOF. Function libraries, the shebang declaration, registering multiple functions in one library, replication semantics, and migration from EVALSHA.
A-3 — Redis Functions: Persistent Stored Procedures
Who this module is for: You use
EVALSHAto call Lua scripts by SHA digest and manage script lifecycle manually — re-loading scripts after restarts, distributing SHAs across application instances. Redis Functions (introduced in Redis 7.0) solve exactly these pain points. This module covers the Functions model, how it differs from EVAL/EVALSHA, and when to migrate.
The Problem with EVALSHA
SCRIPT LOAD + EVALSHA works, but has operational friction:
-
Scripts do not persist — they live in a volatile in-memory cache. Redis restart = all scripts gone. Your application must re-load scripts on every startup (or handle
NOSCRIPTerrors). -
SHA distribution — every application instance needs to know the SHA digest of every script. If you deploy a new script version, every instance must get the new SHA simultaneously.
-
No introspection — Redis cannot list what scripts are loaded.
SCRIPT EXISTS shatells you if a specific SHA is loaded, but you cannot ask "what scripts are in the cache?" -
No namespace — all scripts share one global namespace (the SHA cache). No way to group related scripts.
Redis Functions solve all four problems.
What Redis Functions Are
Redis Functions (Redis 7.0+) are named, persistent, typed server-side functions. They are stored in Redis like data — persisted to RDB/AOF, replicated to replicas, and survive restarts.
Key differences from EVAL scripts:
| Feature | EVAL / EVALSHA | Redis Functions |
|---|---|---|
| Persistence | Volatile (cache) | Durable (RDB + AOF) |
| Naming | SHA digest only | Named (library + function name) |
| Discovery | SCRIPT EXISTS sha | FUNCTION LIST |
| Engine | Lua only | Lua (+ future engines) |
| Replication | Command replicated | Library replicated |
| Namespace | Global | Per-library |
Function Library Anatomy
Functions are grouped into libraries. A library is a Lua module with a #! shebang declaring the engine and library name, plus one or more redis.register_function() calls:
#!lua name=mylib
-- Register a rate limiter function
redis.register_function('check_rate_limit', function(keys, args)
local key = keys[1]
local window = tonumber(args[1])
local limit = tonumber(args[2])
local now = tonumber(args[3])
local window_start = now - window
redis.call('ZREMRANGEBYSCORE', key, 0, window_start)
local count = redis.call('ZCARD', key)
if count >= limit then
return 0
end
redis.call('ZADD', key, now, tostring(now) .. tostring(math.random(1000000)))
redis.call('PEXPIRE', key, window)
return 1
end)
-- Register a conditional set function
redis.register_function('set_if_greater', function(keys, args)
local current = redis.call('ZSCORE', keys[1], args[1])
if current == false or tonumber(args[2]) > tonumber(current) then
redis.call('ZADD', keys[1], args[2], args[1])
return 1
end
return 0
end)
Loading a Library
FUNCTION LOAD [REPLACE] function-code
# Load from command line
redis-cli FUNCTION LOAD "#!lua name=mylib\n\nredis.register_function('my_fn', function(keys, args)\n return args[1]\nend)"
# Load from file
redis-cli FUNCTION LOAD "$(cat mylib.lua)"
# Replace existing library (update)
redis-cli FUNCTION LOAD REPLACE "$(cat mylib.lua)"
In Node.js:
import fs from 'fs';
const libraryCode = fs.readFileSync('./src/redis/mylib.lua', 'utf8');
async function loadFunctions() {
try {
await redis.call('FUNCTION', 'LOAD', libraryCode);
console.log('Functions loaded');
} catch (err: any) {
if (err.message.includes('Library already exists')) {
// Already loaded — skip (or use REPLACE to update)
return;
}
throw err;
}
}
// Call once at application startup
await loadFunctions();
Sign in to keep reading
The rest of this module is free — sign in with Google to unlock it and track your progress.
Sign in & RegisterDiscussion
0Join the discussion