Lua scripting in Tair (Redis OSS-compatible)

更新时间:
复制 MD 格式

Lua scripts in Tair (Redis OSS-compatible) run atomically on the server, enabling compare-and-swap (CAS) operations and multi-step workflows that would otherwise require multiple round trips. Covers commands, caching, error handling, and cluster constraints.

Lua commands

Full command documentation is available on 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.
EVAL also caches the script, similar to SCRIPT LOAD.
Misusing KEYS[] and ARGV[] — such as passing keys in ARGV[] or hardcoding key names — causes unexpected behavior, particularly under Cluster instance constraints.
Always pass values through KEYS[] and ARGV[]. Hardcoded constants create unique cached scripts that each consume memory in the Lua interpreter, potentially causing OOM errors and data loss.

Command examples

Before running these examples, set the test key:

SET foo value_test

EVAL

EVAL "return redis.call('GET', KEYS[1])" 1 foo

Output:

"value_test"

SCRIPT LOAD

SCRIPT LOAD "return redis.call('GET', KEYS[1])"

Output:

"620cd258c2c9c88c9d10db67812ccf663d96bdc6"

EVALSHA

EVALSHA 620cd258c2c9c88c9d10db67812ccf663d96bdc6 1 foo

Output:

"value_test"

SCRIPT EXISTS

SCRIPT EXISTS 620cd258c2c9c88c9d10db67812ccf663d96bdc6 ffffffffffffffffffffffffffffffffffffffff

Output:

1) (integer) 1
2) (integer) 0

SCRIPT FLUSH

Warning

This command deletes all cached Lua scripts. Back up your scripts before running it.

SCRIPT FLUSH

Output:

OK

Reduce 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')" 0

Solution 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 v2

Solution 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 v2

Manage 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 Lua scripts that consume excessive memory or process large datasets. Manage cache growth with data eviction policies.

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 the cache was cleared during a migration, configuration change, version upgrade, or switchover. Tair does not guarantee persistence or replication of cached scripts across these events.

Solution: Run EVAL or SCRIPT LOAD to re-cache the script. Your client should handle the NOSCRIPT error gracefully.

The redis-py library provides a Script class that handles NOSCRIPT errors 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 keeps Lua scripts cached unless the instance restarts or SCRIPT FLUSH is run. Script persistence and cross-node synchronization are not guaranteed during:

  • Instance migrations

  • Configuration changes

  • Version upgrades

  • Instance switchovers

Solution: Store all Lua scripts locally. Re-cache them with EVAL or SCRIPT LOAD after any restart or HA switchover to prevent NOSCRIPT errors.

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 stricter syntax checks on Lua scripts 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 script_check_enable to 0 to disable these proxy checks. Modify instance parameters.

When readonly_lua_route_ronode_enable is set to 1 on a read/write splitting instance, the proxy inspects Lua scripts for read-only commands to route them to read-only nodes. This imposes additional syntax limits.

Impact of setting script_check_enable to 0:
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 fails.
Other versions: The proxy stops checking Lua syntax; 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')" 0

Error: -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 foobar

Proxy syntax check errors

Disable these checks by setting script_check_enable to 0. 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 bar

Error: -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 foo

Version-specific errors

The following errors apply only to:

  • Redis Open-Source Edition 5.0 (minor version earlier than 5.0.8) or 4.0 and earlier

  • Cloud-native edition proxy versions earlier than 7.0.2

  • Classic edition proxy versions earlier than 6.8.12

If you run a version higher than those listed above but still encounter these errors, modify any proxy parameter (such as query_cache_expire) and wait 1 minute before retrying.

Error: -ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS array\r\n

Cause: All keys in redis.call/redis.pcall must come from the KEYS array. Lua variables, string literals, and ARGV are not allowed as key references.

# Valid
EVAL "return redis.call('mget', KEYS[1], KEYS[2])" 2 foo {foo}bar

# Invalid -- key as string literal
EVAL "return redis.call('mget', KEYS[1], '{foo}bar')" 1 foo

# Invalid -- variable index for KEYS (not allowed in proxy mode; allowed in direct connection mode)
EVAL "local i = 2 return redis.call('mget', KEYS[1], KEYS[i])" 2 foo {foo}bar

# Invalid -- ARGV used as a key
EVAL "return redis.call('mget', KEYS[1], ARGV[1])" 1 foo {foo}bar

The following table lists additional version-specific errors related to specific commands:

Error Cause
-ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS array, include destination, and KEYS should not be in expression The destination parameter of ZUNIONSTORE and ZINTERSTORE must be specified through the KEYS array.
-ERR bad lua script for redis cluster, ZUNIONSTORE/ZINTERSTORE numkeys parameter should be a single number and not expression The numkeys parameter of ZUNIONSTORE/ZINTERSTORE must be a numeric constant, not an expression.
-ERR bad lua script for redis cluster, ZUNIONSTORE/ZINTERSTORE numkeys value is not an integer or out of range The numkeys parameter of ZUNIONSTORE/ZINTERSTORE is not a valid integer.
-ERR bad lua script for redis cluster, ZUNIONSTORE/ZINTERSTORE all the keys that the script uses should be passed using the KEYS array All keys in ZUNIONSTORE/ZINTERSTORE must come from the KEYS array.
-ERR bad lua script for redis cluster, XREAD/XREADGROUP all the keys that the script uses should be passed using the KEYS array All keys in XREAD/XREADGROUP must come from the KEYS array.
-ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS array, and KEYS should not be in expression, sort command store key does not meet the requirements All keys in SORT commands must come from the KEYS array.

Read/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. DMS does not support Lua commands. Use a Redis client or redis-cli instead.