No history yet

高度なデータ構造と活用

Hash: 複雑なオブジェクトを効率的に保存する

RedisのString型にJSONをシリアライズして保存するのは一般的な手法ですが、オブジェクトの一部のフィールドだけを更新したい場合、JSON全体を読み込み、デシリアライズし、変更し、再度シリアライズして書き戻す必要があります。これは非効率です。

ここでHashデータ構造が役立ちます。Hashは、フィールドと値のペアを格納するマップのようなものです。ユーザープロフィールのような構造化されたデータを保存するのに最適です。

Lesson image

各フィールドを個別に更新できるため、ネットワークトラフィックと処理負荷が大幅に削減されます。例えば、ユーザーの最終ログイン日時だけを更新する場合、他のプロフィール情報を読み書きする必要はありません。

コマンド HSET はフィールドを設定し、HGET はフィールドを取得します。これらの操作の計算量は O(1)O(1) であり、非常に高速です。ただし、多数の小さなキーを持つよりも、一つのHashに多くのフィールドを格納する方がメモリ効率が良い場合があります。これは、Redisが小さなHashを zipmap または listpack と呼ばれる特殊なエンコーディングで圧縮して保存するためです。

コマンド HSET はフィールドを設定し、HGET はフィールドを取得します。これらの操作の計算量は O(1)O(1) であり、非常に高速です。ただし、多数の小さなキーを持つよりも、一つのHashに多くのフィールドを格納する方がメモリ効率が良い場合があります。これは、Redisが小さなHashを または と呼ばれる特殊なエンコーディングで圧縮して保存するためです。

Sorted Set: 動的ランキングの実装

ゲームのリーダーボードや人気記事ランキングのように、スコアに基づいて順位が常に変動するシステムを考えます。Sorted Set(ソート済みセット)は、このようなユースケースに最適なデータ構造です。

各メンバーは一意であり、それぞれにスコアが関連付けられています。メンバーはスコア順にソートされて保存されます。メンバーの追加、削除、スコアの更新は非常に効率的に行えます。

# ユーザー'player1'のスコアを1500に設定
ZADD leaderboard 1500 player1

# 'player2'のスコアを500点追加
ZINCRBY leaderboard 500 player2

# 上位3名を取得
ZREVRANGE leaderboard 0 2 WITHSCORES

Sorted Setは、内部的に skip list とハッシュテーブルの2つのデータ構造を組み合わせて実装されています。これにより、メンバーのスコアによる順位付けと、メンバー名によるスコアの高速な検索の両方を実現しています。

メンバーの追加やスコアの更新 (ZADD, ZINCRBY) は O(logN)O(\log N) です。ここで NN はセット内のメンバー数です。範囲指定での取得 (ZRANGE, ZREVRANGE) も O(logN+M)O(\log N + M) と高速です(MMは取得する要素数)。

特殊なデータ構造

Redisは、特定のユースケースに特化した、メモリ効率の高いデータ構造も提供しています。これらは従来のデータ構造では実現が難しい問題をエレガントに解決します。

大規模なデータセットを扱う際には、メモリ使用量とパフォーマンスのトレードオフを常に意識する必要があります。これから紹介するデータ構造は、その良い例です。

Bitmaps: 巨大なフラグ管理

数百万人のユーザーが毎日ログインしたかどうか、あるいは特定のアクションを実行したかどうかを追跡したいとします。ユーザーごとにキーを持つと、大量のメモリを消費します。

Bitmapsは、String型をビットの配列として扱うことで、この問題を解決します。各ユーザーに一意のオフセット(ID)を割り当て、対応するビットを1に設定するだけです。例えば、ID 100のユーザーがログインしたら、100番目のビットを立てます。

# ID 100 のユーザーがログインしたことを記録
SETBIT daily_logins:2024-10-26 100 1

# ID 100 のユーザーがログインしたか確認
GETBIT daily_logins:2024-10-26 100

# 特定の日にログインした総ユーザー数をカウント
BITCOUNT daily_logins:2024-10-26

SETBITGETBITO(1)O(1) です。100万ユーザーの状態を管理するのに必要なメモリは、わずか125KB程度(1,000,000ビット / 8)です。これはキーバリューストアで管理するより桁違いに効率的です。

HyperLogLog: ユニーク数の推定

Webサイトのユニークな訪問者数など、重複を除いた要素数を正確に数えるには、すべての要素をSetに保存する必要があります。しかし、要素数が億単位になると、メモリがすぐに枯渇します。

HyperLogLog は、非常に少ないメモリで集合のカーディナリティ(ユニークな要素数)を推定するための確率的データ構造です。

PFADD コマンドで要素を追加し、PFCOUNT で推定値を取得します。PFADDO(1)O(1) です。HyperLogLogは、常に固定サイズの12KBのメモリしか使用しません。その代わり、結果には標準誤差(約0.81%)が含まれます。絶対的な精度が不要で、高速なトレンド分析が必要な場合に最適です。

Redisは、その豊富なデータ構造とアトミックな操作により、高性能なキャッシュとして名声を得ましたが、複雑なユースケースに対応できる強力なデータベースでもあります。

Geospatial: 近傍検索

「現在地から最も近いカフェを探す」といった機能は、多くのアプリケーションで必要とされます。Geospatialインデックスは、緯度と経度で指定されたオブジェクトを効率的に保存し、特定の地点からの距離で検索する機能を提供します。

この機能はSorted Setを基盤にしており、GeoHashと呼ばれる技術を使って2次元の座標を1次元のスコアに変換しています。

# カフェの場所を追加 (経度、緯度、場所名)
GEOADD cafes 139.7528 35.6852 'Cafe A'
GEOADD cafes 139.7580 35.6860 'Cafe B'

# 特定の地点から半径1km以内のカフェを検索
GEORADIUS cafes 139.7550 35.6855 1 km

GEOADD の計算量は O(logN)O(\log N) です。GEORADIUS のような検索コマンドは O(N)O(N) ですが、ここで NN は検索範囲内の要素数であり、インデックス全体ではありません。これにより、非常に高速な近傍検索が可能になります。

これらのデータ構造を理解し、適切に使い分けることで、アプリケーションのパフォーマンスとスケーラビリティを劇的に向上させることができます。知識を定着させるために、いくつかの質問に答えてみましょう。

Quiz Questions 1/5

Redisにユーザープロフィールのような構造化されたデータを保存する場合、JSONをシリアライズしてString型に保存するよりもHash型を使用する方が効率的な主な理由は何ですか?

Quiz Questions 2/5

リアルタイムで更新されるゲームのリーダーボードを実装するのに最も適したRedisのデータ構造はどれですか?