HyperLogLogの概要とメリット
Webサービスにおいて、特定のページのユニークユーザー数(UV)を計測することは非常に一般的な要件です。単純なアクセス数(PV)であればカウンタをインクリメントするだけで済みますが、重複を排除したUV計数の場合、単純なアプローチではメモリ効率が課題となります。
例えば、RedisのSet型を使用してユーザーIDを保存する方法があります。この方法は正確ですが、ユーザー数が数千万人規模になると、Setが消費するメモリ量は膨大になります。一方で、統計データとしてのUV計測では、厳密な正確性よりも、わずかな誤差を許容しつつリソースを節約することが求められる場面が多くあります。
RedisのHyperLogLogは、まさにこの課題を解決するためのデータ構造です。各キーにつきわずか12KBという極めて小さい固定メモリ消費で、数億件以上のユニーク要素をカウントできます。標準誤差は0.81%と非常に低く、実用的な統計用途には十分な精度を提供します。
主要な操作コマンド
1. PFADD
PFADD key [element [element ...]]
指定した要素をHyperLogLog構造に追加します。内部の推定値が更新された場合は1を返し、すでに存在する要素などで推定値に変化がない場合は0を返します。キーが存在しない場合は、新しいデータ構造が自動的に作成されます。
2. PFCOUNT
PFCOUNT key [key ...]
単一のキーを指定した場合、そのHyperLogLogが保持する近似的な基数(ユニークな要素数)を返します。複数のキーを指定した場合は、それらをマージした一時的なHyperLogLogの近似基数を計算して返します。
3. PFMERGE
PFMERGE destkey sourcekey [sourcekey ...]
複数のソースHyperLogLogを一つのターゲットキー(destkey)に統合します。これにより、例えば「日別のUVデータ」を統合して「週間のUVデータ」を算出するといった操作が効率的に行えます。
Spring Data Redisによる実装例
JavaのRedisTemplateを使用してHyperLogLogを操作する場合、opsForHyperLogLog()を利用します。以下に、特定のコンテンツへのアクセスを記録し、その統計を取得する実装例を示します。
// ユーザーの訪問を記録する(要素の追加)
public void recordUserVisit(String contentId, String userId) {
String redisKey = "analytics:uv:" + contentId;
// 戻り値は推定値が変化したかどうか
Long result = redisTemplate.opsForHyperLogLog().add(redisKey, userId);
}
// 現在のユニークユーザー数を取得する
public long getUniqueVisitorCount(String contentId) {
String redisKey = "analytics:uv:" + contentId;
return redisTemplate.opsForHyperLogLog().size(redisKey);
}
// 複数の期間データをマージする
public void consolidateStats(String targetKey, List<String> sourceKeys) {
redisTemplate.opsForHyperLogLog().union(targetKey, sourceKeys.toArray(new String[0]));
}
HyperLogLogは、ランキングのユニーク閲覧者数、検索クエリのユニーク数、リアルタイムのアクセス統計など、大量のストリームデータを扱う際の強力な武器となります。メモリコストを劇的に抑えつつ、スケーラブルな分析基盤を構築する際に非常に有効です。