• Get Multiple Redis Hashes in Node.js

    Redis is an open source, scalable, in-memory, key/value store. It can store various types of values (Strings, Lists, Sorted Lists, Hashes, Etc...) with built-in commands for each data type. This post is not about setting up Redis or configuring your environment. I am assuming that you already have Redis up and running and that you are working with Node.js to interact with your Redis store. It is sometimes useful to get multiple hashes with all key-value pairs from Redis in one go.

    Other types in Redis have commands to get or set multiple values (LRANGE, MGET/SET, Etc...). There is even a command to get all key-value pairs from a single hash (HGETALL) but the command (MHGETALL) does not exist for returning multiple hashes. This post will show you how to implement this as a JavaScript function using the ioredis module with MULTI/EXEC, Pipelining or both. Other options do exist. This is just an example of one possible way to achieve this by writing your own MHGETALL function.

    Setup Sample Data

    To keep this simple, lets assume that we are building a Redis cache for status updates. Each update will contain two fields (author and update).

    {
        "author":1000,
        "update":"Redis is awesome!"
    }
    

    First we will create a few status update hashes in Redis using redis-cli (the command line interface that comes with Redis).

    > HMSET post:1000 author 1000 update "Redis is awesome!"
    OK
    > HMSET post:1001 author 1000 update "Redis is still awesome!"
    OK
    > HMSET post:1002 author 1000 update "Ok you get the point."
    OK

    And just to confirm that our store actually contains the status updates, we can retrieve the updates using the HGETALL Redis command. I'll only retrieve one of these here for your reference.

    > HGETALL post:1000
    1) "author"
    2) "1000"
    3) "update"
    4) "Redis is awesome!"
    

    Note: At the time of this writing there is not a single command to get all keys from all three status hashes at once. i.e: MHGETALL [<key>, <key>, <key>] where key is the primary key of a hash. In the examples that follow, we will create this function for ourselves.

    Working in Node

    Step 1: Install the ioredis and, optionally, the hiredis modules...

    npm install ioredis

    • ioredis is the JavaScript module that we will use to connect to our Redis server and execute commands.

    npm install hiredis

    • hiredis is a minimalistic C client library for the Redis database. If you choose not to install this module then ioredis will default to a JavaScript parser.

    Note: you can skip this step if you already have the ioredis module installed in your project directory. However, if you skip this step and you don't have it, you will not be able to execute the sample code that follows. Instead you would get an error similar to the this one.

    jbiard$ node sample.js 
    module.js:338
        throw err;
              ^
    Error: Cannot find module 'ioredis'
        ...
        at Function.Module.runMain (module.js:501:10)

    Step 2: Write some JavaScript code...

    Create a new program and enter the sample code shown below. We are creating two different functions that do basically the same thing. In both functions, we are using the Redis API to execute a list of commands and after the commands are executed, Redis returns an array of responses to us. ioredis nicely converts those responses back into JavaScript hashes.

    var Redis = require('ioredis'),  
        redis = new Redis();
    
    /*  
      Use the MULTI command to execute mulitple HGETALL commands.
    */
    
    MHGETALL(['post:1000', 'post:1001', 'post:1002'], function(err, arr) {  
        console.log('Received output from Redis Multi/Exec:');
        console.log(JSON.stringify(arr));
    });
    
    function MHGETALL(keys, cb) {
    
        redis.multi({pipeline: false});
    
        keys.forEach(function(key, index){
            redis.hgetall(key);
        });
    
        redis.exec(function(err, result){
            cb(err, result);
        });
    }
    
    /*  
      Use a pipeline to execute mulitple HGETALL commands.
    */
    
    MHGETALL_P(['post:1000', 'post:1001', 'post:1002'], function(err, arr) {  
        console.log('Received output from Redis Pipeline/Exec:');
        console.log(JSON.stringify(arr));
    });
    
    function MHGETALL_P(keys, cb) {
    
        var pipeline = redis.pipeline();
    
        keys.forEach(function(key, index){
            pipeline.hgetall(key);
        });
    
        pipeline.exec(function(err, result){
            cb(err, result);
        });
    }

    NB: All standard sample code disclaimers apply. This is just sample code. Add your own error checking and otherwise use as-is at your own risk.

    Step 3: Run the program to see the output...

    To run the code just call node and pass in the name of your program. I called my sample application mhgetall.js. Also, make sure that your Redis server is running and that you have created the sample data noted above. NB: This program assumes that your Redis server is running on the default port on localhost. If your setup is more customized then you will want to review the ioredis documentation to get information on more advanced configurations.

    jbiard$ node mhgetall.js

    And the output from running this command shows the values retrieved from Redis collected into a single response. (We executed both functions MHGETALL() and MHGETALL_P() to show the output from MULTI/EXEC or Pipeline/EXEC respectively.)

    Received output from Redis Multi/Exec:
    [[null,["author","1000","update","Redis is awesome!"]],[null,["author","1000","update","Redis is still awesome!"]],[null,["author","1000","update","Ok you get the point."]]]
    Received output from Redis Pipeline/Exec:
    [[null,{"author":"1000","update":"Redis is awesome!"}],[null,{"author":"1000","update":"Redis is still awesome!"}],[null,{"author":"1000","update":"Ok you get the point."}]]

    The choice between MULTI (for transactions) or Pipeline (or both; transactions with pipelining) is up to your specific application and the features you need to implement. For this simple example either approach is fine. Have a read through those former links for reference and consider it in your own implementation.

    So, what's up with all those null objects in the array?

    Well, that has to do with how ioredis returns results from multi / pipelining. The response from the callback passed to exec() will contain a 2-dimensional array. Per the documentation, each sub-array will contain two elements that correspond with the response for each command in the form of [err, result].

    I hope you have found this to be useful.

    Cheers!

Justin Biard

Working with computers / technology for 20+ years and just scratching the surface... (Opinions are my own)

Copyright Justin Biard © 2017