Redis - Scripting
Redis - Lua Scripting
Section titled “Redis - Lua Scripting”Redis allows for the execution of server-side scripts using a built-in Lua interpreter (available since Redis 2.6.0). The primary and most powerful benefit of Redis scripting is atomicity. A script is always executed as a single, uninterruptible operation. No other command can run concurrently while a script is executing, ensuring data consistency for complex operations.
Why Use Lua Scripts?
Section titled “Why Use Lua Scripts?”- Atomicity: Perform complex, multi-step operations (like ‘read-then-write’) as a single atomic transaction without needing to use
MULTI/EXEC. - Performance: Reduce network latency by combining multiple commands into a single round trip between your client and the Redis server.
- Data Locality: Logic executes on the server where the data lives, which can be more efficient than fetching data to the client, processing it, and sending it back.
Executing Scripts: EVAL and EVALSHA
Section titled “Executing Scripts: EVAL and EVALSHA”You can execute scripts using the EVAL command. A better practice for performance is to use SCRIPT LOAD to cache the script on the server, which returns a SHA1 hash. You can then execute the cached script using EVALSHA and its hash, saving network bandwidth on subsequent calls.
Syntax
Section titled “Syntax”EVAL script numkeys key [key ...] arg [arg ...]- script: The Lua script body as a string.
- numkeys: The number of key names being passed to the script (from the arguments that follow).
- key [key …]: The names of the keys your script will access. These are available in Lua via the
KEYStable (e.g.,KEYS[1],KEYS[2]). - arg [arg …]: Additional arguments for your script. These are available in Lua via the
ARGVtable (e.g.,ARGV[1],ARGV[2]).
Practical Example: An Atomic Rate Limiter
Section titled “Practical Example: An Atomic Rate Limiter”Let’s implement a simple rate limiter that allows a certain number of actions per user within a time window. This requires an atomic ‘increment and set-expiry’ operation, which is a perfect use case for a Lua script.
-- The Lua Script ---- KEYS[1]: The key for the user (e.g., 'rate_limit:user123')-- ARGV[1]: The expiration time window in seconds
local current = redis.call('INCR', KEYS[1])
if tonumber(current) == 1 then redis.call('EXPIRE', KEYS[1], ARGV[1])end
return currentExecuting the script:
# First request for user123, with a 60 second window.# The script increments the count to 1 and sets the expiry.redis> EVAL "local current = redis.call('INCR', KEYS[1])\nif tonumber(current) == 1 then\n redis.call('EXPIRE', KEYS[1], ARGV[1])\nend\nreturn current" 1 rate_limit:user123 60(integer) 1
# Second request within 60 seconds. The count is now 2.redis> EVAL "... same script ..." 1 rate_limit:user123 60(integer) 2
# The application logic would then check if the returned count exceeds the limit (e.g., > 10).Best Practices and Modern Developments
Section titled “Best Practices and Modern Developments”- Prefer EVALSHA: Always try to cache your scripts with
SCRIPT LOADand execute them withEVALSHA. Most client libraries handle this automatically. - Keep Scripts Fast: Scripts are blocking. A long-running script will stall your entire Redis server. Avoid complex loops or CPU-intensive logic.
- Keys vs. ARGV: Always pass key names via the
KEYStable, notARGV. This is crucial for Redis Cluster compatibility and future analysis tools. - Redis Functions (Redis 7+): Redis 7 introduced Functions, a more advanced way to manage scripts. Functions are grouped into libraries, are persisted, and are replicated across your cluster, providing a more robust and manageable alternative to standalone
EVALscripts.
Scripting Commands
Section titled “Scripting Commands”| Command | Description |
|---|---|
| EVAL script numkeys key [key …] arg [arg …] | Executes a Lua script. |
| EVALSHA sha1 numkeys key [key …] arg [arg …] | Executes a Lua script that is already cached by its SHA1 hash. |
| SCRIPT LOAD script | Loads a script into the script cache and returns its SHA1 hash. |
| SCRIPT EXISTS sha1 [sha1 …] | Checks for the existence of one or more scripts in the cache. |
| SCRIPT FLUSH [ASYNC|SYNC] | Removes all scripts from the script cache. |
| SCRIPT KILL | Kills the script currently in execution. Can only be used on scripts that haven’t performed any write operations. |
| FUNCTION LOAD [REPLACE] function-code | Loads a library of functions (Redis 7+). |
| FCALL function numkeys [key [key …]] [arg [arg …]] | Invokes a function from a loaded library (Redis 7+). |