diff --git a/commands/delifeq.md b/commands/delifeq.md new file mode 100644 index 00000000..aca7a870 --- /dev/null +++ b/commands/delifeq.md @@ -0,0 +1,58 @@ +Deletes the key if its value matches the given string. + +This command is typically used to safely release locks in distributed systems, ensuring that only the owner of the lock (as identified by the value) can delete it. + +If the key exists and the stored value is a string that exactly matches the provided value, the key is removed and `1` is returned. Otherwise, the key is left unchanged and `0` is returned. + +## Examples + +Basic usage: + +``` +127.0.0.1:6379> SET mykey abc123 +OK +127.0.0.1:6379> DELIFEQ mykey abc123 +(integer) 1 +127.0.0.1:6379> DELIFEQ mykey abc123 +(integer) 0 +``` + +Wrong value: + +``` +127.0.0.1:6379> SET mykey xyz789 +OK +127.0.0.1:6379> DELIFEQ mykey abc123 +(integer) 0 +``` + +Wrong type: + +``` +127.0.0.1:6379> SADD mykey somevalue +(integer) 1 +127.0.0.1:6379> DELIFEQ mykey somevalue +(error) WRONGTYPE Operation against a key holding the wrong kind of value +``` + +## Use case: Safe lock release + +`DELIFEQ` enables an atomic check-and-delete operation to safely release a lock only if the process still holds it. This avoids race conditions where a lock might expire and be claimed by another process before the original owner tries to delete it. + +This pattern is commonly used as part of distributed locking systems like [Redlock](../topics/distlock.md), which previously relied on Lua scripting to implement this logic. With `DELIFEQ`, such a use case becomes more robust and idiomatic. + +Instead of: + +``` +EVAL "if redis.call('GET',KEYS[1]) == ARGV[1] then return redis.call('DEL',KEYS[1]) else return 0 end" 1 mykey abc123 +``` + +You can now use: + +``` +DELIFEQ mykey abc123 +``` + +## See also + +* [`SET IFEQ`](set.md) - Sets the key only if it matches the given value. diff --git a/commands/set.md b/commands/set.md index d3467cbe..7fc5c495 100644 --- a/commands/set.md +++ b/commands/set.md @@ -73,3 +73,7 @@ An example of unlock script would be similar to the following: end The script should be called with `EVAL ...script... 1 resource-name token-value` + +## See also + +* [`DELIFEQ`](delifeq.md) - Deletes the key if its value matches the given string. diff --git a/resp2_replies.json b/resp2_replies.json index 37dd782b..b6c4ad43 100644 --- a/resp2_replies.json +++ b/resp2_replies.json @@ -399,6 +399,11 @@ "DEL": [ "[Integer reply](../topics/protocol.md#integers): the number of keys that were removed." ], + "DELIFEQ": [ + "One of the following:", + "* [Integer reply](../topics/protocol.md#integers): `1` if the key was deleted.", + "* [Integer reply](../topics/protocol.md#integers): `0` if the key was not deleted because it did not exist or the value was not equal to the provided value." + ], "DISCARD": [ "[Simple string reply](../topics/protocol.md#simple-strings): `OK`." ], diff --git a/resp3_replies.json b/resp3_replies.json index 6cffb4dc..167e4b01 100644 --- a/resp3_replies.json +++ b/resp3_replies.json @@ -399,6 +399,11 @@ "DEL": [ "[Integer reply](../topics/protocol.md#integers): the number of keys that were removed." ], + "DELIFEQ": [ + "One of the following:", + "* [Integer reply](../topics/protocol.md#integers): `1` if the key was deleted.", + "* [Integer reply](../topics/protocol.md#integers): `0` if the key was not deleted because it did not exist or the value was not equal to the provided value." + ], "DISCARD": [ "[Simple string reply](../topics/protocol.md#simple-strings): `OK`." ], diff --git a/topics/distlock.md b/topics/distlock.md index 425ce7ae..63fb2939 100644 --- a/topics/distlock.md +++ b/topics/distlock.md @@ -84,7 +84,13 @@ To acquire the lock, the way to go is the following: The command will set the key only if it does not already exist (`NX` option), with an expire of 30000 milliseconds (`PX` option). The key is set to a value “my\_random\_value”. This value must be unique across all clients and all lock requests. -Basically the random value is used in order to release the lock in a safe way, with a script that tells Valkey: remove the key only if it exists and the value stored at the key is exactly the one I expect to be. This is accomplished by the following Lua script: +Basically the random value is used in order to release the lock in a safe way, with a script that tells Valkey: remove the key only if it exists and the value stored at the key is exactly the one I expect to be. + +In Valkey 9.0.0 and later, this can be done atomically using the built-in DELIFEQ command: + + DELIFEQ resource_name my_random_value + +In earlier versions of Valkey, the same behavior can be achieved using a Lua script: if server.call("get",KEYS[1]) == ARGV[1] then return server.call("del",KEYS[1])