Redis HyperLogLogによる効率的なユニークユーザー数計数

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は、ランキングのユニーク閲覧者数、検索クエリのユニーク数、リアルタイムのアクセス統計など、大量のストリームデータを扱う際の強力な武器となります。メモリコストを劇的に抑えつつ、スケーラブルな分析基盤を構築する際に非常に有効です。

タグ: redis HyperLogLog Java Spring Data Redis Big Data

8月23日 15:50 投稿