Redis Functions: Persistent Stored Procedures18 min read
Module A-3·18 min read
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.
Who this module is for: You use EVALSHA to 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 NOSCRIPT errors).
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 sha tells 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
Loading a Library
FUNCTION LOAD [REPLACE] function-code
bash
In Node.js:
typescript
Calling Functions: FCALL
text
typescript
Managing Libraries
text
text
Function Flags
Register functions with flags to declare their behaviour:
lua
Available flags:
no-writes — function is read-only; can be called with FCALL_RO and executed on replicas
allow-stale — allow calling on replicas even when replica is in stale state
no-cluster — function cannot run in Cluster mode
allow-busy — allow calling during lua-time-limit exceeded state (use with care)
no-writes is the most important: it enables calling the function via FCALL_RO on read replicas, reducing primary load.
Persistence and Replication
Unlike EVAL scripts, Functions are part of the Redis dataset:
Stored in RDB snapshots (survives restart)
Logged to AOF (survives restart with AOF persistence)
Replicated to all replicas automatically when a library is loaded or deleted
When a replica becomes a primary (after Sentinel failover), its function library is already up-to-date — no manual re-loading required.
Deployment Workflow
Initial Deploy
bash
Update Existing Library
bash
FUNCTION LOAD REPLACE atomically replaces the old library with the new one. Inflight calls to old function names complete normally; new calls after the replacement use the new code.
Multi-Region / Multi-Instance
Since Functions are replicated to all replicas, you only need to load to the primary. All replicas receive the library automatically.
For multiple independent Redis instances (not replicas), load to each:
bash
Backup and Restore
bash
EVAL vs Functions: When to Use Which
Scenario
Recommendation
Redis < 7.0
EVAL / EVALSHA (no choice)
Redis >= 7.0, new project
Functions — better operational model
Quick one-off script
EVAL — no need to manage a library
Production atomic operations
Functions — persistence, naming, discoverability
Cross-service shared logic
Functions — load once, call by name
Frequent deployments with script changes
Functions with REPLACE — atomic updates
Summary
Redis Functions (7.0+) are persistent, named, replicated server-side functions — the production-grade evolution of EVAL
Libraries group related functions with a #!lua name=libname header + redis.register_function() calls
FUNCTION LOAD loads a library (survives restart); FCALL calls a function by name
FUNCTION LOAD REPLACE atomically updates an existing library — zero-downtime function updates
FCALL_RO + no-writes flag allows read-only functions to run on replicas, reducing primary load
Functions are replicated to all replicas automatically — no manual re-loading after failover
FUNCTION LIST, FUNCTION DUMP/RESTORE for discovery and backup
Use Functions for production atomic operations in Redis 7.0+; use EVAL for quick scripts or older Redis versions
Next: A-4 — Redlock: The Algorithm, Its Guarantees, and Its Critics — the multi-instance distributed lock, what it guarantees under bounded clock drift, Martin Kleppmann's critique, and when to use it versus alternatives.
Knowledge Check
What is the most significant operational difference between using the older EVALSHA approach and using Redis Functions (introduced in Redis 7.0)?
A developer has written a Redis Function that analyzes a user's recent activity from several sets and lists, and returns a computed "engagement score." The function never writes or modifies any data in Redis. How should the developer register this function to optimize performance in a Redis Cluster with read replicas?
An engineering team needs to update an existing Redis Function library named user_utils with a critical bug fix. They want to ensure zero downtime and avoid any NOSCRIPT style errors during the deployment. Which command should they use?
Test your knowledge with more question sets
Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.
// Call the rate limiter functionconst allowed =awaitredis.call('FCALL','check_rate_limit',1,// numkeys`rate:${userId}`,// KEYS[1]'60000',// ARGV[1]: window ms'100',// ARGV[2]: limitString(Date.now())// ARGV[3]: current time);// Call the conditional setconst updated =awaitredis.call('FCALL','set_if_greater',1,'leaderboard:season:1','user:1001',// member'9500'// new score);
FUNCTION LIST [LIBRARYNAME pattern] [WITHCODE] → list all libraries (optionally with source)
FUNCTION INFO libraryname → info about a specific library
FUNCTION DELETE libraryname → remove a library
FUNCTION STATS → currently executing function (if any)
FUNCTION DUMP → serialize all libraries to RDB format
FUNCTION RESTORE payload [FLUSH | APPEND | REPLACE] → restore libraries from DUMP
FUNCTION FLUSH [ASYNC | SYNC] → delete all libraries
redis.register_function{ function_name ='read_session', callback =function(keys, args)return redis.call('HGETALL', keys[1])end, flags ={'no-writes'}-- declare: this function never writes}
# Load function library as part of deployment pipelineredis-cli -h redis.internal FUNCTION LOAD "$(cat src/redis/mylib.lua)"
# Use REPLACE to update in place (atomic replacement)redis-cli -h redis.internal FUNCTION LOAD REPLACE "$(cat src/redis/mylib.lua)"