This document covers Lua script commands, performance optimization, error handling, and cluster-specific limitations in Tair (Redis OSS-compatible). Lua scripts run atomically on the server, making them well suited for compare-and-swap (CAS) operations and multi-step workflows that would otherwise require multiple round trips.
Lua commands
For full command documentation, see the Redis official website.
| Command | Syntax | Description |
|---|---|---|
EVAL | EVAL script numkeys [key [key ...]] [arg [arg ...]] | Run a Lua script with the specified keys and arguments. Returns the script output. |
EVALSHA | EVALSHA sha1 numkeys key [key ...] arg [arg ...] | Run a cached script by its SHA1 digest. Returns a NOSCRIPT error if the script is not in the cache. |
SCRIPT LOAD | SCRIPT LOAD script | Cache a script and return its SHA1 digest. |
SCRIPT EXISTS | SCRIPT EXISTS script [script ...] | Check whether scripts exist in the cache by SHA1 digest. Returns 1 (exists) or 0 (not found) for each. |
SCRIPT KILL | SCRIPT KILL | Terminate the currently running Lua script. |
SCRIPT FLUSH | SCRIPT FLUSH | Remove all Lua scripts from the script cache. |
EVAL parameters
| Parameter | Description |
|---|---|
script | The Lua script to run. |
numkeys | The number of keys in the KEYS array. Must be a non-negative integer. |
KEYS[] | Keys passed to the script. Indexes start from 1. |
ARGV[] | Additional arguments passed to the script. Indexes start from 1. |
EVALalso caches the script, similar toSCRIPT LOAD.
Incorrect usage ofKEYS[]andARGV[], such as passing keys inARGV[]or hardcoding key names in the script, causes unexpected behavior -- especially in cluster architecture. See Cluster instance constraints.
Always pass values throughKEYS[]andARGV[]instead of encoding them as constants in the script. Hardcoded constants create unique scripts that each consume memory in the Lua interpreter. In a worst-case scenario, this causes an out-of-memory (OOM) error and data loss.
Command examples
Before running these examples, set the test key:
SET foo value_testEVAL
EVAL "return redis.call('GET', KEYS[1])" 1 fooOutput:
"value_test"SCRIPT LOAD
SCRIPT LOAD "return redis.call('GET', KEYS[1])"Output:
"620cd258c2c9c88c9d10db67812ccf663d96bdc6"EVALSHA
EVALSHA 620cd258c2c9c88c9d10db67812ccf663d96bdc6 1 fooOutput:
"value_test"SCRIPT EXISTS
SCRIPT EXISTS 620cd258c2c9c88c9d10db67812ccf663d96bdc6 ffffffffffffffffffffffffffffffffffffffffOutput:
1) (integer) 1
2) (integer) 0SCRIPT FLUSH
This command deletes all cached Lua scripts from the instance. Back up your Lua scripts before running this command.
SCRIPT FLUSHOutput:
OKReduce memory and network overhead
When multiple scripts with duplicate logic are cached separately, they waste memory and can cause an OOM error.
Problem -- hardcoded constants create separate cache entries:
EVAL "return redis.call('set', 'k1', 'v1')" 0
EVAL "return redis.call('set', 'k2', 'v2')" 0Solution 1 -- use KEYS and ARGV so the same script is cached once:
EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 k1 v1
EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 k2 v2Solution 2 -- cache the script once with SCRIPT LOAD, then call it with EVALSHA:
SCRIPT LOAD "return redis.call('set', KEYS[1], ARGV[1])"
# Returns: "55b22c0d0cedf3866879ce7c854970626dcef0c3"
EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k1 v1
EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k2 v2Manage the Lua script cache
The Lua script cache consumes instance memory. When used memory approaches or exceeds the limit, the instance returns an OOM error:
-OOM command not allowed when used memory > 'maxmemory'.To flush the cache, run SCRIPT FLUSH on the client.
| Behavior | Detail |
|---|---|
SCRIPT FLUSH blocking | Synchronous. If many scripts are cached, this command blocks the instance for an extended period. Run it during off-peak hours. |
| Purge Data in the console | Clears data but does not flush the Lua script cache. |
| Data eviction | Does not evict the Lua script cache, regardless of the eviction policy. The default eviction mode is volatile-lru. |
Avoid writing Lua scripts that consume excessive memory or process large datasets. For details on eviction policies, see data eviction.
Handle NOSCRIPT errors
When EVALSHA targets a script that is not in the cache, the instance returns:
(error) NOSCRIPT No matching script. Please use EVAL.Cause: The script was never loaded, or it was cleared during an instance migration, configuration change, version upgrade, or instance switchover. Tair (Redis OSS-compatible) cannot guarantee persistence or replication of cached Lua scripts across these events.
Solution: Run EVAL or SCRIPT LOAD to re-cache the script. Your client should handle the NOSCRIPT error gracefully.
Theredis-pylibrary provides aScriptclass that handlesNOSCRIPTerrors automatically.
The following Python example shows manual NOSCRIPT handling. The script prepends a string to an existing key value:
import redis
import hashlib
# Returns the SHA1 hex digest of a string.
def calcSha1(strin):
sha1_obj = hashlib.sha1()
sha1_obj.update(strin.encode('utf-8'))
sha1_val = sha1_obj.hexdigest()
return sha1_val
class MyRedis(redis.Redis):
def __init__(self, host="localhost", port=6379, password=None, decode_responses=False):
redis.Redis.__init__(self, host=host, port=port, password=password, decode_responses=decode_responses)
def prepend_inLua(self, key, value):
script_content = """\
local suffix = redis.call("get", KEYS[1])
local prefix = ARGV[1]
local new_value = prefix..suffix
return redis.call("set", KEYS[1], new_value)
"""
script_sha1 = calcSha1(script_content)
if self.script_exists(script_sha1)[0] == True: # Check whether the script is already cached.
return self.evalsha(script_sha1, 1, key, value) # If cached, use EVALSHA.
else:
return self.eval(script_content, 1, key, value) # Otherwise, use EVAL (which also caches the script).
r = MyRedis(host="r-******.redis.rds.aliyuncs.com", password="***:***", port=6379, decode_responses=True)
print(r.prepend_inLua("k", "v"))
print(r.get("k"))Handle Lua script timeouts (BUSY error)
Lua scripts run atomically. A slow script blocks the entire instance. After 5 seconds of blocking, the instance returns a BUSY error for all other commands:
BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.Solution: Run SCRIPT KILL to terminate the script, or wait for it to finish.
SCRIPT KILL does not take effect during the first 5 seconds of script execution because the instance is blocked.To prevent extended blocking, estimate execution time before deploying a script, check for infinite loops, and split large scripts if necessary.
Handle UNKILLABLE errors
If a Lua script has already run write commands, SCRIPT KILL fails:
(error) UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.Solution: On the Instances page of the console, find the target instance and click restart.
Script persistence and replication
Tair (Redis OSS-compatible) keeps Lua scripts cached as long as the instance is not restarted and SCRIPT FLUSH is not run. However, Tair cannot guarantee script persistence or synchronization to other nodes during:
Instance migrations
Configuration changes
Version upgrades
Instance switchovers
Solution: Store all Lua scripts on your on-premises device. Re-cache them with EVAL or SCRIPT LOAD as needed. This prevents NOSCRIPT errors after an instance restart or high availability (HA) switchover.
Cluster instance constraints
General constraints
The following constraints apply to all cluster instances:
| Constraint | Detail |
|---|---|
| Single-shard execution | A Lua script runs on a single shard. It cannot be split across shards. The instance routes the script based on the keys specified in the command. |
| Minimum one key | Include at least one key in cluster mode. |
| Same hash slot | All keys in a single Lua script must belong to the same hash slot. |
| Keyless commands | Commands such as KEYS, SCAN, and FLUSHDB run on a single shard and return only that shard's data. |
SCRIPT LOAD scope | Running SCRIPT LOAD on one node does not guarantee that the script is available on other nodes. |
Proxy mode restrictions
The proxy performs its own syntax checks on Lua scripts before forwarding them. These checks are stricter than the Lua interpreter on data nodes.
| Restriction | Detail |
|---|---|
UNPACK | Not supported in proxy mode. |
MULTI/EXEC transactions | EVAL, EVALSHA, and SCRIPT commands are not supported inside MULTI/EXEC transactions. |
Set the script_check_enable parameter to 0 to disable these additional proxy checks. See modify instance parameters.
If the readonly_lua_route_ronode_enable parameter is set to 1 for a read/write splitting instance, the proxy checks whether Lua scripts contain only read-only commands and determines whether to forward them to read-only nodes. This check imposes additional limits on Lua syntax.
Impact of settingscript_check_enableto0:
Redis 5.0 (minor version earlier than 5.0.8) and 4.0 or earlier: Not recommended. The proxy may return normal results even when a script does not run correctly.
Other versions: The proxy stops checking Lua syntax, but data nodes still perform their own checks.
Cluster architecture errors
Error: -ERR for redis cluster, eval/evalsha number of keys can't be negative or zero\r\n
Cause: Lua scripts in cluster architecture must include at least one key so the proxy can determine which shard to use.
# Valid
EVAL "return redis.call('get', KEYS[1])" 1 fooeval
# Invalid -- no key specified
EVAL "return redis.call('get', 'foo')" 0Error: -ERR 'xxx' command keys must in same slot
Cause: All keys must hash to the same slot. Use hash tags (for example, {foo}) to guarantee slot alignment.
# Valid -- {foo} hash tag ensures same slot
EVAL "return redis.call('mget', KEYS[1], KEYS[2])" 2 foo {foo}bar
# Invalid -- foo and foobar may hash to different slots
EVAL "return redis.call('mget', KEYS[1], KEYS[2])" 2 foo foobarProxy syntax check errors
Disable these checks by settingscript_check_enableto0. See modify instance parameters.
Error: -ERR bad lua script for redis cluster, nested redis.call/redis.pcall
Cause: Nested redis.call/redis.pcall calls are not allowed. Use local variables instead.
# Valid
EVAL "local value = redis.call('GET', KEYS[1]); redis.call('SET', KEYS[2], value)" 2 foo bar
# Invalid -- nested call
EVAL "redis.call('SET', KEYS[1], redis.call('GET', KEYS[2]))" 2 foo barError: -ERR bad lua script for redis cluster, first parameter of redis.call/pcall must be a single literal string
Cause: The command name in redis.call/redis.pcall must be a string literal, not a variable.
# Valid
eval "redis.call('GET', KEYS[1])" 1 foo
# Invalid -- command name stored in a variable
eval "local cmd = 'GET'; redis.call(cmd, KEYS[1])" 1 fooRead/write permission errors
| Error | Cause |
|---|---|
-ERR Write commands are not allowed from read-only scripts | Lua scripts sent with EVAL_RO cannot contain write commands. |
-ERR bad write command in no write privilege | Lua scripts sent from a read-only account cannot contain write commands. |
Unsupported command errors
| Error | Cause |
|---|---|
-ERR script debug not support | SCRIPT DEBUG is not supported in proxy mode. |
-ERR bad lua script for redis cluster, redis.call/pcall unkown redis command xxx | The script uses a command not supported in proxy mode. See limits on commands for cluster architecture and read/write splitting instances. |
Lua syntax errors
| Error | Cause |
|---|---|
-ERR bad lua script for redis cluster, redis.call/pcall expect '(' or -ERR bad lua script for redis cluster, redis.call/redis.pcall definition is not complete, expect ')' | Missing parentheses in redis.call. Both ( and ) are required. |
-ERR bad lua script for redis cluster, at least 1 input key is needed for ZUNIONSTORE/ZINTERSTORE | The numkeys parameter of ZUNIONSTORE/ZINTERSTORE must be greater than 0. |
-ERR bad lua script for redis cluster, ZUNIONSTORE/ZINTERSTORE key count < numkeys | The number of keys provided is less than the numkeys value. |
-ERR bad lua script for redis cluster, xread/xreadgroup command syntax error | Incorrect XREAD or XREADGROUP syntax. Check the parameter count. |
-ERR bad lua script for redis cluster, xread/xreadgroup command syntax error, streams must be specified | The STREAMS keyword is required in XREAD and XREADGROUP commands. |
-ERR bad lua script for redis cluster, sort command syntax error | Incorrect SORT command syntax. |
FAQ
Does Data Management Service (DMS) support Lua scripts?
No. The DMS console does not support Lua script commands. Use a Redis client or redis-cli to connect to your instance and run Lua scripts.