Set the string value of a key only when the key doesn’t exist.
SETNX
key value
Set key
to hold string value
if
key
does not exist. In that case, it is equal to
SET
. When key
already holds a value, no
operation is performed. SETNX
is short for
“SET if Not
eXists”.
One of the following:
Integer reply:
0
if the key was not set.
Integer reply:
1
if the key was set.
O(1)
@fast @string @write
127.0.0.1:6379> SETNX mykey "Hello"
(integer) 1
127.0.0.1:6379> SETNX mykey "World"
(integer) 0
127.0.0.1:6379> GET mykey
"Hello"
SETNX
Please note that:
SET
command
to acquire the lock, and a simple Lua script to release the lock. The
pattern is documented in the SET
command page.That said, SETNX
can be used, and was historically used,
as a locking primitive. For example, to acquire the lock of the key
foo
, the client could try the following:
SETNX lock.foo <current Unix time + lock timeout + 1>
If SETNX
returns 1
the client acquired the
lock, setting the lock.foo
key to the Unix time at which
the lock should no longer be considered valid. The client will later use
DEL lock.foo
in order to release the lock.
If SETNX
returns 0
the key is already
locked by some other client. We can either return to the caller if it’s
a non blocking lock, or enter a loop retrying to hold the lock until we
succeed or some kind of timeout expires.
In the above locking algorithm there is a problem: what happens if a client fails, crashes, or is otherwise not able to release the lock? It’s possible to detect this condition because the lock key contains a UNIX timestamp. If such a timestamp is equal to the current Unix time the lock is no longer valid.
When this happens we can’t just call DEL
against the key
to remove the lock and then try to issue a SETNX
, as there
is a race condition here, when multiple clients detected an expired lock
and are trying to release it.
lock.foo
to check the timestamp, because
they both received 0
after executing SETNX
, as
the lock is still held by C3 that crashed after holding the lock.DEL lock.foo
SETNX lock.foo
and it succeedsDEL lock.foo
SETNX lock.foo
and it succeedsFortunately, it’s possible to avoid this issue using the following algorithm. Let’s see how C4, our sane client, uses the good algorithm:
C4 sends SETNX lock.foo
in order to acquire the
lock
The crashed client C3 still holds it, so Valkey will reply with
0
to C4.
C4 sends GET lock.foo
to check if the lock expired.
If it is not, it will sleep for some time and retry from the
start.
Instead, if the lock is expired because the Unix time at
lock.foo
is older than the current Unix time, C4 tries to
perform:
GETSET lock.foo <current Unix timestamp + lock timeout + 1>
Because of the GETSET
semantic, C4 can check if the
old value stored at key
is still an expired timestamp. If
it is, the lock was acquired.
If another client, for instance C5, was faster than C4 and
acquired the lock with the GETSET
operation, the C4
GETSET
operation will return a non expired timestamp. C4
will simply restart from the first step. Note that even if C4 set the
key a bit a few seconds in the future this is not a problem.
In order to make this locking algorithm more robust, a client holding
a lock should always check the timeout didn’t expire before unlocking
the key with DEL
because client failures can be complex,
not just crashing but also blocking a lot of time against some
operations and trying to issue DEL
after a lot of time
(when the LOCK is already held by another client).
SET
with the
NX
argument.APPEND, DECR, DECRBY, GET, GETDEL, GETEX, GETRANGE, INCR, INCRBY, INCRBYFLOAT, LCS, MGET, MSET, MSETNX, SET, SETRANGE, STRLEN.