The Problem

Redis Lua scripts provide atomic execution but can fail in multiple ways: syntax errors during loading, runtime errors during execution, or timeout errors that block the server. Understanding which type of failure you're facing determines the resolution path.

Understanding the Error Types

Syntax Errors

Syntax errors occur when loading the script:

bash
ERR Error compiling script (new function): user_script:1: ')' expected near 'then'

Runtime Errors

Runtime errors occur during script execution:

bash
ERR Error running script (call to f_1234567890abcdef): @user_script:3: ERR value is not an integer or out of range

Timeout Errors

Scripts running too long get killed:

bash
BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.

Diagnosis Commands

Check Script Load

Test script syntax without execution:

bash
redis-cli SCRIPT LOAD "local x = 1 return x"

If it returns a SHA1 hash, syntax is valid. If it returns an error, the script has syntax issues.

Debug Mode

Enable debugging to see which line fails:

bash
redis-cli SCRIPT DEBUG yes

Then run your script - Redis will provide detailed line-by-line error information.

Check Running Scripts

bash
redis-cli CLIENT LIST | grep -i lua

This shows any clients currently executing Lua scripts.

Common Script Errors and Fixes

Error 1: Type Mismatch in Operations

lua
-- FAILS: KEYS[1] might not exist or contain wrong type
local val = redis.call('GET', KEYS[1])
return val + 1

Fix: Add type checking:

lua
local val = redis.call('GET', KEYS[1])
if val and tonumber(val) then
    return tonumber(val) + 1
else
    return nil
end

Error 2: Accessing Non-Existent Keys

lua
-- FAILS when key doesn't exist
local list = redis.call('LRANGE', KEYS[1], 0, -1)
return list[1]

Fix: Check key existence:

lua
if redis.call('EXISTS', KEYS[1]) == 1 then
    local list = redis.call('LRANGE', KEYS[1], 0, -1)
    return list[1]
end
return nil

Error 3: Wrong Number of Arguments

bash
ERR wrong number of arguments for 'eval' command

Fix: Ensure you pass the correct format:

bash
# Format: EVAL script numkeys key [key ...] arg [arg ...]
redis-cli EVAL "return KEYS[1]" 1 mykey

The number 1 indicates one key follows.

Error 4: Global Variable Usage

lua
-- FAILS: Redis prohibits global variables
counter = 0
for i = 1, 10 do
    counter = counter + 1
end
return counter

Fix: Use local variables:

lua
local counter = 0
for i = 1, 10 do
    counter = counter + 1
end
return counter

Handling Script Timeouts

Scripts blocking Redis too long trigger the busy error. First, check the timeout setting:

bash
redis-cli CONFIG GET lua-time-limit

Default is 5000 milliseconds.

Resolution for Running Stuck Script

If a script is currently stuck:

bash
# Stop the script (only works if script hasn't written anything)
redis-cli SCRIPT KILL

If SCRIPT KILL fails because the script already wrote data:

bash
# Last resort - this loses all unsaved data
redis-cli SHUTDOWN NOSAVE

Prevention: Optimize Long-Running Scripts

Break complex operations into smaller scripts:

lua
-- Instead of processing 10000 items in one script
-- Process 100 items per script and call multiple times
local start = tonumber(ARGV[1])
local count = 100
local results = {}
for i = start, start + count - 1 do
    local val = redis.call('GET', 'item:' .. i)
    if val then table.insert(results, val) end
end
return results

Debugging Workflow

Step 1: Validate Syntax

bash
redis-cli -p 6379 --eval your_script.lua key1 , arg1

The --eval flag loads and executes, showing errors immediately.

Step 2: Use Redis.LOG for Debugging

Add logging inside your script:

lua
local val = redis.call('GET', KEYS[1])
redis.log(redis.LOG_WARNING, 'Got value: ' .. tostring(val))
return val

Check Redis logs:

bash
tail -f /var/log/redis/redis-server.log

Step 3: Test with Minimal Reproduction

Create a test script that isolates the failing section:

bash
redis-cli EVAL "local v = redis.call('TYPE', KEYS[1]); return v.ok" 1 testkey

Best Practices

  1. 1.Always use KEYS array for key access - Never construct key names from ARGV alone as this breaks cluster compatibility
  2. 2.Keep scripts short - Under 5000 instructions to avoid timeouts
  3. 3.Use script SHA caching - Load once, call many times:

```bash # Load script SHA=$(redis-cli SCRIPT LOAD "$(cat script.lua)")

# Call by SHA redis-cli EVALSHA $SHA 2 key1 key2 arg1 ```

  1. 1.Handle nil values explicitly - Lua nil propagates through operations unexpectedly
  2. 2.Use redis.pcall for non-critical operations - Returns error as table instead of throwing:
lua
local result = redis.pcall('GET', KEYS[1])
if result.err then
    -- handle error
end