How to use redis WATCH in Node.js?
Asked Answered
I

1

5

Background

I have an atomic operation and I need to use a lock to prevent other clients from reading an unstable value.

  • Platform: Node 10.1.0
  • Library: redis

Solution

According to the official documentation, the solution for this is to use WATCH together with MULTI:

Problem

Now, the usage of MULTI is documented and I have a general idea on how I can use it.

var redis = require( "redis" );
var bluebird = require( "bluebird" );
var client = redis.createClient();
var multi = client.multi();

multi.hsetAsync( "test", "array", "[1, 2]" );
multi.hgetAsync( "test", "array" );
multi.execAsync( ).then( console.log ); // [ 0, "[1, 2]" ]

I understand that this is the correct implementation of multi. First i need to create a client, and then I create a multi query.

I understand that multi and client share the same interface, but it is also not clear what benefits ( if any ) I have from using hgetAsync instead of hget in a multi query, since I assume that all that multi does is adding said requests to a queue synchronously ( therefore I shouldn't need the Async vartiant ).

Upon calling multi.execAsync( ) the execution of the query will happen atomically.

But I don't get how WATCH is supposed to enter here. I couldn't find any reference to it in the documentation, not anything regarding the optimist lock system that REDIS has.

Questions

So I have the following questions:

  1. Is WATCH supported with MULTI ?
  2. If so, can you share a code snippet ?
  3. Does it make sense in this example to use multi.hgetAsync( "test", "array" ); instead of multi.hget( "test", "array" ); ?
Integrated answered 6/6, 2018 at 8:52 Comment(0)
I
13

Asnwer

There is no documentation regarding the usage of WATCH in node-redis. I did however found an extremely useful set of tips in MDN:

https://developer.mozilla.org/en-US/docs/Mozilla/Redis_Tips

In sum, WATCH should be used like the following:

var redis  = require("redis"),
client = redis.createClient({ ... });

client.watch("foo", function( err ){
    if(err) throw err;

    client.get("foo", function(err, result) {
        if(err) throw err;

        // Process result
        // Heavy and time consuming operation here

        client.multi()
            .set("foo", "some heavy computation")
            .exec(function(err, results) {

                /**
                 * If err is null, it means Redis successfully attempted 
                 * the operation.
                 */ 
                if(err) throw err;

                /**
                 * If results === null, it means that a concurrent client
                 * changed the key while we were processing it and thus 
                 * the execution of the MULTI command was not performed.
                 * 
                 * NOTICE: Failing an execution of MULTI is not considered
                 * an error. So you will have err === null and results === null
                 */

            });
    });
});

So, to answer my questions:

  1. Yes, although watch is called on the RedisClient prototype and not on the Multi prototype.
  2. Code snippet provided above.
  3. Because each method from an object that has a Multi prototype returns the object itself, using the Async versions of the methods brings no benefit whatsoever, except for execAsync which allows you to execute the multi queries and deal with the response in a Promise instead of a callback.

Important Notes

Another really important thing is that watch works for KEYS only, not for hashes. So in my case, you cannot watch the field array of the hash test. You can watch the entire test set, but not a specific field.

So, because in my code I actually want to watch a field on a hash. This is not possible. I must instead use a key naming system that will allow that instead:

var redis = require( "redis" );
var bluebird = require( "bluebird" );
var client = redis.createClient();
var multi = client.multi();

client.watchAsync( "test_array" )
    then( ( ) =>
        multi.set( "test_array", "[1, 2]" )
            .get( "test_array" )
            .execAsync( ) 
    )
    .then( console.log ); // [ 0, "[1, 2]" ]

The documentation for this is really sparse, but I hope this question helps someone in the future.


Are you reading this from the future?

If you are reading this from the future, you can now enjoy my personal contribution to the node_redis project and check the updated documentation:

Integrated answered 7/6, 2018 at 6:43 Comment(2)
Is it possibile to have a race condition using the same redis client? I'm thinking about running the same MULTI with async.parallel for example.Platonism
github.com/redis/node-redis/blob/master/docs/… -- A new doc on node Redis explains this use case very wellCuevas

© 2022 - 2024 — McMap. All rights reserved.