All Products
Search
Document Center

Tair (Redis® OSS-Compatible):Lua script syntax and solutions to common errors

Last Updated:Dec 18, 2024

Tair (Redis OSS-compatible) instances support Lua commands. Lua scripts can be used to efficiently process compare-and-set (CAS) commands. This improves the performance of Tair (Redis OSS-compatible) and simplifies the implementation of features. This topic describes the syntax and usage of Lua scripts.

Precautions

Commands related to Lua scripts cannot be used in the Data Management (DMS) console. For more information about DMS, see Overview. You can use a client or redis-cli to connect to instances and use Lua scripts.

Syntax

Command

Syntax

Description

EVAL

EVAL script numkeys [key [key ...]] [arg [arg ...]]

Executes a specific script that includes parameters and returns the output.

Parameters:

  • script: the Lua script.

  • numkeys: the number of arguments in the KEYS array. The number is an non-negative integer.

  • KEYS[]: the keys that you want to pass to the script as arguments.

  • ARGV[]: the additional arguments that you want to pass to the script. The indexes of the KEYS[] and ARGV[] parameters start from 1.

Note
  • The EVAL command loads a script into the script cache in a similar way as the SCRIPT LOAD command.

  • Mixed use or misuse of the KEYS[] and ARGV[] parameters may cause instances, especially cluster instances, to run abnormally. For more information, see Limits on Lua scripts in cluster instances.

  • We recommend that you pass values in the KEYS[] and ARGV[] parameters to call Lua scripts, instead of encoding parameters into Lua scripts. Otherwise, the memory usage of the Lua virtual machine increases and cannot be reduced at the earliest opportunity. In a worst-case scenario, an out of memory (OOM) error occurs on the instance and results in data loss.

EVALSHA

EVALSHA sha1 numkeys key [key ...] arg [arg ...]

Evaluates a cached script by its SHA1 digest and runs the script.

If the script is not cached in Tair (Redis OSS-compatible) when you use the EVALSHA command, Tair (Redis OSS-compatible) returns the NOSCRIPT error. Cache the script in Tair (Redis OSS-compatible) by using the EVAL or SCRIPT LOAD command and try again. For more information, see Handle the NOSCRIPT error.

SCRIPT LOAD

SCRIPT LOAD script

Caches a specified script and returns the SHA1 digest of the script.

SCRIPT EXISTS

SCRIPT EXISTS script [script ...]

Returns information about the existence of one or more scripts in the script cache by using their corresponding SHA1 digests. If a specific script exists, a value of 1 is returned. Otherwise, a value of 0 is returned.

SCRIPT KILL

SCRIPT KILL

Terminates a running Lua script.

SCRIPT FLUSH

SCRIPT FLUSH

Removes all Lua scripts from the script cache in the current instance.

For more information about the commands, visit the Redis official website.

The following sample code provides examples of specific Redis commands. Before the following commands are run, the SET foo value_test command is run.

  • Sample EVAL command:

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

    Sample output:

    "value_test"
  • Sample SCRIPT LOAD command:

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

    Sample output:

    "620cd258c2c9c88c9d10db67812ccf663d96bdc6"
  • Sample EVALSHA command:

    EVALSHA 620cd258c2c9c88c9d10db67812ccf663d96bdc6 1 foo

    Sample output:

    "value_test"
  • Sample SCRIPT EXISTS command:

    SCRIPT EXISTS 620cd258c2c9c88c9d10db67812ccf663d96bdc6 ffffffffffffffffffffffffffffffffffffffff

    Sample output:

    1) (integer) 1
    2) (integer) 0
  • Sample SCRIPT FLUSH command:

    Warning

    This command deletes all cached Lua scripts from the instance. Make sure that you back up the Lua scripts before you run this command.

    SCRIPT FLUSH

    Sample output:

    OK

Reduce memory and network overheads

Problem description:

A large number of scripts that serve the same purposes are cached in the instance. These scripts take up large amounts of memory and may cause an OOM error. Example of invalid usage:

EVAL "return redis.call('set', 'k1', 'v1')" 0
EVAL "return redis.call('set', 'k2', 'v2')" 0

Solution:

  • Do not pass parameters to Lua scripts as constants to reduce memory usage.

    # The following commands serve the same purposes as the preceding sample commands but cache scripts only 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
  • Use the following command syntax to reduce memory and network overheads:

    SCRIPT LOAD "return redis.call('set', KEYS[1], ARGV[1])"    # After this command is run, the following output is returned: "55b22c0d0cedf3866879ce7c854970626dcef0c3".
    EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k1 v1
    EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k2 v2

Flush the Lua script cache

Problem description:

