Use EVAL to run a Lua script on the server.
<numkeys> says how many of the arguments that follow are key names. The script receives those in the KEYS table and every remaining argument in ARGV. Passing key names as keys rather than hardcoding them in the script body matters, because Redis uses that list for routing and access checks. Inside the script, redis.call runs Redis commands and its return value is converted to a Lua value.
The script runs as a single atomic step, which makes it the standard way to do read, decide, and write logic, such as a rate limiter or a compare-and-set update, in one round trip and without a transaction. Keep scripts short, since a script that holds the database blocks everything else, and keep them deterministic by deriving values from KEYS, ARGV, or data read inside the script rather than from clock or random sources.
Upstash isolates a script with a lock. By default that is the global lock, because the engine cannot know in advance which keys the script will touch, so no other command runs while the script does. Adding the allow-key-locking flag to the script's shebang line makes it lock the hash tag of each key passed in KEYS instead, so calls that work on disjoint hash tags run in parallel:
With the flag set, every key the script touches must appear in KEYS or share a valid hash tag with an already locked key. Commands that need database-wide access, such as FLUSHDB, are rejected. See Key-Based Locking for the full rules.
Pass every key the script touches through KEYS when possible, even when the
script runs under the global lock. Upstash keeps idle entries
on disk: declared keys are loaded before the
script starts and the lock is released during that read. A same-tag dynamic
key is allowed with allow-key-locking, but it is read from disk with the
lock held. This stalls commands using the same hash tag, or the whole
database when the script uses the global lock. See
Dynamic Keys and Latency.
Sending a script also caches it under its SHA1 digest, so later calls can use EVALSHA and avoid resending the body. Use EVAL_RO for scripts that only read.
Syntax#
Arguments#
| Argument | Required | Repeatable | Description |
|---|---|---|---|
<script> | Yes | No | Lua script source. |
<numkeys> | Yes | No | Number of key arguments that follow. |
<key> | No | Yes | Redis key targeted by the command. |
<arg> | No | Yes | Additional argument, available to the script as ARGV. |
Important points#
numkeysmust equal the number of key arguments that immediately follow it; remaining arguments are available to the script or function as ordinary arguments.- The script takes the global lock unless its shebang sets the
allow-key-lockingflag, in which case it locks the hash tag of each key passed inKEYSand any matching Search indexes. See Key-Based Locking. - A script queued inside a
MULTI/EXECtransaction always runs under the global lock, even when it setsallow-key-locking. Call it directly if you want per-key locking. - Pass every key the script touches through
KEYSwhen possible. Withallow-key-locking, a dynamic key is accepted only when its hash tag is already locked, and a cold dynamic key is loaded with that lock held. See Dynamic Keys and Latency.
Reply conversion#
Replies from redis.call and redis.pcall reach the script as Lua values, converted with RESP2 rules by default whatever protocol the client connection itself negotiated. redis.setresp(3) switches the script to RESP3 conversions for every call that follows, and redis.setresp(2) switches back. The setting lasts for the rest of the script's execution and does not change how the script's own return value is sent to the client.
RESP3 conversions keep information that RESP2 flattens away: a double stays a number instead of becoming a formatted string, a map keeps its keys, and a null is distinguishable from false.
| Reply type | RESP2 conversion | RESP3 conversion |
|---|---|---|
| Null | false | nil |
| Boolean | 1 or 0 | true or false |
| Double | string | { double = <number> } |
| Big number | string | { big_number = "<digits>" } |
| Verbatim string | string | { verbatim_string = { format = "<3-char format>", string = "<value>" } } |
| Map | flat array of alternating keys and values | { map = { [key] = value, ... } } |
| Set | array of members | { set = { [member] = true, ... } } |
The same tables are accepted on the way out: returning { double = 1.5 } from a script sends a double reply to a RESP3 client, and { map = { ... } } sends a map.
Response#
The reply reports the result of the operation. Error replies have the same shape in RESP2 and RESP3 and are surfaced as exceptions by the SDKs below.
| Protocol | Reply |
|---|---|
| RESP2 | Reply produced by the evaluated script |
| RESP3 | Reply produced by the evaluated script |
Client libraries often decode bulk strings, maps, sets, and numeric strings into language-native values. The table describes the Redis wire reply.
Examples#
TCP examples use the TLS REDIS_URL from the Upstash console. REST examples use UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN.