Redis / Intermediate: application correctness and coordination / Lesson 10
Bounded scripts and Redis Functions
Move small read-dependent decisions to the server while preserving bounded execution, declared keys and honest error semantics. Distinguish script caching from function deployment.
On this page
Lesson goals
- Explain script atomicity, blocking cost and validation order.
- Identify EVAL/Functions lifecycle and Cluster key constraints.
Atomic execution also occupies the server
A Redis Lua script can read values and decide a small update without a client round trip between the read and write. Its execution is atomic with respect to other client commands, but long scripts block other work on the relevant server. Bound work by input and data size; do not scan an unbounded collection inside a script. Lua is a constrained server environment, not permission to read host files or contact external services. Atomic execution does not create automatic rollback of writes made before a later script error, so validate inputs and expected types before mutating state.
Validate a small inventory decision before its single write
The illustrative Lua fragment expects one declared key and one quantity argument. It accepts only small nonnegative integer stock and a positive bounded integer request, then decrements only when sufficient. The numeric bound is a teaching domain chosen to avoid pretending Lua floating-number conversion handles arbitrary huge integers exactly. No EVAL command is issued and the fragment is not run. With stored stock nine and request three, its reasoned return is six. Missing, malformed or insufficient values return before the mutation. This is a one-key primitive, not a complete reservation ledger or distributed lock.
local stock = tonumber(redis.call('GET', KEYS[1]))
local need = tonumber(ARGV[1])
if not stock or not need or stock < 0 or need <= 0
or stock ~= math.floor(stock) or need ~= math.floor(need)
or stock > 1000000 or need > 1000000 then
return redis.error_reply('INVALID_STOCK_INPUT')
end
if stock < need then return false end
return redis.call('DECRBY', KEYS[1], need)
For stock 9 and need 3, expected result is 6. No live script, key or persistence setting is created. Declared key routing and permissions still require review.
Check every failure before claiming an invariant
An error after a script has already written does not generally undo those earlier changes. redis.call errors and redis.pcall handling have different control-flow behavior, but neither should be advertised as a transaction rollback engine. The busy-script threshold is not an automatic safe cancellation deadline; interrupting a script after writes has integrity implications and documented restrictions. Prefer tiny deterministic workflows with explicit validation and known command complexity. A function that accepts a callerโs arbitrary command or unbounded key list expands authority and cost beyond a reviewed application primitive.
Test the declared domain separately from server execution
The Python model exercises the same small arithmetic preconditions without representing Lua types, Redis atomic scheduling or client error replies. It rejects insufficient stock and invalid quantities before producing a new balance. Such offline cases are useful for the business rule, but a production implementation must also verify command types, declared-key routing, permissions, script lifecycle and concurrent behavior in its approved environment. Keep the source available when relying on EVALSHA because cached scripts can be absent after restart or cache lifecycle events; a digest alone is not a durable deployment artifact.
def reserve_model(stock, need):
if type(stock) is not int or type(need) is not int:
raise ValueError('INVALID_STOCK_INPUT')
if not (0 <= stock <= 1000000 and 0 < need <= 1000000):
raise ValueError('INVALID_STOCK_INPUT')
return None if stock < need else stock - need
assert reserve_model(9, 3) == 6
assert reserve_model(2, 3) is None
Only arithmetic outcomes are checked. Lua execution, Redis error behavior and durability remain untested illustrations.
Separate EVAL cache and versioned function management
EVAL scripting has existed since Redis 2.6, while Redis Functions are available from 7.0 and have a different loading and persistence lifecycle. Function deployment is an administrative change owned by the release process, not something this course performs. Redis Cluster scripts and functions must obey declared-key and slot requirements; do not construct hidden data-dependent key access that evades routing assumptions. Managed services may restrict programmability or administrative commands. Version source, test boundaries, define compatible client behavior and keep fallback/reload handling explicit without allowing uncontrolled script retries or arbitrary code submission.
Common errors and fixes
NOSCRIPT No matching script. Please use EVAL.
- Why it happens
- The application assumed an EVALSHA cache entry always existed.
- How to fix it
- Use the reviewed client/source reload lifecycle without replaying uncertain business effects blindly.
- Prevent it next time
- Keep source/version metadata and test restart/cache-miss behavior.
A script error leaves earlier changes applied
- Why it happens
- Atomic execution was mistaken for rollback on error.
- How to fix it
- Validate before writes and redesign multi-step failure paths.
- Prevent it next time
- Test errors at every mutation boundary.
A slow script delays unrelated clients
- Why it happens
- Work was unbounded or command complexity ignored.
- How to fix it
- Reduce the server-side primitive to bounded work under review.
- Prevent it next time
- Budget input sizes and avoid unbounded loops/scans.
Check your understanding
1. Why is validation before mutation important even inside an atomic Lua script?
Show worked solution
Atomic execution prevents other commands from interleaving, but does not promise rollback of writes before a later error. Check argument domain, key type and required state first, then perform the minimal bounded mutation. Also inspect any remaining failure path after the first write instead of equating one EVAL call with SQL transaction semantics.
2. An application knows a script SHA but no longer has its source. What operational weakness follows?
Show worked solution
It cannot reliably reload a missing script after restart or cache lifecycle events. Keep a reviewed source artifact and version binding, and use documented client handling for missing cache entries. The SHA identifies script content; it is not proof that every target server has loaded that content or that the application is authorized to load it.
Key takeaways
- Bound server-side work and validate before its first mutation.
- Atomic execution, error recovery and deployment lifecycle are distinct.