How It Works
- Single-key commands (for example
GET,SET,INCR,HSET) acquire a lock on just that key. - Multi-key commands acquire locks on every key they reference, in a deterministic order to avoid deadlocks.
- Read-only commands (for example
GET,HGET,LRANGE) take a shared read lock, so multiple readers on the same key run concurrently. Read locks block writers on that key until they complete. - Commands that need a database-wide operation, such as
FLUSHDBandFLUSHALL, take the global lock and can reduce concurrency while they run.
Transactions
Transactions (MULTI/EXEC) use key-based locking at EXEC time. While
commands are queued, Upstash collects the keys referenced by the transaction.
When EXEC runs, the engine takes an exclusive write lock for the union of
those keys and executes the queued commands atomically.
Transactions that touch disjoint key sets can run concurrently. Transactions
that share any key block each other until one transaction finishes.
EXEC locks user:42:name and user:42:version for the
duration of the transaction.
If a queued command requires a database-wide lock, the whole transaction uses
the global lock. This includes commands such as FLUSHDB and FLUSHALL.
Lua scripts queued inside a transaction always execute under the global lock,
even if the script declares allow-key-locking. If you want
script-level key locking, run the script directly with EVAL/EVALSHA
outside of a transaction.
Lua Scripts
Lua scripts (EVAL, EVALSHA, EVAL_RO, EVALSHA_RO) default to the global
lock because the engine cannot know in advance which keys the script will use.
To opt into key-based locking, add the allow-key-locking flag to the script’s
shebang line:
KEYS
array when the script is invoked. Other commands and scripts that touch disjoint
keys can run in parallel.
Rules for allow-key-locking
-
Every key passed to
redis.callmust appear inKEYS. You may compute the key value inside the script (for example by concatenating parts ofARGV), but the final string must exactly match one of the entries declared in theKEYSarray. Otherwise the engine rejects the command:In practice, this means you should pass fully resolved keys throughKEYSrather than reconstructing them fromARGVinside the script. -
Database-wide writes are not allowed. Commands that require database-wide
exclusive access, such as
FLUSHDBandFLUSHALL, cannot be called from a script withallow-key-locking. Run those scripts without the flag so the engine can use the global lock.
no-writes flag also need
allow-key-locking if you want them to use per-key read locks. Without it, they
run under the global lock. To use both flags in a Lua script, separate them with
a comma:
When to use it
Enableallow-key-locking for short scripts that operate on a small, known
set of keys and are called frequently enough that the global lock becomes a
bottleneck (for example counters, rate limiters, or per-user state
transitions). For scripts that must scan or mutate many keys at once, leave
the flag off so the engine uses the global lock.
Example: Key-Locked Counter
user:<id>:quota key.
Redis Functions
Redis functions (FCALL, FCALL_RO) also default to the global lock. For
functions, allow-key-locking is set on each registered function, not on the
library shebang:
FCALL key list. Keys passed as regular arguments are not locked unless they
also appear in the key list.
If the function is also read-only, include both flags in the function
registration: