The Quick Fix
Redis throws a NOSCRIPT error when it cannot find a Lua script that matches the SHA1 hash you provided via EVALSHA. Since Redis stores scripts in RAM, any disruption to the server can wipe this cache. The solution is to catch the error in your application and fall back to the full EVAL command.
# The logical flow for a robust fix
try:
return redis.evalsha(script_sha1, keys, args)
catch error "NOSCRIPT":
# The script is missing from cache; send the full source
# This also re-caches the script for next time
return redis.eval(script_content, keys, args)
The Root Cause: Why Scripts Go Missing
Redis optimizes performance by caching scripts. Imagine you have a 10KB Lua script. Sending that over the wire 1,000 times per second would waste 10MB of bandwidth every second. Redis avoids this by letting you send the script once (getting a 40-character SHA1 hash) and then using EVALSHA <SHA1> for all future calls.
However, this cache is volatile. The NOSCRIPT error typically triggers in these four scenarios:
- **Server Restarts:** Redis does not persist the script cache to the RDB or AOF files. A restart wipes the memory clean.
- **Cluster Sharding:** This is a common pitfall. Each node in a Redis Cluster maintains its own script cache. If your client connects to a new master node that hasn't seen the script, it will fail.
- **Manual Flushes:** An administrator or a maintenance cron job might have executed `SCRIPT FLUSH`, clearing all cached logic.
- **Memory Pressure:** If Redis hits its `maxmemory` limit, it may evict data. While script memory is handled separately, extreme instability can lead to unpredictable cache behavior.
How to Solve It Permanently
1. The "Try-Catch-Reload" Pattern (Best Practice)
Do not assume the script is always there. Instead, design your client to handle its absence gracefully. Here is an implementation using Node.js (ioredis):
const script = "return redis.call('get', KEYS[1])";
const sha1 = "d0c0316d2d385f76263e699b664d50c76ca4c01d";
try {
await redis.evalsha(sha1, 1, "user:session");
} catch (err) {
if (err.message.includes("NOSCRIPT")) {
// Fallback to EVAL. This executes AND re-caches the script.
await redis.eval(script, 1, "user:session");
} else {
throw err;
}
}
2. Pre-loading During Startup
If your application relies on core scripts, load them during the bootstrap phase. This ensures the cache is warm before the first user request hits.
# Manually load a script via CLI
redis-cli SCRIPT LOAD "return redis.call('get', KEYS[1])"
# Output: "d0c0316d2d385f76263e699b664d50c76ca4c01d"
In your production code, iterate through your .lua files and run SCRIPT LOAD on each. Keep the Try-Catch pattern in place regardless, as it acts as a vital safety net if the server restarts mid-session.
3. Handling Redis Cluster
In a cluster, SCRIPT LOAD on one node does not sync to others. Most high-level libraries (like redis-py or ioredis) provide "Script" objects that automatically handle multi-node loading. Use these abstractions instead of raw commands to simplify cluster management.
Verification: Checking the Cache
You can verify if a script is currently cached without actually running it. Use the SCRIPT EXISTS command with your SHA1 hash:
# Check for your specific SHA1
redis-cli SCRIPT EXISTS d0c0316d2d385f76263e699b664d50c76ca4c01d
# Results:
# 1) (integer) 1 <-- Cached and ready
# 1) (integer) 0 <-- Missing; will cause NOSCRIPT
Pro-Tips and Prevention
A single hidden space or an extra newline at the end of your script will completely change the SHA1 hash. This often leads to frustrating debugging sessions where the code looks identical but the hashes don't match. I always use a consistent formatting tool for Lua blocks to prevent this.
I personally use the Hash Generator on ToolCraft to verify my script hashes. It runs entirely in your browser. This means your logic and keys never leave your machine, keeping your internal code secure while you debug.
Further Reading
- [Official Redis Lua API Guide](https://redis.io/docs/manual/programmability/lua-api/)
- [EVALSHA Command Reference](https://redis.io/commands/evalsha/)
- [SCRIPT LOAD Documentation](https://redis.io/commands/script-load/)

