Chapter 14 · Lua Scripting and Redis Functions

Redis Functions: Libraries, FCALL, Persistence, Deployment, and Versioned Server Logic

Package small server-side logic as named Redis Function libraries, invoke it with FCALL, inspect and back it up, and roll versions without confusing persisted Functions with the volatile script cache.

Advanced170–230 minutesRedis Functions lifecycleRedis Open Source 8.10.1redis-py 8.1.0 where usedFree/local-firstLast reviewed: September 6, 2026

Learning outcomes

AtlasMart has a reservation routine worth deploying consistently instead of shipping ad-hoc Lua source from every application process. Redis Functions, available since Redis 7, package functions into named libraries that are loaded into the server, persisted with Redis data, replicated, inspected, dumped, restored, and invoked with FCALL.

01

Explain library, function, engine, FCALL, and the difference between Functions persistence and Eval script-cache volatility.

02

Load a versioned Lua library, inspect it with FUNCTION LIST/STATS, and invoke it with declared keys and arguments.

03

Back up and restore Function libraries with FUNCTION DUMP/RESTORE using a binary-safe client path.

04

Use whole-library replacement/version naming deliberately rather than editing one function in place.

05

Separate standalone persistence evidence from later replication/failover topology verification.

Exact lab baseline

All Chapter 14 mandatory labs reuse the disposable Chapter 01 environment: Redis Open Source 8.10.1 from pinned Docker image redis:8.10.1, container atlasmart-redis-ch01, standalone topology, host endpoint 127.0.0.1:6379, TLS disabled only because traffic remains on loopback, default ACL user disabled, named academy-admin and atlasmart-app users, logical database 0, AOF with appendfsync everysec plus RDB snapshots, persistent /data, and no explicit maxmemory/eviction policy. Client examples that need Python use redis-py 8.1.0. Chapter-specific fixtures stay under atlasmart:ch14:.

1. Functions move code lifecycle onto the Redis server

An Eval script belongs to the application and may disappear from the script cache at any time. A Redis Function belongs to a named library loaded into Redis. Libraries are persisted in RDB/AOF and replicated with server state. This makes Functions a better fit for stable, explicitly deployed server-side APIs—but it also creates a deployment artifact that must be versioned, reviewed, backed up, and rolled out like code.

Lifecycle concern EVAL/EVALSHA Redis Function
Name/version Application convention + SHA1 Named library + named functions
Presence Cache can miss/evict/restart away Loaded server state
Persistence No Yes, with Redis data
Update unit Application source changes Whole library is immutable; replace/load new library
Invocation EVAL/EVALSHA FCALL/FCALL_RO

2. A library starts with a shebang and registered functions

The first line declares the Lua engine and library name. Functions are registered with redis.register_function. Unlike Eval scripts, Functions receive keys and args callback arrays rather than relying on global KEYS/ARGV. Keep the callback small and make all Redis keys invocation inputs.

Lua · atlasmart_ch14_v1 library
#!lua name=atlasmart_ch14_v1redis.register_function{function_name='reserve_stock_v1', callback=function(keys,args) local qty=tonumber(args[1]); local stock=tonumber(redis.call('GET',keys[1]) or '0'); if stock < qty then return {0,stock}; end; redis.call('DECRBY',keys[1],qty); redis.call('HSET',keys[2],'status','reserved','qty',qty,'logicVersion','v1'); return {1,stock-qty}; end, description='AtlasMart bounded reservation v1'}

3. Load and inspect the library with redis-py

FUNCTION LOAD accepts the library source as one payload. Using redis-py avoids cross-shell quoting problems and keeps the exact source version visible in the lab file.

Python · load v1 and inspect it
import redisr = redis.Redis(host="127.0.0.1", port=6379, username="academy-admin", password="AtlasMart-Admin-Lab-Only-2026", decode_responses=True)library = "#!lua name=atlasmart_ch14_v1\nredis.register_function{function_name='reserve_stock_v1', callback=function(keys,args) local qty=tonumber(args[1]); local stock=tonumber(redis.call('GET',keys[1]) or '0'); if stock < qty then return {0,stock}; end; redis.call('DECRBY',keys[1],qty); redis.call('HSET',keys[2],'status','reserved','qty',qty,'logicVersion','v1'); return {1,stock-qty}; end, description='AtlasMart bounded reservation v1'}"try:    r.function_delete("atlasmart_ch14_v1")except redis.ResponseError:    passprint(r.function_load(library))print(r.function_list(libraryname="atlasmart_ch14_v1", withcode=True))print(r.function_stats())

