Reputation: 2156
I want to update a Redis key, but only if its value by the time of the update is equal to certain value.
Similar question has been asked on SO here and what I found were suggestions about a Lua script, which I am not into.
I found this post on Reddit, suggesting the following:
GET
X
If value is A
then WATCH
X
MULTI
SET
X to B
EXEC
At first glance, this looks good, because if the key gets update by another process anywhere between WATCH
and EXEC
, Redis will abort the transaction.
However, when I thought more about it, I came up with the question:
What if the key gets updated between GET
and WATCH
?
Is there a way to update the key only if it's 100% sure that at the time of the transaction it holds a certain value, and achieve this only by using Redis commands, as one would do it in SQL with an
UPDATE ...
CASE ...
WHEN ...
THEN ...
statement?
Upvotes: 0
Views: 8572
Reputation: 6774
There is no way to do it having the check done at the server with commands only. It has been discussed and discarded precisely because of Lua scripts being suitable for the purpose. See the discussion here
This can be done with Lua scripting in a one-liner EVAL
command:
EVAL "if redis.call('get', 'myKey') == 'expectedVal' then return redis.call('set', 'myKey', 'newVal') else return redis.error_reply('myKey has changed!') end" 0
"Redis guarantees that a script is executed in an atomic way: no other script or Redis command will be executed while a script is being executed" EVAL command docs
The Lua script has better performance than doing Optimistic locking using check-and-set. If there are race conditions, it is better to do check and set on an atomic operation to decrease the probability of losing the race.
Upvotes: 5
Reputation: 23021
Although transaction is NOT recommended, you can still use it to achieve your goal, i.e. watch the key before any other operations.
1. WATCH X
2. GET X
3. if value is not equal to A, UNWATCH X and abort
4. MULTI
5. SET X B
6. EXEC
Upvotes: 5
Reputation: 7267
it's best to use Lua script but if you insist on not using it, you may consider using redlock to lock the resource while you update it. any attempt to update the value will fail during the time you lock, given that the update process is also using redlock
you can change retryCount
and retryDelay
and other options to fit with your use case
example with redlock
// the string identifier for the resource you want to lock
var resource = 'locks:account:322456';
// the maximum amount of time you want the resource locked in milliseconds,
// keeping in mind that you can extend the lock up until
// the point when it expires
var ttl = 1000;
redlock.lock(resource, ttl).then(function(lock) {
// ...do something here...
// unlock your resource when you are done
return lock.unlock()
.catch(function(err) {
// we weren't able to reach redis; your lock will eventually
// expire, but you probably want to log this error
console.error(err);
});
});
Upvotes: 2