前回紹介したRedisのLIST型に続き、
SET型の構造
RedisのSET型は、
LIST型のPUSHやPOPと同様、
一見、
実は、
集合演算
SET型には3種類の集合演算コマンドが用意されています。積集合

上図では2つのセット間の演算を示していますが、
また、
SET型を使ったタグ検索
SET型の基本操作にひと通り触れてみるために、
各タグごとに、

基本的なタグの操作は、
<?php
// タグにコンテンツIDを関連付ける
function tagset_add($tag_id, $content_id) {
$redis = new Redis();
$redis->connect('localhost', 6379);
$redis->sAdd('tag:' . $tag_id, $content_id);
$redis->close();
}
// タグとコンテンツIDの関連を削除する
function tagset_remove($tag_id, $content_id) {
$redis = new Redis();
$redis->connect('localhost', 6379);
$redis->sRemove('tag:' . $tag_id, $content_id);
$redis->close();
}
// タグに関連するコンテンツIDを収得する
function tagset_contents($tag_id) {
$redis = new Redis();
$redis->connect('localhost', 6379);
$mems = $redis->sMembers('tag:' . $tag_id);
$redis->close();
return $mems;
}
SETへの要素の追加はSADDコマンド、
次に、
SINTER tag:dance tag:sing
実際のアプリケーションではもう少し後処理が必要になるかもしれません。例えば、
SINTERSTORE tmpset tag:dance tag:sing SDIFF tmpset tag:r18
SINTERSTOREコマンドで、
以上のコマンド操作をプログラムで実装すると次のようになります。なお、
<?php
function tagset_search($tag_id1, $tag_id2) {
$redis = new Redis();
$redis->connect('localhost', 6379);
// 指定されたタグのAND検索結果をtmpset1に格納
// SINTERSTORE tmpset1 タグ1 タグ2
$tmpset1 = /*適当なテンポラリキーを生成*/;
$redis->sInterStore($tmpset1,
'tag:' . $tag_id1,
'tag:' . $tag_id2);
// R18タグに関連するコンテンツを除外してtmpset2に格納
// SDIFFSTORE tmpset2 tmpset1 tag:r18
$tmpset2 = /*適当なテンポラリキーを生成*/;
$redis->sDiffStore($tmpset2, $tmpset1, 'tag:r18');
// アクセス数の多い順にソート
// SORT tmpset2 BY access_count:* DESC
$result = $redis->sort($tmpset2,
array('sort'=>'desc', 'by'=>'access_count:*'));
$redis->close();
return $result;
}
前回、
容量の問題
上記の手法によるタグ検索の問題は、
本来であればRedis VMによってある程度解決できる問題であったはずですが、
ただし、
ニコニコ生放送のリアルタイム視聴者集計
ニコニコ生放送の公式番組では、
従来この分析を行うには、
これらの分析情報は放送中だけRedisに格納しておけばよいので、
リアルタイム視聴者集計の仕組み
番組IDごとに、
性別 | userid:man:番組ID userid:woman:番組ID |
---|---|
年代 |
userid:20:番組ID userid:30:番組ID |
地域 |
userid:kantou:番組ID userid:touhoku:番組ID |
SETに格納した要素は自動的にユニークになるため、
SORTED SET型の構造
最後に紹介するデータ型は、

重複のない要素を保持する点や、
SORTED SET型ではさらに、
範囲指定取得
範囲指定取得の方法は2通りあり、
ZRANGE | インデックス範囲の要素を取得する。順序はスコアの昇順。 |
---|---|
ZREVRANGE | ZRANGEの降順バージョン |
ZRANGEBYSCORE | スコア範囲の要素を取得する。順序はスコアの昇順。 |
ZREVRANGEBYSCORE | ZRANGEBYSCOREの降順バージョン |
LIST型との使い分け
SORTED SET型では、
LIST型の場合、
ここで問題になるのは、
このため我々のシステムでは、
- LIST型の使用例
- リアルタイムログ
(追加順) - 開始時間順のユーザー番組一覧
(時系列順) - 更新通知キュー
(追加順)
- リアルタイムログ
- SORTED SET型の使用例
- 人気順番組ランキング
(視聴者数やコメント数順) - コンテンツタイムアウトリスト
(最終参照日時順)
- 人気順番組ランキング
コンテンツのアクセス数順リストの構築
前回のSORTコマンドの例で紹介した、
キー名
<?php
function contents_zset_add($content_id, $access_count) {
$redis = new Redis();
$redis->connect('localhost', 6379);
$redis->zAdd('contents_zset', $access_count, $content_id);
$redis->close();
}
アクセス数は最初、
既に追加済みにコンテンツIDに対して上記のコードを実行すると、
スコアのインクリメントにはZINCRBYコマンドを使います。
<?php
function contents_zset_countup($content_id) {
$redis = new Redis();
$redis->connect('localhost', 6379);
$redis->zIncrBy('contents_zset', 1, $content_id);
$redis->close();
}
最後に、
<?php
function contents_zset_top10() {
$redis = new Redis();
$redis->connect('localhost', 6379);
$contents = $redis->zRevRange('contents_zset', 0, 9, true);
$redis->close();
return $contents;
}
以上が、
LIST型もSORTED SET型も、
コンテンツのタイムアウト制御の実装
SORTED SET型の応用例として、
我々のサービスでは様々なコンテンツ情報をRedisに格納していますが、
このような場合、
- コンテンツIDを格納するSORTED SETを用意します。スコアとして、
そのコンテンツの最終参照時刻のタイムスタンプをセットします。 - コンテンツが参照されるたびに、
スコアを現在時刻で更新します。SORTED SETの内部は、 常に最終参照時刻で並んだ状態になります。 - ZRANGEBYSCOREコマンドを使って、
最終参照時刻が退避基準時刻 (例えば現在時刻の24時間前) よりも古いコンテンツIDを列挙します。 - 列挙されたコンテンツIDを元に、
関連する情報をRedisから回収/ 削除し、 必要な情報をMySQLにバックアップしていきます。
ニコニコ生放送のようなコンシューマー向けのWebサイトでは、
おわりに
4回に渡って、
著者がRedisを調べようと思ったのは、
しかし一方、
今後、