Used memory of the instance may be higher than expected because the Lua script cache takes up memory of the instance. When the used memory of the instance approaches or exceeds the upper limit, an OOM error is returned. Error example:

-OOM command not allowed when used memory > 'maxmemory'.

Solution:

Flush the Lua script cache by running the SCRIPT FLUSH command on the client. Different from the FLUSHALL command, the SCRIPT FLUSH command is synchronous. If the instance caches a large number of Lua scripts, the SCRIPT FLUSH command can block the instance for an extended period of time and the involved instance may become unavailable. Proceed with caution. We recommend that you perform this operation during off-peak hours.

Note

If you click Clear Data in the console, data can be cleared but the Lua script cache cannot be flushed.

Do not write Lua scripts that may take up excessive amounts of memory. Moreover, do not write Lua scripts that involve large amounts of data. Otherwise, memory usage significantly increases and an OOM error may occur. To reduce memory usage, we recommend that you enable data eviction by using the volatile-lru policy. By default, data eviction is enabled for the instance. However, the instance does not evict the Lua script cache regardless of whether data eviction is enabled.

Handle the NOSCRIPT error

Problem description:

If the script is not cached in the instance when you use the EVALSHA command, the instance returns the NOSCRIPT error. Error example:

(error) NOSCRIPT No matching script. Please use EVAL.

Solution:

Run the EVAL or SCRIPT LOAD command to cache the script in the instance and try again. In specific scenarios, such as instance migrations and configuration changes, the instance flushes the Lua script cache because the instance cannot ensure the persistence and replicability of Lua scripts. In this case, your client must be able to handle the error. For more information, see Caching, persistence, and replication of scripts.

The following sample Python code shows a method for handling the NOSCRIPT error. The sample code prepends strings by using Lua scripts.

Note

You can also use redis-py to handle the error. redis-py provides the Script class that encapsulates the judgment logic for Lua scripts, such as a catch statement for the NOSCRIPT error.

import redis
import hashlib

