1. 程式人生 > >第三章:Time Series (A Collection of Observations)

第三章:Time Series (A Collection of Observations)

Chapter 3. Time Series (A Collection of Observations)

時間序列(Time Series)是在一段時間範圍內排序的時間點的值,可用於統計,社交網路,電信工程等一切需要時間度量的場景。它可以來進行預測,如股市走向,環境條件變化等。stock market changes, real estate trends, environmental conditions, and more.

時間序列的例子包括:
* 隨時間變化,報紙中特定詞語的使用
* 年度最低工資變化
* 每日股價波動
* 按月產品購買趨勢
* 氣候變化

時間序列面臨儲存的挑戰,例如如果每秒存一個時間,每天則需86,400個時間點,長時間儲存壓力更大,對於記憶體資料庫如Redis更是如此。

另一個問題是,隨時間推移,之前細粒度的時間點失去價值,例如在後期的統計中,秒級的時間點已沒有意義,通常用小時就夠了。

New York Times提供了一個有趣的工具,可統計自1851年起,在報紙中使用的特定詞語的統計圖。可參見http://chronicle.nytlabs.com
下面是對於china關鍵詞的年度統計圖,不能按月或按天:

Building the foundation

本節演示用Strings, Hash, Sorted Sets 和 HyperLogLogs實現時間序列。

時間粒度支援日,小時,分鐘,秒,每一個粒度一個key,當事件發生時,每一個key都需要更新

時間按時間粒度分組,例如某一秒發生的時間會同時歸類到相應的分鐘,小時和天。

此解決方案非常適合於實時計數,如頁面瀏覽,視訊觀看,點選數,購買商品數等。

此程式的執行方式如下,在命令列中可指定資料型別從而對應相應的實現,但結果是一樣的。

[[email protected] examples]$ cd chapter\ 3
[[email protected] chapter 3]$ ls
timeseries-hash.js         timeseries-sorted-set.js  using-timeseries.js
timeseries-hyperloglog.js  timeseries-string.js      using-timeseries-unique.js
[
[email protected]
chapter 3]$ node using-timeseries.js string Results from 1sec: Timestamp | Value --------------- | ------ 0 | 1 1 | 2 2 | 0 3 | 1 4 | 0 Results from 1min: Timestamp | Value --------------- | ------ 0 | 4 60 | 1 120 | 0 [[email protected] chapter 3]$ node using-timeseries.js hash Results from 1sec: Timestamp | Value --------------- | ------ 0 | 1 1 | 2 2 | 0 3 | 1 4 | 0 Results from 1min: Timestamp | Value --------------- | ------ 0 | 4 60 | 1 120 | 0

為了避免存太多的資料,每一個key都設定了過期時間,如秒級只保留2小時,分鐘級保留7天等,小時級保留60天,天級永不過期.

當事件發生時,每一個key都會被更新, 如下:

TimeSeries.prototype.insert = function(timestampInSeconds) { // 1
  for (var granularityName in this.granularities) { // 2
    var granularity = this.granularities[granularityName]; // 3
    var key = this._getKeyName(granularity, timestampInSeconds); // 4
    this.client.incr(key); // 5
    if (granularity.ttl !== null) { // 6
      this.client.expire(key, granularity.ttl); // 7
    }
  }
};

執行以下的程式碼產生時間序列:

item1Purchases.insert(beginTimestamp); // 7
item1Purchases.insert(beginTimestamp + 1); // 8
item1Purchases.insert(beginTimestamp + 1); // 9
item1Purchases.insert(beginTimestamp + 3); // 10
item1Purchases.insert(beginTimestamp + 61); // 11

通過redis-cli monitor可以看到實際執行的命令:

1462417516.878865 [0 127.0.0.1:63610] "info"
1462417516.884225 [0 127.0.0.1:63610] "flushall"
1462417516.917339 [0 127.0.0.1:63610] "incr" "purchases:item1:1sec:0"
1462417516.917352 [0 127.0.0.1:63610] "expire" "purchases:item1:1sec:0" "7200"
1462417516.917359 [0 127.0.0.1:63610] "incr" "purchases:item1:1min:0"
1462417516.917363 [0 127.0.0.1:63610] "expire" "purchases:item1:1min:0" "604800"
1462417516.917369 [0 127.0.0.1:63610] "incr" "purchases:item1:1hour:0"
1462417516.917373 [0 127.0.0.1:63610] "expire" "purchases:item1:1hour:0" "5184000"
1462417516.917378 [0 127.0.0.1:63610] "incr" "purchases:item1:1day:0"

1462417516.917382 [0 127.0.0.1:63610] "incr" "purchases:item1:1sec:1"
1462417516.917386 [0 127.0.0.1:63610] "expire" "purchases:item1:1sec:1" "7200"
1462417516.917417 [0 127.0.0.1:63610] "incr" "purchases:item1:1min:0"
1462417516.917425 [0 127.0.0.1:63610] "expire" "purchases:item1:1min:0" "604800"
1462417516.917430 [0 127.0.0.1:63610] "incr" "purchases:item1:1hour:0"
1462417516.917435 [0 127.0.0.1:63610] "expire" "purchases:item1:1hour:0" "5184000"
1462417516.917440 [0 127.0.0.1:63610] "incr" "purchases:item1:1day:0"

1462417516.917444 [0 127.0.0.1:63610] "incr" "purchases:item1:1sec:1"
1462417516.917448 [0 127.0.0.1:63610] "expire" "purchases:item1:1sec:1" "7200"
1462417516.917467 [0 127.0.0.1:63610] "incr" "purchases:item1:1min:0"
1462417516.917471 [0 127.0.0.1:63610] "expire" "purchases:item1:1min:0" "604800"
1462417516.917476 [0 127.0.0.1:63610] "incr" "purchases:item1:1hour:0"
1462417516.917481 [0 127.0.0.1:63610] "expire" "purchases:item1:1hour:0" "5184000"
1462417516.917486 [0 127.0.0.1:63610] "incr" "purchases:item1:1day:0"

1462417516.917490 [0 127.0.0.1:63610] "incr" "purchases:item1:1sec:3"
1462417516.917494 [0 127.0.0.1:63610] "expire" "purchases:item1:1sec:3" "7200"
1462417516.917499 [0 127.0.0.1:63610] "incr" "purchases:item1:1min:0"
1462417516.917503 [0 127.0.0.1:63610] "expire" "purchases:item1:1min:0" "604800"
1462417516.917508 [0 127.0.0.1:63610] "incr" "purchases:item1:1hour:0"
1462417516.917513 [0 127.0.0.1:63610] "expire" "purchases:item1:1hour:0" "5184000"
1462417516.917518 [0 127.0.0.1:63610] "incr" "purchases:item1:1day:0"

1462417516.917522 [0 127.0.0.1:63610] "incr" "purchases:item1:1sec:61"
1462417516.917526 [0 127.0.0.1:63610] "expire" "purchases:item1:1sec:61" "7200"
1462417516.917530 [0 127.0.0.1:63610] "incr" "purchases:item1:1min:60"
1462417516.917534 [0 127.0.0.1:63610] "expire" "purchases:item1:1min:60" "604800"
1462417516.917539 [0 127.0.0.1:63610] "incr" "purchases:item1:1hour:0"
1462417516.917550 [0 127.0.0.1:63610] "expire" "purchases:item1:1hour:0" "5184000"
1462417516.917556 [0 127.0.0.1:63610] "incr" "purchases:item1:1day:0"

1462417516.917562 [0 127.0.0.1:63610] "mget" "purchases:item1:1sec:0" "purchases:item1:1sec:1" "purchases:item1:1sec:2" "purchases:item1:1sec:3" "purchases:item1:1sec:4"
1462417516.917576 [0 127.0.0.1:63610] "mget" "purchases:item1:1min:0" "purchases:item1:1min:60" "purchases:item1:1min:120"

這個例子可以很方便的改成記錄股價變動,只需把INCRBY改為SET,把計數改為設定股價。

Optimizing with Hashes

前面String的實現每天會產生87,865個key (60 * 60 * 24 + 60 * 24 + 24 + 1),太多的key除錯不方便,也消耗記憶體(每日11MB),因此我們用Hash的ziplist來優化(每日800KB)。
具體的benchmark可參見:https://gist.github.com/hltbra/2fbf5310aabbecee68c5

Hash要使用ziplist需要同時滿足以下條件, 否則就會用hash table:

It must have fewer fields than the threshold Set in the configuration hash-max-ziplist-entries. The default value for hash-max-ziplist-entries is 512.
No field value can be bigger than hash-max-ziplist-value. The default value for hash-max-ziplist-value is 64 bytes.

Note

前述String的實現每秒至少產生一個key,為了使用Hash介紹記憶體空間,我們需要把多個key置於一個組中, 每組中key的數量小於512。分組的依據如下:

1sec granularity: Stores a maximum of 300 timestamps of 1 second each (5 minutes of data points)
1min granularity: Stores a maximum of 480 timestamps of 1 minute each (8 hours of data points)
1hour granularity: Stores a maximum of 240 timestamps of 1 hour each (10 days of data points)
1day granularity: Stores a maximum of 30 timestamps of 1 day each (30 days of data points)

大部分程式碼是相同的,關鍵是利用hash進行了分割槽,相當於建立多個bucket。
通過redis-cli monitor可以看到實際執行的命令:

[[email protected] ~]$ redis-cli monitor
OK
1462418086.839729 [0 127.0.0.1:63620] "info"
1462418086.843865 [0 127.0.0.1:63620] "flushall"
1462418086.894362 [0 127.0.0.1:63620] "hincrby" "purchases:item1:1sec:0" "0" "1"
1462418086.894425 [0 127.0.0.1:63620] "expire" "purchases:item1:1sec:0" "7200"
1462418086.894437 [0 127.0.0.1:63620] "hincrby" "purchases:item1:1min:0" "0" "1"
1462418086.894446 [0 127.0.0.1:63620] "expire" "purchases:item1:1min:0" "604800"
1462418086.894455 [0 127.0.0.1:63620] "hincrby" "purchases:item1:1hour:0" "0" "1"
1462418086.894464 [0 127.0.0.1:63620] "expire" "purchases:item1:1hour:0" "5184000"
1462418086.894473 [0 127.0.0.1:63620] "hincrby" "purchases:item1:1day:0" "0" "1"

1462418086.894483 [0 127.0.0.1:63620] "hincrby" "purchases:item1:1sec:0" "1" "1"
1462418086.894492 [0 127.0.0.1:63620] "expire" "purchases:item1:1sec:0" "7200"
1462418086.894500 [0 127.0.0.1:63620] "hincrby" "purchases:item1:1min:0" "0" "1"
1462418086.894511 [0 127.0.0.1:63620] "expire" "purchases:item1:1min:0" "604800"
1462418086.894520 [0 127.0.0.1:63620] "hincrby" "purchases:item1:1hour:0" "0" "1"
1462418086.894529 [0 127.0.0.1:63620] "expire" "purchases:item1:1hour:0" "5184000"
1462418086.894537 [0 127.0.0.1:63620] "hincrby" "purchases:item1:1day:0" "0" "1"

1462418086.894546 [0 127.0.0.1:63620] "hincrby" "purchases:item1:1sec:0" "1" "1"
1462418086.894556 [0 127.0.0.1:63620] "expire" "purchases:item1:1sec:0" "7200"
1462418086.894564 [0 127.0.0.1:63620] "hincrby" "purchases:item1:1min:0" "0" "1"
1462418086.894573 [0 127.0.0.1:63620] "expire" "purchases:item1:1min:0" "604800"
1462418086.894581 [0 127.0.0.1:63620] "hincrby" "purchases:item1:1hour:0" "0" "1"
1462418086.894590 [0 127.0.0.1:63620] "expire" "purchases:item1:1hour:0" "5184000"
1462418086.894598 [0 127.0.0.1:63620] "hincrby" "purchases:item1:1day:0" "0" "1"

1462418086.894606 [0 127.0.0.1:63620] "hincrby" "purchases:item1:1sec:0" "3" "1"
1462418086.894615 [0 127.0.0.1:63620] "expire" "purchases:item1:1sec:0" "7200"
1462418086.894623 [0 127.0.0.1:63620] "hincrby" "purchases:item1:1min:0" "0" "1"
1462418086.894631 [0 127.0.0.1:63620] "expire" "purchases:item1:1min:0" "604800"
1462418086.894639 [0 127.0.0.1:63620] "hincrby" "purchases:item1:1hour:0" "0" "1"
1462418086.894648 [0 127.0.0.1:63620] "expire" "purchases:item1:1hour:0" "5184000"
1462418086.894656 [0 127.0.0.1:63620] "hincrby" "purchases:item1:1day:0" "0" "1"

1462418086.894665 [0 127.0.0.1:63620] "hincrby" "purchases:item1:1sec:0" "61" "1"
1462418086.894673 [0 127.0.0.1:63620] "expire" "purchases:item1:1sec:0" "7200"
1462418086.894681 [0 127.0.0.1:63620] "hincrby" "purchases:item1:1min:0" "60" "1"
1462418086.894690 [0 127.0.0.1:63620] "expire" "purchases:item1:1min:0" "604800"
1462418086.894699 [0 127.0.0.1:63620] "hincrby" "purchases:item1:1hour:0" "0" "1"
1462418086.894709 [0 127.0.0.1:63620] "expire" "purchases:item1:1hour:0" "5184000"
1462418086.894718 [0 127.0.0.1:63620] "hincrby" "purchases:item1:1day:0" "0" "1"
1462418086.894726 [0 127.0.0.1:63620] "multi"
1462418086.894759 [0 127.0.0.1:63620] "hget" "purchases:item1:1sec:0" "0"
1462418086.894768 [0 127.0.0.1:63620] "hget" "purchases:item1:1sec:0" "1"
1462418086.894774 [0 127.0.0.1:63620] "hget" "purchases:item1:1sec:0" "2"
1462418086.894780 [0 127.0.0.1:63620] "hget" "purchases:item1:1sec:0" "3"
1462418086.894786 [0 127.0.0.1:63620] "hget" "purchases:item1:1sec:0" "4"
1462418086.894793 [0 127.0.0.1:63620] "exec"
1462418086.894795 [0 127.0.0.1:63620] "multi"
1462418086.894800 [0 127.0.0.1:63620] "hget" "purchases:item1:1min:0" "0"
1462418086.894806 [0 127.0.0.1:63620] "hget" "purchases:item1:1min:0" "60"
1462418086.894812 [0 127.0.0.1:63620] "hget" "purchases:item1:1min:0" "120"
1462418086.894826 [0 127.0.0.1:63620] "exec"

可以看到,本例所有的秒級都存入了key: purchases:item1:1sec:0中,可以存300個時間點,然後具體的秒在field中表示。

Adding uniqueness with Sorted Sets and HyperLogLog

本節使用Sorted Sets 和 HyperLogLog實現統計唯一(distinct)訪問, 利用了這兩個資料型別沒有重複值的特性。Sorted Set 100%準確,而HyperLogLog使用較少記憶體,但準確率只有99.19%。具體使用哪個就看你的需要了。

這兩個例子和前兩個例子的區別是,前面的是計訪問總數,而這兩個只計唯一的訪問(同一使用者的重複訪問不會計入)

要讓Sorted Sets(zset)有效率的使用記憶體,需要一個zset中的field數量小於128,類似於前面Hash的例子,這裡也做了key grouping,相當於做了分割槽。

通過redis-cli monitor可以看到實際執行的命令:

[[email protected] chapter 3]$ node using-timeseries-unique.js sorted-set

[[email protected] ~]$ redis-cli monitor
OK
1462419249.646618 [0 127.0.0.1:63625] "info"
1462419249.650224 [0 127.0.0.1:63625] "flushall"
1462419249.689717 [0 127.0.0.1:63625] "zadd" "concurrentplays:1sec:0" "0" "0:user:max"
1462419249.690007 [0 127.0.0.1:63625] "expire" "concurrentplays:1sec:0" "7200"
1462419249.690033 [0 127.0.0.1:63625] "zadd" "concurrentplays:1min:0" "0" "0:user:max"
1462419249.690053 [0 127.0.0.1:63625] "expire" "concurrentplays:1min:0" "604800"
1462419249.690067 [0 127.0.0.1:63625] "zadd" "concurrentplays:1hour:0" "0" "0:user:max"
1462419249.690083 [0 127.0.0.1:63625] "expire" "concurrentplays:1hour:0" "5184000"
1462419249.690098 [0 127.0.0.1:63625] "zadd" "concurrentplays:1day:0" "0" "0:user:max"

1462419249.690114 [0 127.0.0.1:63625] "zadd" "concurrentplays:1sec:0" "0" "0:user:max"
1462419249.690130 [0 127.0.0.1:63625] "expire" "concurrentplays:1sec:0" "7200"
1462419249.690144 [0 127.0.0.1:63625] "zadd" "concurrentplays:1min:0" "0" "0:user:max"
1462419249.690159 [0 127.0.0.1:63625] "expire" "concurrentplays:1min:0" "604800"
1462419249.690173 [0 127.0.0.1:63625] "zadd" "concurrentplays:1hour:0" "0" "0:user:max"
1462419249.690187 [0 127.0.0.1:63625] "expire" "concurrentplays:1hour:0" "5184000"
1462419249.690201 [0 127.0.0.1:63625] "zadd" "concurrentplays:1day:0" "0" "0:user:max"

