Tair (Redis OSS-compatible) instances support Lua commands. You can use Lua scripts to efficiently handle compare-and-set (CAS) operations, improve instance performance, and implement patterns that are otherwise difficult or inefficient to achieve. This topic describes the basic syntax and usage guidelines for Lua scripts.
Basic syntax
Performance optimization
Optimize memory and network overhead
Caching a large number of functionally repetitive scripts consumes significant memory and can even trigger an out of memory (OOM) error. The following is an example of incorrect usage:
EVAL "return redis.call('set', 'k1', 'v1')" 0
EVAL "return redis.call('set', 'k2', 'v2')" 0Solution:
To reduce memory waste, avoid hardcoding arguments as constants within the Lua script.
# Achieves the same functionality as the incorrect example but only caches the script once. EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 k1 v1 EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 k2 v2For greater efficiency, we recommend the following approach to reduce both memory usage and network overhead.
SCRIPT LOAD "return redis.call('set', KEYS[1], ARGV[1])" # After execution, Redis returns "55b22c0d0cedf3866879ce7c854970626dcef0c3". EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k1 v1 EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k2 v2
Clear Lua script memory
The Lua script cache consumes instance memory, contributing to the used_memory metric. If the instance's memory usage approaches or exceeds maxmemory, it can trigger an out of memory (OOM) error. An example error is shown below:
-OOM command not allowed when used memory > 'maxmemory'.Solution:
Execute the SCRIPT FLUSH command from a client to clear the Lua script cache. The recommended method depends on your instance version:
Asynchronous cleanup (Recommended): open source Redis 7.0 or later and Tair (Enterprise Edition) 6.0 or later support the
SCRIPT FLUSH ASYNCcommand. This command clears the Lua script cache in the background without blocking the instance, minimizing the impact on your services.SCRIPT FLUSH ASYNCSynchronous cleanup: For earlier versions, the
SCRIPT FLUSHcommand operates synchronously. If the instance has a large number of cached Lua scripts, this command can block the instance for an extended period and may make it unavailable. Use this command with caution, preferably during off-peak hours.SCRIPT FLUSH
Clicking Purge Data in the console only clears data and does not clear the Lua script cache.
In addition, avoid writing excessively large Lua scripts to prevent high memory consumption. Do not perform bulk data writes within a Lua script, as this can cause a sharp increase in memory usage and may lead to an OOM error. If your business logic allows, we recommend enabling data eviction (enabled by default with the volatile-lru policy) to save memory space. However, the instance does not evict the Lua script cache, regardless of whether data eviction is enabled.
Error handling
NOSCRIPT error
When you use the EVALSHA command, if the script corresponding to the sha1 value is not cached on the instance, the instance returns a NOSCRIPT error. An example is shown below:
(error) NOSCRIPT No matching script. Please use EVAL.Solution:
Use the EVAL or SCRIPT LOAD command to cache the script on the instance, and then retry. However, the instance does not guarantee the persistence or replication of Lua scripts. In some scenarios, such as an instance migration or a configuration change, the Lua script cache may be cleared. Therefore, your client application must be able to handle this error. For more information, see Persistence and replication issues.
The following Python demo shows an example of how to handle a NOSCRIPT error. This demo uses a Lua script to perform a string prepend operation.
You can also use the redis-py library for Python to handle this type of error. The library provides a Script class that encapsulates low-level logic for Redis Lua scripting, such as catching NOSCRIPT errors.
import redis
import hashlib
# This function takes a Lua script as a string and returns its SHA1 digest.
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]: # Check if the script is cached in Redis.
return self.evalsha(script_sha1, 1, key, value) # If cached, run the script with EVALSHA.
else:
return self.eval(script_content, 1, key, value) # Otherwise, run the script with EVAL, which also caches it. Alternatively, you could use SCRIPT LOAD and then EVALSHA.
r = MyRedis(host="r-******.redis.rds.aliyuncs.com", password="***:***", port=6379, decode_responses=True)
print(r.prepend_inLua("k", "v"))
print(r.get("k"))
Lua script timeout errors
Because Lua scripts execute atomically, a slow-running script can block the instance. If a script runs for more than 5 seconds, the instance returns a
BUSYerror for all other commands until the script finishes executing.BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.Solution:
You can use the
SCRIPT KILLcommand to terminate the Lua script or wait for it to complete.NoteThe
SCRIPT KILLcommand does not take effect during the first 5 seconds of a slow script's execution because the instance is blocked.When writing Lua scripts, estimate their execution time and check for issues like infinite loops. This helps prevent long-running scripts from blocking the instance and making the service unavailable. If necessary, break down long scripts into smaller ones.
If the current Lua script has already executed a write command, the
SCRIPT KILLcommand will not work. An example error is shown below:(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:
In the console, go to the Instances page and click restart for the corresponding instance.
Persistence and replication issues
The instance caches executed Lua scripts indefinitely unless it is restarted or the SCRIPT FLUSH command is called. However, in certain situations, such as an instance migration, a configuration change, a version upgrade, or an HA switchover, the instance does not guarantee the persistence of Lua scripts or their replication to other nodes.
Solution:
Because the instance does not guarantee script persistence or replication, you should store all Lua scripts locally. When necessary, use the EVAL or SCRIPT LOAD command to recache the scripts on the instance. This prevents NOSCRIPT errors that can occur if the script cache is cleared during operations like an instance restart or an HA switchover.
Special restrictions in cluster architecture
Cluster architecture constraints
To ensure atomicity, a Lua script cannot be split and must be executed on a single shard in a cluster architecture. The instance typically uses a key to determine which shard to route the command to. Therefore, you must specify at least one key when you execute a Lua script in a cluster. If a script accesses multiple keys, they must all belong to the same slot; otherwise, the script will fail or return incorrect results. Commands without keys, such as
KEYS,SCAN, andFLUSHDB, can run but will only return data from a single shard. These limitations are inherent to the Redis Cluster architecture.Running the
SCRIPT LOADcommand on a single node does not guarantee that the script is stored on other nodes.
Error codes in proxy mode
The proxy performs syntax checks to proactively identify cases where keys span multiple slots. This helps detect issues early for easier troubleshooting. The proxy's checking method differs from that of the Lua virtual machine, resulting in additional restrictions when executing Lua commands in proxy mode. For example, the UNPACK command is not supported, and EVAL, EVALSHA, and SCRIPT-series commands cannot be used within MULTI/EXEC transactions.
You can also disable some of the proxy's Lua syntax checks by turning off the script_check_enable parameter.
Additionally, for a read/write splitting instance, if the readonly_lua_route_ronode_enable parameter is enabled, the proxy checks if a Lua script contains only read-only commands to determine whether it can be forwarded to a read-only node. This check logic imposes limitations on Lua syntax.
Disabling the script_check_enable parameter has the following effects:
For instances of open source Redis 5.0 (minor versions earlier than 5.0.8) or Redis 4.0 and earlier, we do not recommend disabling this parameter. Doing so may cause a script to return a success message even when it executes incorrectly.
For other versions, disabling this parameter stops the proxy from checking Lua syntax, but the data nodes will still perform their normal syntax checks.
The following are the specific error codes and their causes.
Redis Cluster architecture limitations
Error code:
-ERR for redis cluster, eval/evalsha number of keys can't be negative or zero\r\nDescription: You must specify at least one key when executing a Lua script. The proxy uses the key to determine which shard to forward the script to.
# Correct example EVAL "return redis.call('get', KEYS[1])" 1 fooeval # Incorrect example EVAL "return redis.call('get', 'foo')" 0Error code:
-ERR 'xxx' command keys must in same slotDescription: All keys in a Lua script must belong to the same slot.
# Correct example: EVAL "return redis.call('mget', KEYS[1], KEYS[2])" 2 foo {foo}bar # Incorrect example: EVAL "return redis.call('mget', KEYS[1], KEYS[2])" 2 foo foobar
Proxy Lua syntax restrictions
You can avoid the proxy's additional Lua syntax checks by disabling the script_check_enable parameter.
Error code:
-ERR bad lua script for redis cluster, nested redis.call/redis.pcallDescription: Nested Redis calls are not supported. Use local variables as an alternative.
# Correct example EVAL "local value = redis.call('GET', KEYS[1]); redis.call('SET', KEYS[2], value)" 2 foo bar # Incorrect example EVAL "redis.call('SET', KEYS[1], redis.call('GET', KEYS[2]))" 2 foo barError code:
-ERR bad lua script for redis cluster, first parameter of redis.call/redis.pcall must be a single literal stringDescription: The command invoked within redis.call/pcall must be a string literal.
# Correct example eval "redis.call('GET', KEYS[1])" 1 foo # Incorrect example eval "local cmd = 'GET'; redis.call(cmd, KEYS[1])" 1 foo
Read/write permission issues
Error code:
-ERR Write commands are not allowed from read-only scriptsDescription: Lua scripts sent with the
EVAL_ROcommand cannot contain write commands.Error code:
-ERR bad write command in no write privilegeDescription: Lua scripts sent from a read-only account cannot contain write commands.
Unsupported commands
Error code:
-ERR script debug not supportDescription: The proxy does not currently support the
SCRIPT DEBUGcommand.Error code:
-ERR bad lua script for redis cluster, redis.call/pcall unkown redis command xxxDescription: The Lua script contains a command that is not supported by the proxy. For more information, see Command restrictions on cluster and read/write splitting instances.
Lua syntax errors
Error code:
-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 ')'Note: This is a Lua syntax error because the
redis.callfunction must be followed by a complete pair of parentheses:(and).Error code:
-ERR bad lua script for redis cluster, at least 1 input key is needed for ZUNIONSTORE/ZINTERSTOREDescription: The
numkeysparameter of theZUNIONSTOREandZINTERSTOREcommands must be greater than 0.Error code:
-ERR bad lua script for redis cluster, ZUNIONSTORE/ZINTERSTORE key count < numkeysDescription: The actual number of keys for the
ZUNIONSTOREorZINTERSTOREcommand is less than the specifiednumkeysvalue.Error code:
-ERR bad lua script for redis cluster, xread/xreadgroup command syntax errorDescription: The syntax of the
XREADorXREADGROUPcommand is incorrect. Check the number of parameters.Error code:
-ERR bad lua script for redis cluster, xread/xreadgroup command syntax error, streams must be specifiedDescription: The
XREADandXREADGROUPcommands must include thestreamsparameter.Error code:
-ERR bad lua script for redis cluster, sort command syntax errorDescription: The syntax of the
SORTcommand is incorrect.
FAQ
Q: Can I execute Lua scripts in Data Management (DMS)?
A: The Data Management (DMS) console does not currently support Lua-related commands. To use Lua scripts, connect to your instance using a client or redis-cli.