# strin specifies a string in Lua scripts. The following function returns the SHA1 value of strin in the string format. 
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 in Tair (Redis OSS-compatible). 
            return self.evalsha(script_sha1, 1, key, value) # If the script is already cached, the EVALSHA command is used to run the script.
        else:
            return self.eval(script_content, 1, key, value) # Otherwise, use the EVAL command to run the script. Note that the EVAL command can cache scripts. Another method is to use the SCRIPT LOAD and EVALSHA commands. 

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 timeouts of Lua scripts

  • Problem description:

    Slow Lua requests may block the instance because a Lua script is atomically executed in the instance. One Lua script can block the instance for up to 5 seconds. After 5 seconds, the instance returns a BUSY error for other commands until the script execution is complete.

    BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.

    Solution:

    Run the SCRIPT KILL command to terminate the Lua script or wait until the Lua script finishes running.

    Note
    • During the first 5 seconds when a slow Lua script is being executed, the SCRIPT KILL command does not take effect because the instance is being blocked.

    • To prevent the instance from being blocked for an extended period of time, we recommend that you estimate the amount of time required to execute a Lua script when you write the Lua script, check for infinite loop, and split the Lua script if necessary.

  • Problem description:

    If a Lua script has already run write commands against the dataset, the SCRIPT KILL command does not take effect. Error example:

    (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 instance that you want to manage and click restart in the Actions column.

Caching, persistence, and replication of scripts

Problem description:

Tair (Redis OSS-compatible) keeps caching the Lua scripts that have been executed in an instance if the instance is not restarted or the SCRIPT FLUSH command is not run for the instance. However, Tair (Redis OSS-compatible) cannot ensure the persistence of Lua scripts or the synchronization of Lua scripts from the current node to other nodes in scenarios such as instance migrations, configuration changes, version upgrades, and instance switchovers.

Solution:

Store all Lua scripts in your on-premise device. Recache the Lua scripts in Tair (Redis OSS-compatible) by using the EVAL or SCRIPT LOAD command if necessary. This prevents a NOSCRIPT error from occurring when Lua scripts are cleared during an instance restart or a high availability (HA) switchover.

Limits on Lua scripts in cluster instances

  • To ensure the execution atomicity, a Lua script cannot be split and can be executed only on one shard in a cluster instance. In most cases, a key is used to determine which shard Lua scripts are routed to. Therefore, you must specify at least one key when you run a Lua script in a cluster instance. If you want to read or write multiple keys, the keys in one Lua script must belong to the same slot. Otherwise, an abnormal execution result is returned. Lua scripts that do not have keys (such as KEYS, SCAN, and FLUSHDB) can be executed normally. However, only the data of a single shard is returned. This limit is caused by the architecture of cluster instances.

  • A Lua script may not be stored in other nodes when you run the SCRIPT LOAD command on one node.

Error codes and causes for custom Lua script errors in proxy mode

Proxy nodes perform syntax checks to identify the keys that belong to multiple slots and throw exceptions in advance to assist troubleshooting. Proxy nodes use a different approach to check for errors than Lua virtual machines. This places additional limits on the execution of Lua scripts in proxy mode. For example, the UNPACK command is not supported, and the EVAL, EVALSHA, and SCRIPT commands are not supported in MULTI and EXEC transactions. You can also set the script_check_enable parameter to disable additional checks on Lua syntax in proxy mode.

Note

How does setting the script_check_enable parameter to 0 affect the instance?

  • If the instance is compatible with Redis 5.0 (with minor version earlier than 5.0.8) and 4.0 or earlier, we recommend that you do not disable the checks. Otherwise, normal results may be returned when the script is not executed normally.

  • If you disable the checks on other versions, proxy nodes no longer check Lua syntax, but data nodes still check Lua syntax.

If the readonly_lua_route_ronode_enable parameter is set to 1 for a read/write splitting instance, proxy nodes check whether Lua scripts contain only read-only commands and determine whether to forward them to read-only nodes. This check logic imposes limits on Lua syntax.

The following table describes the error codes and causes.

Category

Error code

Description

Cluster architecture

-ERR for redis cluster, eval/evalsha number of keys can't be negative or zero\r\n

You must include a key when you execute a Lua script. Proxy nodes use the key to determine the shard to which the Lua script is forwarded.

# Example of valid command usage:
EVAL "return redis.call('get', KEYS[1])" 1 fooeval

# Example of invalid command usage:
EVAL "return redis.call('get', 'foo')" 0

-ERR 'xxx' command keys must in same slot

Multiple keys in a Lua script must belong to the same slot.

# Example of valid command usage:
EVAL "return redis.call('mget', KEYS[1], KEYS[2])" 2 foo {foo}bar

# Example of invalid command usage:
EVAL "return redis.call('mget', KEYS[1], KEYS[2])" 2 foo foobar

Lua syntax check in proxy mode

(You can set the script_check_enable parameter to 0 to disable this check.)

-ERR bad lua script for redis cluster, nested redis.call/redis.pcall

Nested calls are not supported. You can call the Lua script by using local variables.

# Example of valid command usage:
EVAL "local value = redis.call('GET', KEYS[1]); redis.call('SET', KEYS[2], value)" 2 foo bar

# Example of invalid command usage:
EVAL "redis.call('SET', KEYS[1], redis.call('GET', KEYS[2]))" 2 foo bar

-ERR bad lua script for redis cluster, first parameter of redis.call/redis.pcall must be a single literal string

When you use Lua scripts with the redis.call or redis.pcall function, the first parameter you provide must be a string literal representing the command that you want to run.

# Example of valid command usage:
eval "redis.call('GET', KEYS[1])" 1 foo

# Example of invalid command usage:
eval "local cmd = 'GET'; redis.call(cmd, KEYS[1])" 1 foo

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

Lua scripts use the redis.call or redis.pcall function to run commands. All keys must be specified by using the KEYS array, which cannot be replaced by Lua variables.

Note

This limit applies only to Redis Open-Source Edition instances that run Redis 5.0 (with minor version earlier than 5.0.8) or Redis 4.0 or earlier, cloud-native instances whose proxy version is earlier than 7.0.2, or classic instances whose proxy version is earlier than 6.8.12.

If the instance version and proxy version meet the requirements but the limit still exists, modify any parameter such as query_cache_expire. Wait for 1 minute and try again.

# Example of valid command usage:
EVAL "return redis.call('mget', KEYS[1], KEYS[2])" 2 foo {foo}bar

# Example of invalid command usage:
EVAL "return redis.call('mget', KEYS[1], '{foo}bar')" 1 foo                      # This command is invalid because the '{foo}bar' key must be specified by using the KEYS array. 
EVAL "local i = 2 return redis.call('mget', KEYS[1], KEYS[i])" 2 foo {foo}bar    # This command is invalid because the index of keys consists of variables, which is not allowed for an instance in proxy mode. Instances in direct connection mode are not subject to this limit. 
EVAL "return redis.call('mget', KEYS[1], ARGV[1])" 1 foo {foo}bar                # This command is invalid because ARGV[1] cannot be specified as a key. 

-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 the ZUNIONSTORE and ZINTERSTORE commands must be specified by using the KEYS array.

Note

This limit applies only to Redis Open-Source Edition instances that run Redis 5.0 (with minor version earlier than 5.0.8) or Redis 4.0 or earlier, cloud-native instances whose proxy version is earlier than 7.0.2, or classic instances whose proxy version is earlier than 6.8.12.

If the instance version and proxy version meet the requirements but the limit still exists, modify any parameter such as query_cache_expire. Wait for 1 minute and try again.

-ERR bad lua script for redis cluster, ZUNIONSTORE/ZINTERSTORE numkeys parameter should be a single number and not expression

The numkeys parameter of the ZUNIONSTORE and ZINTERSTORE commands is not a constant.

Note

This limit applies only to Redis Open-Source Edition instances that run Redis 5.0 (with minor version earlier than 5.0.8) or Redis 4.0 or earlier, cloud-native instances whose proxy version is earlier than 7.0.2, or classic instances whose proxy version is earlier than 6.8.12.

If the instance version and proxy version meet the requirements but the limit still exists, modify any parameter such as query_cache_expire. Wait for 1 minute and try again.

-ERR bad lua script for redis cluster, ZUNIONSTORE/ZINTERSTORE numkeys value is not an integer or out of range

The numkeys parameter of the ZUNIONSTORE and ZINTERSTORE commands is not a number.

Note

This limit applies only to Redis Open-Source Edition instances that run Redis 5.0 (with minor version earlier than 5.0.8) or Redis 4.0 or earlier, cloud-native instances whose proxy version is earlier than 7.0.2, or classic instances whose proxy version is earlier than 6.8.12.

If the instance version and proxy version meet the requirements but the limit still exists, modify any parameter such as query_cache_expire. Wait for 1 minute and try again.

-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 of the ZUNIONSTORE and ZINTERSTORE commands must be specified by using the KEYS array.

Note

This limit applies only to Redis Open-Source Edition instances that run Redis 5.0 (with minor version earlier than 5.0.8) or Redis 4.0 or earlier, cloud-native instances whose proxy version is earlier than 7.0.2, or classic instances whose proxy version is earlier than 6.8.12.

If the instance version and proxy version meet the requirements but the limit still exists, modify any parameter such as query_cache_expire. Wait for 1 minute and try again.

-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 of the XREAD and XREADGROUP commands must be specified by using the KEYS array.

Note

This limit applies only to Redis Open-Source Edition instances that run Redis 5.0 (with minor version earlier than 5.0.8) or Redis 4.0 or earlier, cloud-native instances whose proxy version is earlier than 7.0.2, or classic instances whose proxy version is earlier than 6.8.12.

If the instance version and proxy version meet the requirements but the limit still exists, modify any parameter such as query_cache_expire. Wait for 1 minute and try again.

-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

The keys of the SORT commands must be specified by using the KEYS array.

Note

This limit applies only to Redis Open-Source Edition instances that run Redis 5.0 (with minor version earlier than 5.0.8) or Redis 4.0 or earlier, cloud-native instances whose proxy version is earlier than 7.0.2, or classic instances whose proxy version is earlier than 6.8.12.

If the instance version and proxy version meet the requirements but the limit still exists, modify any parameter such as query_cache_expire. Wait for 1 minute and try again.

Read/write permissions

-ERR Write commands are not allowed from read-only scripts

Lua scripts sent by using the EVAL_RO command cannot contain write commands.

-ERR bad write command in no write privilege

Lua scripts sent by a read-only account cannot contain write commands.

Unsupported commands

-ERR script debug not support

The SCRIPT DEBUG command is not supported in proxy mode.

-ERR bad lua script for redis cluster, redis.call/pcall unkown redis command xxx

Lua scripts contain commands that are not supported in proxy mode. For more information, see Limits on commands supported by cluster instances and read/write splitting instances.

Lua syntax

  • -ERR bad lua script for redis cluster, redis.call/pcall expect '('

  • -ERR bad lua script for redis cluster, redis.call/redis.pcall definition is not complete, expect ')'

Lua syntax error: The redis.call function must be followed by the complete set of ( and ).

-ERR bad lua script for redis cluster, at least 1 input key is needed for ZUNIONSTORE/ZINTERSTORE

The numkeys parameter of the ZUNIONSTORE and ZINTERSTORE commands must be greater than 0.

-ERR bad lua script for redis cluster, ZUNIONSTORE/ZINTERSTORE key count < numkeys

The number of keys in the ZUNIONSTORE and ZINTERSTORE commands is less than the numkeys value.

-ERR bad lua script for redis cluster, xread/xreadgroup command syntax error

The XREAD or XREADGROUP command uses incorrect syntax. Check the number of parameters.

-ERR bad lua script for redis cluster, xread/xreadgroup command syntax error, streams must be specified

The streams parameter is required in the XREAD and XREADGROUP commands.

-ERR bad lua script for redis cluster, sort command syntax error

The SORT command uses incorrect syntax.