1462419249.690215 [0 127.0.0.1:63625] "zadd" "concurrentplays:1sec:0" "1" "1:user:hugo"
1462419249.690235 [0 127.0.0.1:63625] "expire" "concurrentplays:1sec:0" "7200"
1462419249.690248 [0 127.0.0.1:63625] "zadd" "concurrentplays:1min:0" "0" "0:user:hugo"
1462419249.690265 [0 127.0.0.1:63625] "expire" "concurrentplays:1min:0" "604800"
1462419249.690279 [0 127.0.0.1:63625] "zadd" "concurrentplays:1hour:0" "0" "0:user:hugo"
1462419249.690296 [0 127.0.0.1:63625] "expire" "concurrentplays:1hour:0" "5184000"
1462419249.690309 [0 127.0.0.1:63625] "zadd" "concurrentplays:1day:0" "0" "0:user:hugo"

1462419249.690325 [0 127.0.0.1:63625] "zadd" "concurrentplays:1sec:0" "1" "1:user:renata"
1462419249.690342 [0 127.0.0.1:63625] "expire" "concurrentplays:1sec:0" "7200"
1462419249.690382 [0 127.0.0.1:63625] "zadd" "concurrentplays:1min:0" "0" "0:user:renata"
1462419249.690401 [0 127.0.0.1:63625] "expire" "concurrentplays:1min:0" "604800"
1462419249.690415 [0 127.0.0.1:63625] "zadd" "concurrentplays:1hour:0" "0" "0:user:renata"
1462419249.690431 [0 127.0.0.1:63625] "expire" "concurrentplays:1hour:0" "5184000"
1462419249.690445 [0 127.0.0.1:63625] "zadd" "concurrentplays:1day:0" "0" "0:user:renata"

1462419249.690461 [0 127.0.0.1:63625] "zadd" "concurrentplays:1sec:0" "3" "3:user:hugo"
1462419249.690477 [0 127.0.0.1:63625] "expire" "concurrentplays:1sec:0" "7200"
1462419249.690490 [0 127.0.0.1:63625] "zadd" "concurrentplays:1min:0" "0" "0:user:hugo"
1462419249.690504 [0 127.0.0.1:63625] "expire" "concurrentplays:1min:0" "604800"
1462419249.690518 [0 127.0.0.1:63625] "zadd" "concurrentplays:1hour:0" "0" "0:user:hugo"
1462419249.690532 [0 127.0.0.1:63625] "expire" "concurrentplays:1hour:0" "5184000"
1462419249.690545 [0 127.0.0.1:63625] "zadd" "concurrentplays:1day:0" "0" "0:user:hugo"

1462419249.690559 [0 127.0.0.1:63625] "zadd" "concurrentplays:1sec:0" "61" "61:user:kc"
1462419249.690576 [0 127.0.0.1:63625] "expire" "concurrentplays:1sec:0" "7200"
1462419249.690589 [0 127.0.0.1:63625] "zadd" "concurrentplays:1min:0" "60" "60:user:kc"
1462419249.690605 [0 127.0.0.1:63625] "expire" "concurrentplays:1min:0" "604800"
1462419249.690618 [0 127.0.0.1:63625] "zadd" "concurrentplays:1hour:0" "0" "0:user:kc"
1462419249.690634 [0 127.0.0.1:63625] "expire" "concurrentplays:1hour:0" "5184000"
1462419249.690648 [0 127.0.0.1:63625] "zadd" "concurrentplays:1day:0" "0" "0:user:kc"
1462419249.690669 [0 127.0.0.1:63625] "multi"
1462419249.690695 [0 127.0.0.1:63625] "zcount" "concurrentplays:1sec:0" "0" "0"
1462419249.690842 [0 127.0.0.1:63625] "zcount" "concurrentplays:1sec:0" "1" "1"
1462419249.690860 [0 127.0.0.1:63625] "zcount" "concurrentplays:1sec:0" "2" "2"
1462419249.690872 [0 127.0.0.1:63625] "zcount" "concurrentplays:1sec:0" "3" "3"
1462419249.690883 [0 127.0.0.1:63625] "zcount" "concurrentplays:1sec:0" "4" "4"
1462419249.690897 [0 127.0.0.1:63625] "exec"
1462419249.690902 [0 127.0.0.1:63625] "multi"
1462419249.690911 [0 127.0.0.1:63625] "zcount" "concurrentplays:1min:0" "0" "0"
1462419249.690923 [0 127.0.0.1:63625] "zcount" "concurrentplays:1min:0" "60" "60"
1462419249.690936 [0 127.0.0.1:63625] "zcount" "concurrentplays:1min:0" "120" "120"
1462419249.690949 [0 127.0.0.1:63625] "exec"

