Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Redis By James Joyner IV · · 8 min read

Redis Error Guide: 'BUSY Redis is busy running a script' — Blocked by a Long Lua Script

Fix 'BUSY Redis is busy running a script' errors: lua-time-limit, SCRIPT KILL vs SHUTDOWN NOSAVE, looping Lua, and writing non-blocking scripts.

  • #redis
  • #troubleshooting
  • #errors
  • #lua

Overview

BUSY Redis is busy running a script is returned to other clients while a Lua script (or FUNCTION) has been running longer than lua-time-limit (default 5000 ms). Redis is single-threaded for command execution, so a script that runs long blocks every other command. Once the script passes lua-time-limit, Redis starts replying BUSY to incoming commands so operators can intervene — but the script itself keeps running until it finishes or is killed.

The literal error:

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

Crucially, SCRIPT KILL only works if the script has not yet performed a write. If it has already written data, killing it would leave the dataset half-modified, so Redis refuses SCRIPT KILL and your only option is SHUTDOWN NOSAVE (which drops the un-persisted writes). This makes long write-heavy scripts genuinely dangerous.

Symptoms

  • Most clients suddenly get BUSY Redis is busy running a script for several seconds or longer.
  • INFO stats / latency dashboards show a hard stall; instantaneous_ops_per_sec drops to near zero.
  • One EVAL/EVALSHA/FCALL call is stuck; everything else queues behind it.
  • After the incident, SLOWLOG shows a very slow EVAL.
redis-cli PING
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.

Common Root Causes

1. A script iterating over a huge dataset

Lua that loops over KEYS, a big redis.call('KEYS', ...), or a large set with per-element work easily exceeds 5 s.

2. An infinite or unbounded loop in Lua

A while with a bad exit condition, or recursion, never returns — permanently BUSY until killed.

3. lua-time-limit set too low (or expectations too high)

lua-time-limit is a warning threshold, not a kill switch. A legitimately heavy but bounded script trips BUSY even though it will finish.

redis-cli CONFIG GET lua-time-limit
# redis.conf
lua-time-limit 5000

4. Blocking work inside a script

Scripts must be fast and deterministic. Heavy cjson decoding of large blobs, big SORT, or ZRANGEBYSCORE over millions of members inside one EVAL stalls the server.

Diagnostic Workflow

Step 1: Confirm a script is the blocker

redis-cli INFO stats | grep instantaneous_ops_per_sec
redis-cli --latency-history -i 1   # from a separate connection

If ops drop to ~0 and you get BUSY on PING, a script is running.

Step 2: Decide if it is safe to kill

# If the script has NOT written yet, this works:
redis-cli SCRIPT KILL
OK

If it returns UNKILLABLE Sorry the script already executed write commands against the dataset, the script has written and cannot be safely killed.

Step 3: Last resort for a write-heavy stuck script

# Drops writes made since the last save — DATA LOSS of un-persisted changes
redis-cli SHUTDOWN NOSAVE

Only do this knowingly; on a replica-backed setup, failover first if possible.

Step 4: After recovery, find the offending script

redis-cli SLOWLOG GET 10
redis-cli SCRIPT EXISTS <sha>    # confirm which cached script ran
1) 1) (integer) 214
   2) (integer) 1719950400
   3) (integer) 8123456        # microseconds ~ 8.1s
   4) 1) "EVALSHA"
      2) "a1b2c3..."

Example Root Cause Analysis

A rate-limiter deploy shipped an EVAL that, under high cardinality, iterated every member of a large sorted set to expire stale entries. Under load one call ran ~30 s and clients flooded with BUSY.

redis-cli SCRIPT KILL
(error) UNKILLABLE Sorry the script already executed write commands against the dataset.

Because the script had already called ZREM, SCRIPT KILL was refused. The node had a replica, so ops promoted the replica and SHUTDOWN NOSAVE the wedged primary rather than lose the whole cluster’s availability.

The real fix was rewriting the script to be bounded — process at most N elements per call using ZRANGEBYSCORE ... LIMIT 0 100 and let repeated invocations catch up, instead of scanning the entire set in one pass:

-- bounded: never touches more than 100 members per EVAL
local stale = redis.call('ZRANGEBYSCORE', KEYS[1], 0, ARGV[1], 'LIMIT', 0, 100)

After the change, no single EVAL exceeded a few milliseconds and BUSY never recurred.

Prevention Best Practices

  • Keep Lua scripts short, bounded, and deterministic — process a fixed batch per call, never the whole dataset.
  • Never do unbounded loops or large KEYS/SORT/cjson work inside a script; page the work across invocations.
  • Treat lua-time-limit as a warning, not a safety net — scripts should finish in single-digit milliseconds.
  • Run write-heavy maintenance from a client with SCAN, not a single monster EVAL.
  • Always have a replica so you can fail over instead of SHUTDOWN NOSAVE on a wedged primary.
  • Review SLOWLOG for slow EVAL/EVALSHA after any script deploy; see more Redis error guides.

Quick Command Reference

# Confirm a script is blocking
redis-cli INFO stats | grep instantaneous_ops_per_sec
redis-cli --latency-history -i 1

# Kill it (only if no writes yet)
redis-cli SCRIPT KILL

# Last resort for a write-heavy stuck script (data loss)
redis-cli SHUTDOWN NOSAVE

# Post-incident forensics
redis-cli CONFIG GET lua-time-limit
redis-cli SLOWLOG GET 10

Conclusion

BUSY Redis is busy running a script means a Lua script has exceeded lua-time-limit and is blocking the single command thread. Your options are narrow by design:

  1. If the script has not written, SCRIPT KILL stops it cleanly.
  2. If it has already written, only SHUTDOWN NOSAVE clears it — at the cost of un-persisted data.
  3. Root causes are almost always unbounded loops or scripts that iterate huge datasets in one call.

The durable fix is writing scripts that touch a bounded batch per invocation and finish in milliseconds. Keep a replica so you can fail over instead of hard-killing a primary, and watch SLOWLOG after every script change.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.