Redis Lua Script Time Limit Exceeded: Root Causes and Minimal Fixes
Quick Answer
- What it is: Redis's
lua-time-limit(default 5000ms) is a soft threshold—after it's exceeded, Redis marks the script as slow and starts acceptingSCRIPT KILL, but the script keeps running until it finishes or is killed. - First check: Run
redis-cli SLOWLOG GETto identify slow scripts, andredis-cli CONFIG GET lua-time-limitto confirm your current threshold. - Minimal fix: Kill a stuck script with
redis-cli SCRIPT KILL(safe if no writes occurred); if writes already happened, you must useSHUTDOWN NOSAVE(data loss risk). - Prevention: Keep scripts short, avoid
KEYSand unbounded loops, useredis.pcallfor error handling, and test withredis-cli --evalbefore production. - Version boundary: This behavior applies to Redis 2.6+ where Lua scripting was introduced; the default 5000ms limit has remained consistent across all subsequent versions.
What Problem It Solves
Redis executes Lua scripts atomically, blocking all other commands while a script runs. Without a safety mechanism, an infinite loop or poorly optimized script would freeze your Redis instance indefinitely, taking down every application that depends on it.
The lua-time-limit configuration provides a soft interruption point: once a script runs longer than the threshold, Redis starts accepting SCRIPT KILL commands. This gives operators a way to recover from runaway scripts without requiring a full instance restart.
Note that this is not a hard timeout. Redis cannot forcibly stop a running Lua script mid-execution because that would risk leaving the dataset in an inconsistent state. Instead, it flips a flag that allows an external SCRIPT KILL to interrupt the script at the next safe opportunity.
When This Error or Setup Appears
You'll encounter the BUSY Redis is busy running a script error in these scenarios:
- Infinite loops: A script contains
while true do ... endor a recursive pattern that never terminates. - Heavy computation: Scripts performing complex calculations, large table iterations, or expensive string operations exceed the 5-second default.
- Blocking commands inside scripts: Using commands like
BLPOPorWAITinside a Lua script can block indefinitely. - Large data processing: Scripts that iterate over many keys or process large sorted sets, hashes, or lists.
- Concurrent access: Multiple clients attempt to execute commands while a long-running script holds the instance.
The error typically appears in application logs as:
BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
Minimal Working Configuration
The default configuration is usually sufficient for most workloads. To explicitly set the limit:
BASH# Check current value redis-cli CONFIG GET lua-time-limit # Set to 5 seconds (default) redis-cli CONFIG SET lua-time-limit 5000 # Set to 10 seconds redis-cli CONFIG SET lua-time-limit 10000 # Disable the limit (NOT recommended for production) redis-cli CONFIG SET lua-time-limit 0
To make the change permanent, add to your redis.conf:
CONF# redis.conf lua-time-limit 5000
For a minimal test script that demonstrates the timeout behavior:
LUA-- slow_script.lua local i = 0 while true do i = i + 1 if i % 1000000 == 0 then redis.call('SET', 'progress', i) end end
Load and run it:
BASHredis-cli --eval slow_script.lua
After 5 seconds, from another terminal:
BASHredis-cli SCRIPT KILL
Parameters and Environment Variables
| Parameter | Default | Description |
|---|---|---|
lua-time-limit | 5000 | Threshold in milliseconds after which Redis marks a script as slow and accepts SCRIPT KILL. Set to 0 to disable the limit entirely. |
Key behaviors to understand:
- Soft limit: The script continues running after the threshold; Redis only starts accepting kill commands.
- Write detection: If the script has performed any write operation (e.g.,
SET,DEL,LPUSH),SCRIPT KILLwill fail. Redis cannot safely roll back writes, so you must useSHUTDOWN NOSAVE. - No environment variables: This is a runtime configuration, not an environment variable. Set it via
CONFIG SET,redis.conf, or the command-line flag--lua-time-limit.
Root Cause Analysis
The root cause of BUSY Redis is busy running a script is always the same: a Lua script is executing for longer than lua-time-limit milliseconds, and Redis has flagged it as slow.
The underlying mechanics:
- Atomic execution: Redis runs Lua scripts in a single-threaded event loop. While a script executes, no other commands are processed.
- Soft timeout: When the script exceeds
lua-time-limit, Redis sets a flag that allowsSCRIPT KILLto interrupt the script at the next Lua VM instruction boundary. - Write safety: If the script has already modified data, Redis refuses
SCRIPT KILLbecause terminating mid-script could leave partial writes. The only recovery isSHUTDOWN NOSAVE, which discards all unsaved data.
Common root causes in production:
- Unbounded loops: Scripts that iterate until a condition is met without a maximum iteration count.
KEYScommand: Usingredis.call('KEYS', '*')inside a script forces a full keyspace scan, which is O(N) and blocks the instance.- Large collection operations: Iterating over a sorted set with millions of members using
ZRANGEorZSCANinside a loop. - String concatenation in loops: Building large strings with
..in a loop creates O(N²) memory copies. - External calls: Scripts that attempt to perform network I/O or file operations (which Lua supports but Redis discourages).
Common Errors and Fixes
| Error | Cause | Fix |
|---|---|---|
BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE. | Script exceeded lua-time-limit | Run SCRIPT KILL if no writes occurred; otherwise SHUTDOWN NOSAVE (data loss risk) |
ERR Error running script (call to f_...): @user_script:1: Script attempted to access nonexistent global variable '...' | Script references an undefined global variable | Define all variables inside the script or pass them as arguments via KEYS and ARGV |
ERR Error running script (call to f_...): @user_script:1: Wrong number of args calling Redis command From Lua script | Incorrect argument count in redis.call or redis.pcall | Verify the command signature; e.g., SET requires key and value, GET requires one key |
ERR Error running script (call to f_...): @user_script:1: Lua redis() command arguments must be strings or integers | Non-string/integer type passed to redis.call | Convert values with tostring() or tonumber() before passing; never pass Lua tables directly |
Recovery Procedure
BASH# Step 1: Check if the script has written data redis-cli INFO keyspace # Step 2: Try to kill the script (works only if no writes occurred) redis-cli SCRIPT KILL # Step 3: If SCRIPT KILL fails, you must restart with data loss redis-cli SHUTDOWN NOSAVE redis-server /path/to/redis.conf
Production Notes and Security Checks
Monitoring and Alerting
- Enable slowlog:
redis-cli CONFIG SET slowlog-log-slower-than 1000to capture scripts taking over 1 second. - Monitor busy state: Alert on the
BUSYerror appearing in application logs. - Track script execution time: Use
redis-cli --latencyandredis-cli --statto observe instance responsiveness.
Prevention Checklist
- Keep scripts short: Aim for sub-millisecond execution. If a script takes longer, move the logic to the application layer.
- Avoid
KEYS: UseSCANin the application, or pass specific keys viaKEYSarray. - Use
redis.pcall: Wrap commands inredis.pcallto handle errors gracefully instead of aborting the entire script. - Test before deploy: Run
redis-cli --eval script.lua key1 key2 , arg1 arg2in a staging environment. - Set a sane
lua-time-limit: Keep the default 5000ms unless you have a specific reason to change it. Setting it to0disables the safety mechanism entirely. - Restrict network access: Bind Redis to trusted interfaces only (
bind 127.0.0.1or internal IPs) and require authentication (requirepass). - Regular script review: Audit scripts for unbounded loops, large data operations, and blocking commands.
Security Considerations
- Never set
lua-time-limitto0in production: A single infinite loop will permanently block the instance, requiring a restart with data loss. - Limit script complexity: Redis Lua scripts run with full access to the Redis API. A compromised application that can execute arbitrary Lua can read or modify any key.
- Use
EVALwith care: Only pass trusted scripts toEVALorEVALSHA. Never accept script source from user input.
FAQ
Q: What happens if I set lua-time-limit to 0?
A: Setting it to 0 disables the timeout mechanism entirely. Redis will never mark a script as slow and will never accept SCRIPT KILL. An infinite loop will block the instance permanently, and the only recovery is SHUTDOWN NOSAVE followed by a restart. This is not recommended for any environment, especially production.
Q: How do I safely terminate a long-running Lua script without stopping the service?
A: First, try redis-cli SCRIPT KILL. This works only if the script has not performed any write operations. If the script has already written data, SCRIPT KILL will fail with a UNKILLABLE error. In that case, you must use SHUTDOWN NOSAVE, which restarts Redis and discards all unsaved data. To minimize data loss, ensure you have a persistence strategy (RDB snapshots or AOF) with frequent saves.
Q: How can I prevent Lua script timeouts in the first place?
A: Follow these practices: keep scripts minimal and focused on atomic operations; avoid loops that iterate over large collections; use redis.pcall to handle errors gracefully; test scripts with redis-cli --eval and measure execution time; monitor with SLOWLOG to catch slow scripts early; and consider breaking large operations into multiple smaller scripts or using Redis transactions (MULTI/EXEC) instead.