Redisでアクセスランキングを実装

redis

ニュースサイトのサイドメニューでよく見かける「アクセスの多かった記事」のようなランキングを Redis のデータ型 Sorted Set で実装する方法をメモ。

東洋経済の例
東洋経済:アクセスランキング

Redis の Sorted Set を使ったアクセスランクの表現

Redis のデータ型 sorted set は文字通り順序付けられた集合。
key 単位で集合を定義でき、各メンバーはスコアを持ち、スコアによって集合内で順位付けられる。
メンバーを記事、スコアをアクセス数とみなして、アクセスランクを表現する。

日別ランキングであれば下図のようになる。

redis-ranking-daily

週別ランキングであれば下図のようになる。

redis-ranking-weekly

スコアの大きい順(=アクセスの多い順)に並べればアクセスランキングの完成となる。

Sorted Set の操作

次にアクセスされた時の Sorted Set の操作を考える。

キーは YYYYMMDD で持ち、アクセスされるたびに、記事のスコアを1増やす。

スコアを明示的に指定してメンバー追加するときは ZADD を使って ZADD key score member というようにやるけれど、1ずつインクレメントするので ZINCRBY をつかう。

コマンドのシンタックスは ZINCRBY key increment member

redis> ZADD myzset 1 "one"
(integer) 1
redis> ZINCRBY myzset 1 "one"
"2"
redis> ZINCRBY myzset 1 "two"
"1"
redis> ZRANGE myzset 0 -1 WITHSCORES
1) "two"
2) "1"
3) "one"
4) "2"

ZINCRBY myzset 1 "two" の例でもわかるように、メンバーが存在しない場合はスコアの初期値は 0 となる。

If member does not exist in the sorted set, it is added with increment as its score (as if its previous score was 0.0).
http://redis.io/commands/zincrby

日別集計

ZINCRBY を利用して、アクセスされるたびにスコアを増やす。

2014/09/01 のアクセス

# aaa にアクセス
redis> zincrby 20140901 1 aaa
"1"
redis> zrange 20140901 0 -1 withscores
1) "aaa"
2) "1"
# bbb にアクセス
redis> zincrby 20140901 1 bbb
"1"
# bbb にアクセス
redis> zincrby 20140901 1 bbb
"2"
# ccc にアクセス
redis> zincrby 20140901 1 ccc
"1"
# bbb にアクセス
redis> zincrby 20140901 1 bbb
"3"
# ccc にアクセス
redis> zincrby 20140901 1 ccc
"2"

2014/09/01 の記事別アクセス数をスコア順に表示
スコアを降順(REVERSE)で表示するために ZREVRANGEBYSCORE を使う。

redis> ZREVRANGEBYSCORE 20140901 +inf -inf withscores
1) "bbb"
2) "3"
3) "ccc"
4) "2"
5) "aaa"
6) "1"

2014/09/02 のアクセス

2014/09/02 のアクセスも同じようにして集計する

redis> zincrby 20140902 1 aaa
"1"
redis> zincrby 20140902 1 ddd
"1"
redis> zincrby 20140902 1 ddd
"2"
redis> zincrby 20140902 1 ddd
"3"
redis> zincrby 20140902 1 ddd
"4"

2014/09/02 の記事別アクセス数をスコア順に表示

redis> ZREVRANGEBYSCORE 20140902 +inf -inf withscores
1) "ddd"
2) "4"
3) "aaa"
4) "1"

2014/09/03 のアクセス

2014/09/03 のアクセスも同じようにして集計する

redis> zincrby 20140903 1 aaa
"1"
redis> zincrby 20140903 1 bbb
"1"
redis> zincrby 20140903 1 bbb
"2"
redis> zincrby 20140903 1 bbb
"3"
redis> zincrby 20140903 1 ddd
"1"
redis> zincrby 20140903 1 eee
"1"

2014/09/03 の記事別アクセス数をスコア順に表示

redis> ZREVRANGEBYSCORE 20140903 +inf -inf withscores
1) "bbb"
2) "3"
3) "eee"
4) "1"
5) "ddd"
6) "1"
7) "aaa"
8) "1"

週別集計

次に日別集計を集約して週別集計することを考える。

Sorted Set 型の日別ランキングを再集計をする(和集合を取る)には ZUNIONSTORE を使う

コマンドのシンタックスは ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]

2014/09/01-2014/09/03 のアクセスは下記表のようになっている。

redis-ranking-daily

ZUNIONSTORE において 3日分の集計があるので numkeys は 3、 key には 20140901, 20140902, 20140903、destination には週次ランキング用のキー(weekly)を指定する。

redis> zunionstore weekly 3 20140901 20140902 20140903 aggregate sum
(integer) 5

redis-zunionstore

日次ランキングを集約した週次ランキングをスコア(アクセス数)でソートして表示する。

redis> ZREVRANGEBYSCORE weekly +inf -inf withscores
 1) "bbb"
 2) "6"
 3) "ddd"
 4) "5"
 5) "aaa"
 6) "3"
 7) "ccc"
 8) "2"
 9) "eee"
10) "1"

トップ3が欲しい場合はオフセット(LIMIT offset count)を指定する。

redis> ZREVRANGEBYSCORE weekly +inf -inf withscores limit 0 3
1) "bbb"
2) "6"
3) "ddd"
4) "5"
5) "aaa"
6) "3"

アクセス数の多い上位3件だけが残った。
めでたしめでたし。

キーの削除

インメモリ KVS な Redis は鮮度の高いデータ処理に特化させ、古くなったデータは削除したい。
sorted set のキーには TTL を設定できないので、明示的に DEL コマンドで削除しなければいけない。

KEYS コマンドでは次のようにワイルドカードでキー一覧を取得できる。

# キー一覧を取得
redis> keys *
1) "20140903"
2) "20140901"
3) "20140902"
4) "weekley"
# prefix が 2014090 のキー一覧を取得
redis> keys 2014090*
1) "20140903"
2) "20140901"
3) "20140902"

このようにワイルドカードで引っ掛けて DEL するか、プログラマブルに削除されるべきキーを生成して DEL すればよい。

備考

zunionstoreとzinterstoreの違い

zunionstore のかわりに zinterstore を使うと、指定したキーすべてに存在するメンバーだけが集約される。

redis> zinterstore inter 3 20140901 20140902 20140903 aggregate su(integer) 1
redis> ZREVRANGEBYSCORE inter +inf -inf withscores
1) "aaa"
2) "3"

要件によっては union(∪) ではなく intersection(∩) が求められるので、適宜使い分けること。

zunionstore 可能なキーの数

ZUNIONSTORE foo 100000 key1 key2 ... key100000 と大量の集合の和集合もとれるのか?
下の投稿によると 10,000 ではエラーにならなかったということなので、実運用では問題なさそうだけど、本運用するのであれば、コードを確認するなりして上限値の裏をとっておきたいところ。
https://groups.google.com/forum/#!topic/redis-db/j3iN2oPtwXw

Advertisements
Tagged with: , ,
Posted in database

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Archives
%d bloggers like this: