Python Hunter
Python Hunter

Reputation: 2156

How to make a 100% sure conditional update of a Redis key without lua?

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:

  1. GET X

  2. If value is A then WATCH X

  3. MULTI

  4. SET X to B

  5. 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

Answers (3)

LeoMurillo
LeoMurillo

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

for_stack
for_stack

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

Tuan Anh Tran
Tuan Anh Tran

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

Related Questions