What it means
EVAL sends a Lua script’s source with every call. To save sending it each time, Redis keeps every
script it has seen in a cache, keyed by the SHA1 of its source, and EVALSHA <sha1> runs a script
from that cache. If the cache doesn’t hold that SHA1, the server replies:
(error) NOSCRIPT No matching script. Please use EVAL.
Nothing ran. Valkey says NOSCRIPT No matching script., without the second sentence.
The script cache isn’t data: Redis doesn’t save it to disk and doesn’t send it to replicas. So a SHA1 that worked a minute ago stops working when:
- the server restarts, including a managed service’s maintenance or a container being recreated;
- a replica takes over in a failover:
SCRIPT LOADon the primary doesn’t reach its replicas; - the command goes to another cluster node: each node has its own cache, and a cluster client
sends
EVALSHAto the node that owns the script’s keys; - someone runs
SCRIPT FLUSH, which empties the cache.
In a transaction, EVALSHA is queued without being checked, so the error comes back in EXEC’s
reply as one of the results, and the other queued commands still run.
Common causes
- Scripts loaded once, at application start-up, then a restart or failover of the server while the application kept running.
- A Redis Cluster: the script was loaded on one node and called with keys on another.
EVALSHAin a pipeline or transaction, where the client can’t catch the error and retry.- A hard-coded SHA1 copied from another server or an older version of the script. Changing one character of the script changes its SHA1.
How to fix it
Fall back to EVAL, then use EVALSHA again
The usual pattern is: call EVALSHA; on NOSCRIPT, call EVAL with the full source (which also
caches it) or SCRIPT LOAD it and retry. Most client libraries have a script helper that does
this for you; use it rather than calling EVALSHA directly.
SCRIPT EXISTS <sha1> # 1 if cached here, 0 if not
SCRIPT LOAD "<source>" # caches it, returns the SHA1
In a pipeline or transaction, send EVAL
A NOSCRIPT inside a pipeline or MULTI can’t be retried in place. Send EVAL with the source
there, or run SCRIPT LOAD on that connection first.
In a cluster, load the script on every primary
Run SCRIPT LOAD on each primary (redis-cli --cluster call <host>:<port> SCRIPT LOAD "<source>"
sends a command to every node), and again after a failover. Or rely on the EVAL fallback above,
which caches the script on whichever node runs it.
Or use a function instead
Redis 7.0 added functions: FUNCTION LOAD stores a library of Lua functions that is saved with the
data, copied to replicas, and called by name with FCALL, so there’s no SHA1 to go missing. Valkey
has functions too.
FUNCTION LOAD "#!lua name=mylib
redis.register_function('hello', function(keys, args) return 'hi' end)"
FCALL hello 0
A function that only reads, registered with flags={'no-writes'}, can run on a replica with
FCALL_RO.
Reproduce it
On the Redis 8.10.2 and Valkey 8.1.10 test servers, a SHA1 that was never loaded:
EVALSHA ffffffffffffffffffffffffffffffffffffffff 0
(error) NOSCRIPT No matching script. Please use EVAL.
Valkey replied (error) NOSCRIPT No matching script. Both answered SCRIPT EXISTS with
1) (integer) 0. In a transaction on Redis, the error came back from EXEC, and the commands around it ran:
MULTI
OK
SET seo:err:a 1
QUEUED
EVALSHA ffffffffffffffffffffffffffffffffffffffff 0
QUEUED
INCR seo:err:a
QUEUED
EXEC
1) OK
2) (error) NOSCRIPT No matching script. Please use EVAL.
3) (integer) 2
A restart. On a temporary Redis 8.10.2 container, SCRIPT LOAD "return 1" returned
e0e1f9fabfc9d4800c877a703b823ac0578ff8db and EVALSHA of it returned (integer) 1. After
docker restart, the same EVALSHA got NOSCRIPT No matching script. Please use EVAL.
A replica. With a second temporary container replicating the first (master_link_status:up),
the script loaded on the primary wasn’t on the replica: SCRIPT EXISTS replied 1) (integer) 0
and EVALSHA got NOSCRIPT. A function loaded on the primary with FUNCTION LOAD was on the
replica a second later (FCALL_RO hello 0 returned "hi" there), and still on the primary after a
restart.
A cluster. A temporary three-node Redis 8.10.2 cluster (slots 0–5460 on port 6381, 5461–10922 on 6382, 10923–16383 on 6383). The script was loaded on 6381 only:
$ redis-cli -p 6381 SCRIPT LOAD "return redis.call('GET', KEYS[1])"
d3c21d0c2b9ca22f82737626a27bcaf5d288f99f
Then, in one redis-cli -c -p 6381 session, SET bar 1 and the script on bar and on user:1:
OK
"1"
-> Redirected to slot [10778] located at 127.0.0.1:6382
(error) NOSCRIPT No matching script. Please use EVAL.
bar is in slot 5061, on 6381; user:1 is in slot 10778, on 6382, which had never seen the script.
Loading it on every node fixed it; the EVALSHA on user:1 then replied (nil) (no such key):
$ redis-cli --cluster call 127.0.0.1:6381 SCRIPT LOAD "return redis.call('GET', KEYS[1])"
>>> Calling SCRIPT LOAD return redis.call('GET', KEYS[1])
127.0.0.1:6381: d3c21d0c2b9ca22f82737626a27bcaf5d288f99f
127.0.0.1:6382: d3c21d0c2b9ca22f82737626a27bcaf5d288f99f
127.0.0.1:6383: d3c21d0c2b9ca22f82737626a27bcaf5d288f99f
In Inlet
Query tabs take redis-cli commands, so SCRIPT EXISTS, SCRIPT LOAD, EVAL, EVALSHA and
FCALL run there; Stop kills a script that runs too long. On a cluster (Pro), Inlet sends each
command to the node that holds its keys, so a script loaded through one node may be missing on
another, as above. When the server replies NOSCRIPT, Inlet shows its message and links to this
page.