可以看到,key的設計和前面的例子是一樣的,score中存的是時間,而不是累計的訪問次數,field中存的使用者名稱,但不明白為何要加一個時間字首。
幾個命令解釋一下,zcount用來計數,multi和exec用於交易

127.0.0.1:6379> help zcount

  ZCOUNT key min max
  summary: Count the members in a sorted set with scores within the given values
  since: 2.0.0
  group: sorted_set

127.0.0.1:6379> help multi

  MULTI -
  summary: Mark the start of a transaction block
  since: 1.2.0
  group: transactions

127.0.0.1:6379> help exec

  EXEC -
  summary: Execute all commands issued after MULTI
  since: 1.2.0
  group: transactions

HyperLogLog 的實現無需key grouping, 類似於String, 實現非常簡潔!每一個timestamp一個key。並用PFADD代替了INCRBY, 以及PFCOUNT 替代了 MGET.

通過redis-cli monitor可以看到實際執行的命令:

[[email protected] chapter 3]$ node using-timeseries-unique.js hyperloglog

[[email protected] ~]$ redis-cli monitor
OK
1462421278.494144 [0 127.0.0.1:63631] "info"
1462421278.498276 [0 127.0.0.1:63631] "flushall"
1462421278.556209 [0 127.0.0.1:63631] "pfadd" "concurrentplays:1sec:0" "user:max"
1462421278.556277 [0 127.0.0.1:63631] "expire" "concurrentplays:1sec:0" "7200"
1462421278.556300 [0 127.0.0.1:63631] "pfadd" "concurrentplays:1min:0" "user:max"
1462421278.556315 [0 127.0.0.1:63631] "expire" "concurrentplays:1min:0" "604800"
1462421278.556330 [0 127.0.0.1:63631] "pfadd" "concurrentplays:1hour:0" "user:max"
1462421278.556344 [0 127.0.0.1:63631] "expire" "concurrentplays:1hour:0" "5184000"
1462421278.556358 [0 127.0.0.1:63631] "pfadd" "concurrentplays:1day:0" "user:max"

1462421278.556371 [0 127.0.0.1:63631] "pfadd" "concurrentplays:1sec:0" "user:max"
1462421278.556386 [0 127.0.0.1:63631] "expire" "concurrentplays:1sec:0" "7200"
1462421278.556399 [0 127.0.0.1:63631] "pfadd" "concurrentplays:1min:0" "user:max"
1462421278.556412 [0 127.0.0.1:63631] "expire" "concurrentplays:1min:0" "604800"
1462421278.556427 [0 127.0.0.1:63631] "pfadd" "concurrentplays:1hour:0" "user:max"
1462421278.556440 [0 127.0.0.1:63631] "expire" "concurrentplays:1hour:0" "5184000"
1462421278.556454 [0 127.0.0.1:63631] "pfadd" "concurrentplays:1day:0" "user:max"

1462421278.556466 [0 127.0.0.1:63631] "pfadd" "concurrentplays:1sec:1" "user:hugo"
1462421278.556481 [0 127.0.0.1:63631] "expire" "concurrentplays:1sec:1" "7200"
1462421278.556495 [0 127.0.0.1:63631] "pfadd" "concurrentplays:1min:0" "user:hugo"
1462421278.556509 [0 127.0.0.1:63631] "expire" "concurrentplays:1min:0" "604800"
1462421278.556523 [0 127.0.0.1:63631] "pfadd" "concurrentplays:1hour:0" "user:hugo"
1462421278.556537 [0 127.0.0.1:63631] "expire" "concurrentplays:1hour:0" "5184000"
1462421278.556550 [0 127.0.0.1:63631] "pfadd<