FUNCTION LIST WITHCODE exposes library metadata and source; FUNCTION STATS exposes engine/runtime information and a currently running function when one exists. Treat this as deployment evidence, not application business state.

4. FCALL invokes a named function with declared keys

FCALL function numkeys key... arg... looks similar to EVAL's input shape because Cluster routing still needs declared key names. The function name is global on the server, so versioning the function name avoids silently changing callers that still depend on v1 behavior.

redis-cli · invoke reserve_stock_v1
SET atlasmart:ch14:l3:stock 3FCALL reserve_stock_v1 2 atlasmart:ch14:l3:stock atlasmart:ch14:l3:order:9101 2GET atlasmart:ch14:l3:stockHGETALL atlasmart:ch14:l3:order:9101# Expected: FCALL returns [1,1]; order hash contains logicVersion=v1.

5. FUNCTION DUMP/RESTORE is binary deployment evidence

FUNCTION DUMP returns a serialized binary payload for all loaded libraries. Do not round-trip it through a text editor or a decode/encode path. The lab uses redis-py bytes, deletes only the Chapter 14 v1 library, restores the payload with an explicit policy, and verifies the library is back.

Python · binary-safe dump/delete/restore verification
import redisr = redis.Redis(host="127.0.0.1", port=6379, username="academy-admin", password="AtlasMart-Admin-Lab-Only-2026", decode_responses=False)payload = r.function_dump()print("dump_bytes", len(payload), "sha256", __import__("hashlib").sha256(payload).hexdigest())r.function_delete("atlasmart_ch14_v1")print("after-delete", r.function_list(libraryname="atlasmart_ch14_v1"))r.function_restore(payload, policy="APPEND")print("after-restore", r.function_list(libraryname="atlasmart_ch14_v1"))
Scope warning

FUNCTION DUMP serializes all loaded Function libraries, not just the AtlasMart one. On a shared server, restoring a full dump can interact with other libraries. This lab assumes the disposable Chapter 01 container; production needs inventory, collision policy, backup isolation, and restore drills.

6. Restart comparison: Functions persist; Eval cache does not

The clearest local lifecycle test is a controlled restart of the disposable container. First load a harmless Eval script and the Function library, record both, restart Redis, then compare SCRIPT EXISTS with FUNCTION LIST. Because the lab uses AOF/RDB and a persistent volume, the Function library should reload with the dataset while the Eval cache is not a persistence contract.

Python · prepare lifecycle evidence
import redisr = redis.Redis(host="127.0.0.1", port=6379, username="academy-admin", password="AtlasMart-Admin-Lab-Only-2026", decode_responses=True)sha = r.script_load("return 14")print("eval_sha", sha, "exists", r.script_exists(sha))print("functions", r.function_list(libraryname="atlasmart_ch14_v1"))print("Save this SHA, then run the bounded container restart below.")
shell · bounded restart of the disposable Redis lab only
docker restart atlasmart-redis-ch01# Reconnect after the container reports healthy/running. This interrupts only the dedicated learning instance.
redis-cli · post-restart evidence
FUNCTION LIST LIBRARYNAME atlasmart_ch14_v1SCRIPT EXISTS <the-SHA-recorded-before-restart># Expect the Function library to remain if persistence loaded successfully. Do not promise script-cache survival.

7. Version libraries instead of mutating callers invisibly

Function libraries are immutable as loaded units; changes are deployed as a whole. A safe migration can load atlasmart_ch14_v2 beside v1, expose reserve_stock_v2, move callers deliberately, and delete v1 only after rollback windows close. FUNCTION LOAD REPLACE is useful when replacing the same library name is truly intended, but it should not become an excuse for invisible API mutation.

