Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

Use node.js+redis to store average request time

Use node.js+redis to store average request time

Problem

Hi folks I have a very practical redis use-case question. Say I want to store average request time with Redis with the following js code. Basically I am trying to calculate the average request time and save to redis upon each request entry ( [ req_path, req_time ] )

var rc=require('redis').createClient()
    ,rc2=require('redis').createClient()
    ,test_data=[
        ['path/1', 100]
        ,['path/2', 200]
        ,['path/1', 50]
        ,['path/1', 70]
        ,['path/3', 400]
        ,['path/2', 150]
    ];

rc.del('reqtime');
rc.del('reqcnt');
rc.del('avgreqtime');

for(var i=0, l=test_data.length; i

But it's not working as expected for the avgreqtime key. From the stdout I got

debug: iteration # 0, item=["path/1",100]
debug: iteration # 1, item=["path/2",200]
debug: iteration # 2, item=["path/1",50]
debug: iteration # 3, item=["path/1",70]
debug: iteration # 4, item=["path/3",400]
debug: iteration # 5, item=["path/2",150]
req_path=path/2,t=undefined,c=1
debug: added member path/2 to sorted set "avgreqtime" with score %f NaN
req_path=path/2,t=undefined,c=1
debug: added member path/2 to sorted set "avgreqtime" with score %f NaN
req_path=path/2,t=undefined,c=2
debug: added member path/2 to sorted set "avgreqtime" with score %f NaN
req_path=path/2,t=undefined,c=3
debug: added member path/2 to sorted set "avgreqtime" with score %f NaN
req_path=path/2,t=undefined,c=1
debug: added member path/2 to sorted set "avgreqtime" with score %f NaN
req_path=path/2,t=undefined,c=2
debug: added member path/2 to sorted set "avgreqtime" with score %f NaN

The debug lines inside redis functions are printed at once in the end instead of during each iteration. I think this has something to do with node.js's asynchronous nature but I have no clue how to get this work. As an experiment I also tried replacing the for loop with the following without success:

for(var i=0, l=test_data.length; i

I got total request time in each iteration this time but the problem is req_path sticks with 'path/2' which is the last req_path in test_data. As a result only 'path/2' gets saved to avgreqtime and it's wrong:

debug: iteration # 0, item=["path/1",100]
debug: iteration # 1, item=["path/2",200]
debug: iteration # 2, item=["path/1",50]
debug: iteration # 3, item=["path/1",70]
debug: iteration # 4, item=["path/3",400]
debug: iteration # 5, item=["path/2",150]
debug(path/2): got ["100","1"]
debug(path/2): got ["200","1"]
debug(path/2): got ["150","2"]
debug(path/2): got ["220","3"]
debug(path/2): got ["400","1"]
debug(path/2): got ["350","2"]

I am using Redis 2.4.5 and the node redis client is from https://github.com/mranney/node_redis

Problem courtesy of: ricochen

Solution

You are correct in your guess that it has to do with node's asynchronous nature. I will try a simple example here:

for(var i = 0; i 

Here, i will be what you expect it to be the first time you refer to it (as a parameter to someAsyncFunction). Inside the callback to that function, i will always be 10. The for loop has already finished by the time the callback is executed. To fix this, you need to bind i somehow. One way is an anonymous function, immediately executed:

for(var i = 0; i 

Now, i will be bound to the correct value even inside the callback. It's not optimal, because we need to specify a new function each time. This is better:

var executeTheFunction = function(i) {
  someAsyncFunction(i, function(err, data) {
    console.log("executed function for", i);
  });
};

for(var i = 0; i 

Note that our executeTheFunction doesn't take a callback. This means that we can't really control execution -- all calls will be executed immediately, which may not be what we want if there are many calls. In such a case, I recommend the async module, which makes this stuff easy.

Update: Here's an example with async:

var calculateAverage = function(item, callback) {
    var req_path = item[0], req_time = item[1];

    rc.multi()
        .zincrby('reqtime', req_time, req_path )
        .zincrby('reqcnt', 1, req_path )
        .exec( function(err, replies) {
            if(err) return callback(err);
            console.log('debug(%s): got %j', req_path, replies);
            var avg=replies[0]/replies[1];
            rc2.zadd('avgreqtime', avg, req_path, callback);
        });
}

async.map(test_data, calculateAverage, function(err) {
    if(err)
        console.error("Error:", err);
    else
        console.log("Finished");
});

Now, you can easily manage this kind of stuff with async.queue etc.

Solution courtesy of: Linus Gustav Larsson Thiel

Discussion

View additional discussion.



This post first appeared on Node.js Recipes, please read the originial post: here

Share the post

Use node.js+redis to store average request time

×

Subscribe to Node.js Recipes

Get updates delivered right to your inbox!

Thank you for your subscription

×