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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
//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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
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:
That’s all for today 🙂