Lua · v2 adds explicit quantity validation
#!lua name=atlasmart_ch14_v2redis.register_function{function_name='reserve_stock_v2', callback=function(keys,args) local qty=tonumber(args[1]); if not qty or qty <= 0 then return redis.error_reply('ERR qty must be positive'); end; local stock=tonumber(redis.call('GET',keys[1]) or '0'); if stock < qty then return {0,stock}; end; redis.call('DECRBY',keys[1],qty); redis.call('HSET',keys[2],'status','reserved','qty',qty,'logicVersion','v2'); return {1,stock-qty}; end, description='AtlasMart bounded reservation v2 with qty validation'}
Python · load v2 beside v1
import redisr = redis.Redis(host="127.0.0.1", port=6379, username="academy-admin", password="AtlasMart-Admin-Lab-Only-2026", decode_responses=True)library_v2 = "#!lua name=atlasmart_ch14_v2\nredis.register_function{function_name='reserve_stock_v2', callback=function(keys,args) local qty=tonumber(args[1]); if not qty or qty <= 0 then return redis.error_reply('ERR qty must be positive'); end; local stock=tonumber(redis.call('GET',keys[1]) or '0'); if stock < qty then return {0,stock}; end; redis.call('DECRBY',keys[1],qty); redis.call('HSET',keys[2],'status','reserved','qty',qty,'logicVersion','v2'); return {1,stock-qty}; end, description='AtlasMart bounded reservation v2 with qty validation'}"print(r.function_load(library_v2))print(r.function_list(libraryname="atlasmart_ch14_v2", withcode=True))

8. Replication/failover claims require the later topology labs

Official Redis documentation states that Function libraries are replicated and persisted, but this mandatory Chapter 14 topology is standalone. Therefore the chapter can verify persistence across restart and serialized dump/restore locally, but it does not fabricate replica promotion evidence. Chapters 19–20 will add real primary/replica and Sentinel failure tests with the same function-library lifecycle questions.

redis-cli · record server and lab context
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin PINGdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin INFO serverdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin CONFIG GET appendonly appendfsync maxmemory maxmemory-policy lua-time-limitdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin ACL WHOAMI# Record redis_version, persistence settings, maxmemory policy, and ACL identity before interpreting results.

9. Verification checklist, cleanup, and production judgment

  • FUNCTION LOAD created a named v1 library and FUNCTION LIST WITHCODE showed its exact source.
  • FCALL executed reserve_stock_v1 against only declared lesson keys.
  • FUNCTION DUMP produced binary bytes and the restore path verified library recovery.
  • A controlled restart distinguished persisted Function state from volatile Eval cache assumptions.
  • v2 was loaded as a separate versioned API rather than silently changing v1 callers.
redis-cli · bounded Chapter 14 cleanup
UNLINK atlasmart:ch14:l3:stock atlasmart:ch14:l3:order:9101FUNCTION DELETE atlasmart_ch14_v1FUNCTION DELETE atlasmart_ch14_v2

Use Functions for small stable server APIs whose deployment lifecycle matters. Treat library source, names, flags, ACL requirements, RDB/AOF compatibility, restore artifacts, and rollback plans as operational code. Do not move large domain services into Redis simply because Functions persist.

Check your understanding

  1. What makes Functions operationally different from EVALSHA?
  2. What is the update unit for a Function library?
  3. Why use a binary-safe client for FUNCTION DUMP?
  4. Did the restart test prove Sentinel failover?
  5. Why keep v1 and v2 function names during migration?
Review the answers

Functions are named server libraries persisted with Redis state and replicated; EVALSHA depends on an ephemeral script cache.

The whole library, not one function edited independently.

The dump is serialized binary data; text transformations can corrupt it.

No. It proves local persistence/reload behavior only; failover requires a replicated topology.

It makes caller compatibility and rollback explicit instead of silently changing the server API.

10. Summary and next step

You now have a deployable, persisted, inspectable Redis Function library and a versioned rollout model. Lesson 4 adds error handling, read-only flags/commands, ACL proof, and rollout guardrails so “it works as admin” does not become the security model.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.