A time series is an ordered sequence of values that are made over a time interval. You can use time series in statistics, communications, and social networks. I this tutorial we are going to create a simple stock time series Node.js library using Redis Strings. This library records events per second, minute, hour, and day.

This library will be able to save an event at a given timestamp with an insert method and fetch values within a range of timestamps with a fetch method. The library we are going to create provides multiple granularities: second, minute, hour, and day. For example, if an event happens on date 8/11/2015 at 00:00:00(timestamp 1446940800), the following Redis keys will be incremented: - event:1sec:1446940800 - event:1min:1446940800 - event:1hour:1446940800 - event:1day:1446940800

Create a file stock-timeseries.js with the following code:


//time series constructor. Requires a redis client and a namespace
function TimeSeries(client, namespace){

    this.namespace = namespace;
    this.client = client;
    //granularity names and their equivalents in seconds
    this.units = {
        second:1,
        minute: 60,
        hour: 60 * 60,
        day: 24 * 60 * 60
    };
    //each granularity has a name, TTL(time to live) and a duration.
    //the null ttl present on 1dat meands that this ttl never expires
    this.granularities = {
        '1sec': {name: '1sec', ttl: this.units.hour * 2, duration: this.units.second},
        '1min': {name: '1min', ttl: this.units.day * 7, duration: this.units.minute},
        '1hour': {name: '1hour', ttl: this.units.day * 60, duration: this.units.hour},
        '1day': {name: '1day', ttl: null, duration: this.units.day}
    };

}

//insert a particular price at a given timestamp
TimeSeries.prototype.insert = function(timestampInSeconds, price){
    //iterate over all franularities
    for (var granularityName in this.granularities){
        var granularity = this.granularities[granularityName];
        //get a key name in the format "napespace:granularity:timestamp"
        //for ex.: "google:1sec:12"
        var key = this._getKeyName(granularity, timestampInSeconds);
        //execute the SET command
        this.client.set(key, price);
        //the EXPIRE command
        //pass the key and the ttl
        //this command deletes a redis key automatically after a given number
        //of seconds
        if(granularity.ttl !== null){
            this.client.expire(key, granularity.ttl);
        }
    }
};

//returns a key based on granularitu and timestamp
TimeSeries.prototype._getKeyName = function(granularity, timestampInSeconds){
    var roundedTimestamp = this._getRoundedTimestamp(timestampInSeconds, granularity.duration);
    return [this.namespace, granularity.name, roundedTimestamp].join(':');
};

//returns a normalized timestamp by granularity duration.
//For example, all inserts that happen in the first minute of an hour are stored
//in a key like "namespace:1min:0". All inserts from the second minute are stored
//in the "namespace:1min:60", and so on
TimeSeries.prototype._getRoundedTimestamp = function(timestampInSeconds, precision){
    return Math.floor(timestampInSeconds / precision) * precision;
};

//executes a callback by passing an array of data points
TimeSeries.prototype.fetch = function(granularityName, beginTimestamp, endTimestamp, onComplete){

    var granularity = this.granularities[granularityName];
    var begin = this._getRoundedTimestamp(beginTimestamp, granularity.duration);
    var end = this._getRoundedTimestamp(endTimestamp, granularity.duration);
    var keys = [];

    //iterate over all the timestamps in the specified range and save their values
    //in the "keys" variable
    for(var timestamp = begin; timestamp <= end; timestamp += granularity.duration){
        var key = this._getKeyName(granularity, timestamp);
        keys.push(key);
    }

    //the MGET command
    this.client.mget(keys, function(err, replies){
        var results = [];
        //iterate over all replies
        for(var i = 0; i < replies.length; i++){
            var timestamp = beginTimestamp + i * granularity.duration;
            //convert value to an integer
            var value = parseInt(replies[i], 10) || 0;
            //save timestamp and value in the "results" variable
            results.push({timestamp: timestamp, value:value});
        }
        //execute callback passing the variables "granularityName" and "results"
        onComplete(granularityName, results);
    });

};

//make a function available as a module in Node.js
exports.TimeSeries = TimeSeries;

Now create a file called using-stock-timeseries.js, which will illustrate how to use our library. This file inserts stock quotes for a TimeSeries called “GAZPROM”, and then fetches values from a different granularities. Before inserting data, we remove all existing keys.


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

//the FLUSHALL command
//removes all of the data from Redis
client.flushall();

var timeseries = require("./stock-timeseries");
//create a TimeSeries object passing the redis client and the "GAZPROM" namespace
//as an argument
var StocksGazprom = new timeseries.TimeSeries(client, "GAZPROM");

//this timestamp value was chosen to make it easier to read the output
var beginTimestamp = 0;

//execute the insert function
StocksGazprom.insert(beginTimestamp, 10);
//execute the insert function, passing a timestamp that is 1 second after "beginTimestamp"
StocksGazprom.insert(beginTimestamp + 1, 11);
StocksGazprom.insert(beginTimestamp + 2, 12);
StocksGazprom.insert(beginTimestamp + 3, 13);
StocksGazprom.insert(beginTimestamp + 4, 14);

//callback for displaying the output of the "fetch" function
function displayResults(granularityName, results){
    console.log("Results from", granularityName,":");
    console.log("Timestamp   | Value");
    console.log("----------  | ------");
    for(var i = 0; i < results.length; i++){
        console.log('\t' + results[i].timestamp + '\t' + results[i].value);
    }
    console.log();
}

//retrieve an interval of 5 seconds
StocksGazprom.fetch("1sec", beginTimestamp, beginTimestamp + 4, displayResults);
//retrieve an interval of 5 minutes
StocksGazprom.fetch("1min", beginTimestamp, beginTimestamp + 4, displayResults);

client.quit();

Now run your Redis server via redis-server. Then run node using-stock-timeseries.js. You should see the following output:

t3redis

That’s all for today :)