Redisクラスターの構築で失敗しない!ノード偏りと負荷集中を防ぐシャード設計の基本

複数ノードに均等に分散されたRedisクラスターのシャード構成図 データベース

Redisクラスターを本番環境で運用する際、最も陥りやすい落とし穴の一つがシャード設計の失敗です。
ノード間のデータ偏りや特定ノードへの負荷集中が発生すると、クラスター全体の性能が急激に劣化し、可用性まで損なわれるケースが少なくありません。

本記事では、Redisクラスターの構築におけるシャード設計の基本を、ノード偏りと負荷集中を防ぐ観点から体系的に解説します。
具体的には、以下の3つの観点に焦点を当てます。

  • キー空間の均等分散:ハッシュタグの過剰使用を避け、一貫性ハッシュの仕組みを正しく理解した上でキーを分散させる手法
  • スロット割り当ての最適化:16384スロットを各ノードにどのように配分すべきか、およびマスター・レプリカのトポロジー設計
  • アクセスパターンの分析:ホットキーの特定と緩和策、および書き込み集中を避けるためのデータモデリングの工夫

特に、初期構築時にスロット割り当てを均等に行ったつもりでも、実際のワークロードによっては特定ノードに負荷が集中するという「見た目の均等、中身の偏り」は経験豊富なエンジニアであっても見落としがちです。
本記事では、そうした罠を回避するための実践的な設計指針と、運用フェーズでの監視・再配置の考え方を共有します。

  1. Redisクラスター構築の前に知っておくべきシャードの基本概念
    1. シャーディングとは何か
    2. なぜシャード設計が重要なのか
    3. Redisクラスターのアーキテクチャ概要
    4. キー空間の分割とデータ一貫性
  2. ノード偏りが発生する原因とそのリスク
    1. ノード偏りの定義と症状
    2. データ偏りが生じる代表的な原因
    3. アクセス偏りが生じる代表的な原因
    4. ノード偏りを放置した場合のリスク
    5. 偏りの早期発見の重要性
  3. 一貫性ハッシュとRedisのスロット機構の仕組み
    1. CRC16アルゴリズムによるキーからスロットへのマッピング
    2. ハッシュタグの仕組みと使いすぎによる偏りの発生
  4. スロット割り当ての最適化とマスター・レプリカのトポロジー設計
    1. 均等割り当ての落とし穴と実際のワークロードとの乖離
    2. レプリカの配置戦略とフェイルオーバー時の影響範囲
  5. 負荷集中を引き起こすホットキーの特定と対策
    1. RedisのSLOWLOGとINFOコマンドによるボトルネック分析
    2. ホットキーの緩和策:ローカルキャッシュと読み取り分散
  6. データモデリングの工夫で書き込み集中を回避する
    1. カウンターやランキングデータの分散書き込みパターン
  7. Redisクラスター運用時の監視指標と再配置の判断基準
    1. 各ノードのメモリ使用率とCPU使用率の監視ポイント
    2. RESHARDコマンドによるスロット再配置の手順と注意点
  8. シャード設計の基本を押さえて、安定したRedisクラスターを構築する

Redisクラスター構築の前に知っておくべきシャードの基本概念

複数のサーバーノードが連携するRedisクラスターのアーキテクチャ図

Redisをスケールアウトさせる際、単一インスタンスの限界を超えるための標準的な手法がクラスター構成です。
しかし、クラスター構築を安易に進めると、後から修正が困難な設計上の欠陥が埋め込まれてしまうリスクがあります。
ここでは、シャード設計の根幹となる基本概念を整理し、堅牢なRedisクラスターを構築するための土台を固めます。

シャーディングとは何か

シャーディングとは、データセットを複数のノードに水平分割して分散配置する手法です。
RDBMSの世界ではパーティショニングと呼ばれる概念と近いものですが、Redisの場合は各ノードが独立したキー空間のサブセットを保持する点が特徴です。
つまり、単一の巨大なデータベースを複数の小さなデータベースに分割し、それぞれが並列に処理を行うことで、全体のスループットを向上させる仕組みです。

Redisクラスターでは、キー空間が16384個のハッシュスロットに分割されます。
各キーはCRC16アルゴリズムによってハッシュ化され、0から16383までのいずれかのスロットに割り当てられます。
そして、これらのスロットがクラスター内のマスターノードに分散配置されることで、データとアクセス負荷の分散が実現します。

なぜシャード設計が重要なのか

適切なシャード設計を怠ると、以下のような問題が顕在化します。

  • ノード偏り:特定のノードにスロットやデータが集中し、メモリやCPUが逼迫する
  • 負荷集中:特定のキーへのアクセスが集中し、ホットスポットが発生する
  • スケーリング困難:後からスロットを再配置する際に大規模なデータ移動が発生し、サービス影響を及ぼす

これらの問題は、構築後に気づくことが多く、修正にはクラスター全体の停止や長時間の再配置作業を伴うため、初期設計段階での慎重な検討が不可欠です。

Redisクラスターのアーキテクチャ概要

Redisクラスターは、マスターノードとレプリカノードのペアで構成されるのが基本形です。
マスターがスロットの所有権を持ち、読み書きの両方を処理します。
レプリカはマスターのデータを非同期に複製し、マスター障害時に自動フェイルオーバーによってマスターに昇格します。

クラスター内のノード間通信はGossipプロトコルによって行われ、各ノードが他のノードの状態を認識し合うことで、分散システムとしての一貫性を保ちます。
クライアントは、接続したノードからスロットとノードのマッピング情報を取得し、適切なノードにリクエストをルーティングします。

構成要素 役割 注意点
マスターノード スロットの所有と読み書き処理 最低3ノードが推奨される
レプリカノード マスターのデータ複製とフェイルオーバー マスターと同じハードウェアスペックが望ましい
ハッシュスロット キー空間の論理的分割単位 合計16384個で固定
クラスターバス ノード間の状態同期用ポート ファイアウォール設定で開放が必要

キー空間の分割とデータ一貫性

Redisクラスターでは、単一キーの操作はアトミックですが、複数キーを跨ぐトランザクションは保証されません。
ただし、同じスロットに属するキーに対しては、MGETやMULTI/EXECなどの複合操作が可能です。
この性質を利用するために、ハッシュタグという仕組みが提供されています。

ハッシュタグを使用すると、特定のパターンを含むキーを強制的に同じスロットに配置できます。
例えば、user:{123}:profile と user:{123}:settings は、両方とも {123} 部分がハッシュタグとして認識され、同一スロットに配置されます。

# ハッシュタグを使用したキー命名例
SET user:{1001}:name "Taro"
SET user:{1001}:email "taro@example.com"
SET user:{1001}:last_login "2026-08-14"

しかし、ハッシュタグの過剰使用はスロット偏りを招くため、関連データの局所性と分散性のバランスを慎重に取る必要があります。
後の章で詳しく解説しますが、ハッシュタグは必要最小限に留めるのが原則です。

以上が、Redisクラスターのシャード設計における基本概念です。
次章では、実際にノード偏りが発生する具体的な原因と、そのリスクについて掘り下げていきます。

ノード偏りが発生する原因とそのリスク

サーバー間でデータ量が不均等に分散しているRedisクラスターの状態図

前章でRedisクラスターのシャードの基本概念を整理しました。
本章では、実際の運用現場で頻出するノード偏りという問題に焦点を当て、なぜ発生するのか、そして放置した場合にどのようなリスクが生じるのかを具体的に解説します。

ノード偏りの定義と症状

ノード偏りとは、クラスター内の複数ノードのうち、特定のノードにデータやアクセスが不均等に集中する状態を指します。
理想的には、各マスターノードがほぼ同量のスロットとデータを保持し、ほぼ同程度のリクエストを処理するのが望ましいですが、現実はそう単純にはいきません。

偏りが発生すると、以下の症状が観察されます。

  • 特定ノードのメモリ使用率が他ノードの数倍に達する
  • CPU使用率のグラフで一部のノードだけが常に高負荷
  • 遅延が増大し、クライアント側でタイムアウトが頻発する
  • クラスター全体のスループットが、最も弱いノードの性能に律速される

データ偏りが生じる代表的な原因

データ偏りの原因は複数ありますが、特に多いパターンを挙げます。

ハッシュタグの過剰使用は最も典型的な原因です。
関連データを同じスロットに集めることでトランザクション的な整合性を保とうとする発想は理解できますが、過度に使用すると特定スロットに膨大なキーが集中します。
例えば、全ユーザーのセッションデータを session:{global} のようなハッシュタグで管理すると、単一スロットに全セッションが押し込まれ、そのスロットを持つノードが圧倒的な負荷を受けます。

キー分布の偏りも見落としがちです。
RedisのCRC16ハッシュは一見すると均一に分布するように設計されていますが、実際のキー命名規則に偏りがある場合、理論上の均等分布が崩れます。
例えば、連番のユーザーIDをそのままキーに使用する場合、IDの生成パターンによってはハッシュ値に偏りが生じることがあります。

時系列データの蓄積も大きな要因です。
ログやイベントデータをRedisに格納する場合、古いデータの削除を怠ると特定ノードのデータ量が時間とともに増大します。
TTLを適切に設定しない、または手動での削除戦略が欠如しているケースで特に顕著です。

アクセス偏りが生じる代表的な原因

データ量の偏りだけでなく、アクセス頻度の偏りも深刻な問題です。

ホットキーの存在が代表的です。
特定のキーが極端に多くのリクエストを受ける状況は、SNSのフォロワー数カウンターや、ECサイトの在庫数管理などで頻出します。
例えば、人気商品の在庫キーに対して毎秒数千回の読み書きが発生すると、そのキーが所属するノードだけが過負荷になります。

書き込み集中も見逃せません。
特定のマスターノードに対して書き込みが集中すると、レプリカへの複製負荷もそのノードに集中し、結果としてレプリカのCPUやネットワーク帯域も逼迫します。
読み取りはレプリカに分散できますが、書き込みはマスターに集中するため、この非対称性がボトルネックとなります。

偏りの種類 主な原因 影響を受けるリソース
データ量偏り ハッシュタグの過剰使用、TTL未設定 メモリ、ディスク
アクセス偏り ホットキー、書き込み集中 CPU、ネットワーク帯域
スロット偏り 手動スロット割り当てのミス 上記すべて

ノード偏りを放置した場合のリスク

ノード偏りを放置すると、段階的に深刻化する連鎖的リスクが生じます。

まず、偏ったノードがメモリ上限に達すると、該当ノードでの書き込みが失敗します。
Redisのmaxmemory-policyがnoevictionに設定されている場合、OOMエラーが発生し、該当スロットに属するすべてのキーへの書き込みがブロックされます。
クラスター全体としては一部のキー空間だけが利用不能になるため、部分的なサービス停止という最悪の事態に陥ります。

次に、フェイルオーバー時の影響範囲が拡大します。
偏りのあるノードがダウンした場合、レプリカがマスターに昇格しますが、偏っていたデータ量や負荷をそのまま引き継ぐため、昇格後のノードも即座に過負荷に陥ります。
さらに、元マスターの復旧後のレプリカ同期にも時間がかかり、クラスターの安定性が長期にわたって損なわれます。

また、スケールアウトの効果が減衰します。
新しいノードを追加しても、偏りが解消されていなければ、単に全体的なリソースが増えただけで、ボトルネックとなっていたノードの負荷は低下しません。
結果として、インフラコストは増大するのに性能改善が見込めないという状況に陥ります。

偏りの早期発見の重要性

以上のリスクを回避するためには、構築時の設計だけでなく、運用中の継続的な監視が不可欠です。
次章では、Redisのハッシュ機構の詳細を理解した上で、どのようにスロットを最適に割り当てるかについて解説します。
ただし、その前に本章で述べたリスクを常に念頭に置き、監視体制の整備を並行して進めることを強く推奨します。

一貫性ハッシュとRedisのスロット機構の仕組み

Redisの16384スロットがノード間に分散されるハッシュ機構の図解

ノード偏りのリスクを理解したところで、本章ではRedisクラスターがどのようにキーを分散させているのか、その核心的なメカニズムを解説します。
一貫性ハッシュとRedis固有のスロット機構を正しく理解することは、後述する設計判断の基盤となります。

Redisクラスターは、一見すると一貫性ハッシュを採用しているように見えますが、実際には固定された16384個のスロットを介した間接的な分散方式を採用しています。
この設計は、ノードの追加や削除時に再ハッシュの範囲を最小限に抑えつつ、柔軟なスロット再配置を可能にするという、分散システム設計における優れたトレードオフの産物です。

CRC16アルゴリズムによるキーからスロットへのマッピング

Redisクラスターでは、各キーがどのスロットに属するかを決定する際、CRC16(Cyclic Redundancy Check 16bit)アルゴリズムを使用します。
具体的には、キーのバイト列に対してCRC16を計算し、その結果を16384で剰余算することで、0から16383までのスロット番号を導出します。

この計算はRedisサーバー内部で自動的に行われ、クライアント側で特別な処理を行う必要はありません。
ただし、クライアントが正しいノードにリクエストを送信するためには、クラスタートポロジーとスロットマッピングを把握しておく必要があります。

# Redis内部でのスロット計算のイメージ
slot = CRC16(key) % 16384

# 実際の例
# "user:1001" というキーの場合
# CRC16("user:1001") = 12345(仮の値)
# slot = 12345 % 16384 = 12345
# したがって、スロット12345に割り当てられる

CRC16の採用理由としては、計算コストの低さと、実用上十分な分散性の両立が挙げられます。
MD5やSHA-1などの暗号学的ハッシュ関数と比較して、CRC16はCPUサイクル数が少なく、毎リクエスト実行されるハッシュ計算においては大きなアドバンテージです。
また、16384というスロット数は、クラスター規模が数千ノードに達しても十分に管理可能な粒度であり、かつGossipプロトコルでのスロットマッピング交換にも負荷を与えにくい設計となっています。

項目 値 設計意図
ハッシュ関数 CRC16 高速計算と実用上の分散性
スロット総数 16384 大規模クラスター対応と通信効率のバランス
スロット番号 0〜16383 剰余算による均等分散の実現
マッピング方式 キー→スロット→ノード ノード変更時の再配置範囲の最小化

スロットからノードへのマッピングは動的です。
クラスター構築時に各マスターノードにスロットが割り当てられ、ノード追加時には既存ノードから一部のスロットを移動させることで、データ移動を局所的に留めます。
この間接マッピングのおかげで、ノード数の変更がキーとスロットの対応関係に影響を与えないという、スケーラビリティ上の大きな利点が生まれています。

ハッシュタグの仕組みと使いすぎによる偏りの発生

Redisクラスターでは、複数のキー操作をアトミックに行いたい場合にハッシュタグという仕組みを利用できます。
キー名の中に {...} で囲まれた部分がある場合、Redisはその部分のみをCRC16の入力として使用します。

# ハッシュタグを使用したキー例
SET user:{1001}:profile "..."
SET user:{1001}:settings "..."
SET user:{1001}:history "..."

# これら3つのキーはすべて、{1001}部分のみがハッシュ対象となる
# したがって、同じスロットに配置される

ハッシュタグの利点は明確です。
同じスロットに配置されたキーに対しては、MGETやMULTI/EXEC、さらにはLuaスクリプトによる複合操作が可能になります。
これは、分散環境において強い整合性が求められるユースケースにおいて非常に有用です。

しかし、ハッシュタグの使いすぎはスロット偏りを招く最大の要因の一つです。
例えば、全ユーザーの共通設定を config:{global} のようなキーで管理すると、単一スロットに全ユーザーの設定が集中し、そのスロットを持つノードが圧倒的な負荷を受けます。

# 危険なハッシュタグの使用例
SET app:{global}:feature_flag "on"
SET app:{global}:maintenance_mode "off"
SET app:{global}:rate_limit_config "1000"

# {global}部分がすべて同じため、単一スロットに集中

ハッシュタグを使用する際の指針としては、以下を推奨します。

  • ハッシュタグ内の値が取りうるパターン数が十分に多いことを確認する
  • グローバルな共通キーにハッシュタグを使用しない
  • 必要に応じて、ハッシュタグなしの代替キーで読み取りを分散させる

適切に使用すれば強力なツールですが、安易な適用はクラスター全体の性能を著しく低下させるため、ハッシュタグの使用は設計段階で慎重に検討すべきです。
次章では、スロット割り当ての最適化と、マスター・レプリカのトポロジー設計について具体的に解説します。

スロット割り当ての最適化とマスター・レプリカのトポロジー設計

マスターとレプリカノードが冗長構成で配置されたRedisクラスターの図

一貫性ハッシュとスロット機構の仕組みを理解したところで、本章では実際のクラスター構築における核心的な判断、すなわちスロットの割り当て方針とマスター・レプリカのトポロジー設計について解説します。
理論上の均等配置と、実際のワークロード特性の間には大きな乖離があり、その認識なしに設計を進めると後悔することになります。

均等割り当ての落とし穴と実際のワークロードとの乖離

Redisクラスターの構築を初めて行うエンジニアの多くは、16384スロットをマスターノード数で単純に割り、各ノードに同数のスロットを割り当てることを第一の目標とします。
確かに、スロット数の観点ではこれが最も公平な配置ですが、スロット数の均等と負荷の均等は全く別の問題です。

実際のワークロードでは、各スロットが受けるリクエスト数は大きく異なります。
例えば、ユーザーセッションを管理するキーがスロット100に集中し、そのスロットがマスターAに割り当てられているとします。
一方、マスターBに割り当てられたスロット群にはほとんどアクセスがないと仮定します。
この場合、スロット数は両ノードで同じでも、マスターAはマスターBの数倍の負荷を受けることになります。

# スロット数は均等だが負荷が偏っている例
# マスターA: スロット 0〜5461(5462スロット)
#   → スロット100にホットキーあり、毎秒10000リクエスト
#   → その他のスロットはほぼアクセスなし
#
# マスターB: スロット 5462〜10922(5461スロット)
#   → 全スロットにほぼ均等に分散、合計毎秒500リクエスト
#
# マスターC: スロット 10923〜16383(5461スロット)
#   → 全スロットにほぼ均等に分散、合計毎秒500リクエスト

この「見た目の均等、中身の偏り」を解消するためには、スロット割り当てだけでなく、実際のキー分布とアクセスパターンを事前に把握することが不可欠です。
本番投入前の負荷テストで、各ノードのメトリクスを詳細に監視し、偏りが生じていないかを検証する工程を必ず設けるべきです。

また、データのライフサイクルも考慮に入れる必要があります。
初期構築時には均等に見えたスロット割り当ても、時間の経過とともに特定のノードにデータが蓄積し、メモリ使用率に差が生じるケースが少なくありません。
特に時系列データやログデータを格納する場合は、TTLの設定と定期的な削除戦略を併せて検討してください。

観点 単純な均等割り当て ワークロード考慮型の割り当て
スロット数 ノード数で均等分割 実際のアクセス分布に応じて調整
メモリ使用率 時間経過で偏りが生じる可能性 データライフサイクルを考慮した設計
CPU負荷 ホットキーによる偏りが発生しやすい 負荷テストに基づく再配置
保守性 シンプルだが問題発見が遅れがち 監視と再配置のプロセスが必要

レプリカの配置戦略とフェイルオーバー時の影響範囲

マスターノードに対しては、最低1つ以上のレプリカノードを配置することが推奨されます。
ただし、レプリカの配置場所には注意が必要です。
単純に同じデータセンターや同じラック内にレプリカを配置すると、電源やネットワークの障害がマスターとレプリカを同時に破壊するリスクがあります。

理想的には、レプリカはマスターと異なる可用性ゾーンやラックに配置し、物理的な障害ドメインを分離すべきです。
クラウド環境では、AWSであれば異なるAZ(Availability Zone)、GCPであれば異なるゾーンにレプリカを配置するのが標準的なベストプラクティスです。

フェイルオーバー時の影響範囲を最小化するためには、レプリカの昇格後の負荷分散も考慮に入れる必要があります。
マスターAのレプリカがマスターA’に昇格した場合、元のマスターAが復旧してレプリカに戻るまでの間、A’が新たなマスターとして全負荷を受け止めなければなりません。
もしA’が元々他のマスターのレプリカも兼ねていた場合、その複製負荷と新たなマスター負荷が重なり、性能劣化を招きます。

# 推奨されるレプリカ配置の例(3マスター3レプリカ構成)
# マスターA(ゾーン1)→ レプリカA'(ゾーン2)
# マスターB(ゾーン2)→ レプリカB'(ゾーン3)
# マスターC(ゾーン3)→ レプリカC'(ゾーン1)
#
# フェイルオーバー時:
# マスターAがダウン → レプリカA'がゾーン2でマスターに昇格
# 元マスターAは復旧後、レプリカA'のレプリカとして再参加

さらに、レプリカの読み取り分散についても言及しておきます。
Redisクラスターでは、デフォルトではクライアントはマスターノードに対して読み書きを行いますが、レプリカに対して読み取りを分散させることでマスターの負荷を軽減できます。
ただし、レプリカのデータはマスターからの非同期複製であるため、レプリカ読み取りは結果整合性となり、最新の書き込みが即座に反映されない可能性がある点に留意が必要です。

トポロジー設計の最終チェックとして、以下の項目を確認することを推奨します。

  • 各マスターのスロット数は論理的に均等か
  • レプリカはマスターと異なる障害ドメインに配置されているか
  • フェイルオーバー後の負荷増大を見込んだスペック余裕があるか
  • レプリカ読み取りを利用する場合、結果整合性を許容できる設計になっているか

次章では、負荷集中の具体的な原因であるホットキーの特定方法と、その緩和策について解説します。

負荷集中を引き起こすホットキーの特定と対策

特定のキーに大量のアクセスが集中しているRedisのホットキー図

スロット割り当てとトポロジー設計を最適化しても、特定のキーへの過度なアクセス、いわゆるホットキーの存在によって、クラスター全体の性能が律速されるケースは少なくありません。
本章では、ホットキーの特定方法と、その緩和策について具体的に解説します。

ホットキーは、一見すると分散システムの設計問題とは異なるように見えますが、実際にはシャード設計の重要な構成要素です。
なぜなら、ホットキーが存在する場合、そのキーが所属するノードが必然的にボトルネックとなるため、スロット配置の戦略自体を見直す必要があるからです。

RedisのSLOWLOGとINFOコマンドによるボトルネック分析

ホットキーを特定する第一歩は、Redisが提供する組み込みコマンドを活用してボトルネックの発生箇所を特定することです。

SLOWLOGは、実行時間が閾値を超えたコマンドを記録する機能です。
デフォルトでは10ミリ秒以上かかったコマンドが記録されますが、本番環境では状況に応じて調整が必要です。

# SLOWLOGの設定と確認
CONFIG SET slowlog-log-slower-than 10000
SLOWLOG GET 10

# 出力例:
# 1) 1) (integer) 12
#    2) (integer) 1723612800
#    3) (integer) 15000
#    4) 1) "GET"
#       2) "user:1001:profile"

SLOWLOGの出力から、特定のキーへの操作が頻繁に遅延しているパターンを抽出できます。
ただし、SLOWLOGは実行時間が長いコマンドを記録するものであり、高頻度かつ短時間のリクエストは直接捉えられない点に注意が必要です。

INFOコマンドは、Redisインスタンスの包括的な統計情報を提供します。
特に INFO stats と INFO commandstats セクションは、ボトルネック分析に有用です。

# INFOコマンドによる統計情報の取得
INFO stats
INFO commandstats

# keyspace_hits と keyspace_misses の比率からキャッシュ効率を確認
# instantaneous_ops_per_sec で現在の秒間処理数を確認

instantaneous_ops_per_sec が特定ノードで異常に高い場合、そのノードにホットキーが存在する可能性が高まります。
さらに INFO keyspace で各データベースのキー数と有効期限の統計を確認し、データ分布の偏りを把握することも有効です。

コマンド 主な用途 ホットキー特定における役割
SLOWLOG GET 遅延コマンドの記録 特定キーへの重い操作の検出
INFO stats 全般的な統計情報 秒間処理数の異常値検出
INFO commandstats コマンド別の実行統計 特定コマンドの過剰使用の特定
INFO keyspace キー数と有効期限の統計 データ分布の偏りの把握

加えて、Redisの redis-cli --hotkeys オプションを使用すると、サンプリングに基づいてホットキーの候補を自動的に抽出できます。
ただし、本番環境で実行する際は、サンプリングによるオーバーヘッドを考慮し、低負荷時に実行するか、レプリカノードに対して実行することを推奨します。

ホットキーの緩和策:ローカルキャッシュと読み取り分散

ホットキーが特定できたら、次は緩和策の実装です。
最も効果的なアプローチは、アプリケーション層でのローカルキャッシュを導入することです。
アプリケーションサーバー内に短時間のキャッシュを持つことで、Redisへの同一キーへの繰り返しアクセスを削減できます。

# Pythonでのローカルキャッシュ導入例(簡易的な実装)
import time

local_cache = {}
local_ttl = 0.1  # 100ミリ秒

def get_with_local_cache(key):
    now = time.time()
    if key in local_cache:
        value, cached_at = local_cache[key]
        if now - cached_at < local_ttl:
            return value

    value = redis_client.get(key)
    local_cache[key] = (value, now)
    return value

ローカルキャッシュのTTLは、データの鮮度要件と負荷軽減効果のバランスから決定します。
読み取り頻度が極めて高く、かつ少しの遅延が許容されるデータであれば、100ミリ秒〜数秒のTTLでも十分な効果が期待できます。

次に、レプリカノードへの読み取り分散も有効な手段です。
マスターノードに集中していた読み取りリクエストの一部をレプリカに振り分けることで、マスターの負荷を軽減できます。
ただし、先述の通りレプリカ読み取りは結果整合性となるため、書き込み直後の読み取りが必要なケースではマスターに向けるなど、ルーティング戦略を設計する必要があります。

さらに、ホットキーの分割という手法もあります。
例えば、単一のカウンターキー view_count:article:1001 がホットキーになっている場合、複数のシャードキーに分割して書き込みを分散させ、読み取り時に合算する方法です。

# ホットキーの分割例
INCR view_count:article:1001:shard0
INCR view_count:article:1001:shard1
INCR view_count:article:1001:shard2

# 読み取り時に合算
total = redis_client.mget(
    "view_count:article:1001:shard0",
    "view_count:article:1001:shard1",
    "view_count:article:1001:shard2"
)
sum(int(v) for v in total if v)

この手法は、書き込みの分散には有効ですが、読み取り時の合算コストが増加するため、読み書き比率を考慮した上で採用するかどうかを判断すべきです。
書き込みが支配的なユースケースであれば効果的ですが、読み取りが支配的な場合はローカルキャッシュやレプリカ分散を優先するのが賢明です。

ホットキー対策は、一つの銀の弾丸ではなく、ユースケースに応じた複数の手法の組み合わせが必要です。
次章では、データモデリングの段階から書き込み集中を回避するための設計パターンについて解説します。

データモデリングの工夫で書き込み集中を回避する

キー設計を工夫して書き込み負荷を分散させたRedisデータモデル図

ホットキーが発生した後の対策は重要ですが、設計段階で書き込み集中を回避するデータモデリングを行う方が、はるかに効果的です。
本章では、特に書き込み負荷が高くなりやすいカウンターやランキングデータを題材に、分散書き込みを実現する設計パターンを解説します。

Redisは単一スレッドでコマンドを処理するため、特定キーへの書き込み集中は直接スループットの上限に到達する原因となります。
データモデルを工夫して書き込み先を分散させることで、この根本的な制約を緩和できます。

カウンターやランキングデータの分散書き込みパターン

時間ベースシャーディングは、カウンターに対して最も汎用的に適用できるパターンです。
単一のカウンターキーではなく、時間単位でキーを分割することで、書き込みを時間軸上に分散させます。

# 時間ベースシャーディングによるカウンター
# 1秒ごとにキーを切り替える例
import time
import redis

def increment_with_time_shard(redis_client, base_key):
    timestamp = int(time.time())
    shard_key = f"{base_key}:{timestamp}"
    redis_client.incr(shard_key)
    redis_client.expire(shard_key, 3600)  # 1時間後に自動削除

# 使用例
increment_with_time_shard(redis_client, "api:request_count")

このパターンの利点は、書き込みが時間とともに異なるキーに分散されるため、単一キーへの書き込み集中が回避される点です。
ただし、集計時には複数のキーを走査する必要があるため、集計頻度と精度のトレードオフを考慮する必要があります。
例えば、リアルタイム性が求められる場合は直近数秒のキーのみを集計し、日次レベルの集計であればバッチ処理で全キーを走査する、といった使い分けが有効です。

ハッシュベースシャーディングは、同一論理エンティティに対する書き込みを複数の物理キーに分散させる手法です。
先の章で触れたカウンターの分割と同様の考え方ですが、ここではより実用的な実装パターンを示します。

# ハッシュベースシャーディングによるランキングスコアの更新
import hashlib

def update_score_with_hash_shard(redis_client, user_id, score, shard_count=8):
    # ユーザーIDのハッシュ値からシャード番号を決定
    hash_value = int(hashlib.md5(user_id.encode()).hexdigest(), 16)
    shard_num = hash_value % shard_count

    shard_key = f"ranking:daily:{shard_num}"

    # 各シャードにスコアを記録(ユーザーIDをメンバーとする)
    redis_client.zadd(shard_key, {user_id: score})
    redis_client.expire(shard_key, 86400)  # 24時間後に自動削除

# ランキング取得時に全シャードをマージ
def get_merged_ranking(redis_client, shard_count=8, top_n=100):
    all_members = {}
    for i in range(shard_count):
        shard_key = f"ranking:daily:{i}"
        members = redis_client.zrevrange(shard_key, 0, -1, withscores=True)
        for member, score in members:
            all_members[member] = all_members.get(member, 0) + score

    # 合算スコアでソートして上位N件を返す
    sorted_members = sorted(all_members.items(), key=lambda x: x[1], reverse=True)
    return sorted_members[:top_n]

ハッシュベースシャーディングでは、シャード数を決定する際に将来のスケールを見据えた余裕を持たせることが重要です。
シャード数を後から変更すると、既存データの再配置が必要になるため、初期構築時に十分な数のシャードを確保しておくのが賢明です。
ただし、シャード数を増やしすぎると集計時のオーバーヘッドが増大するため、書き込み頻度と集計頻度のバランスから最適値を導出してください。

HyperLogLogを活用した概算集計も、書き込み集中を回避する有効な手段です。
厳密なカウントが不要なユースケース、例えばユニークユーザー数の概算などでは、HyperLogLogを使用することで極めて少ないメモリで近似値を得られます。

# HyperLogLogによるユニークユーザー数の概算
# 日次でキーを切り替える例
def add_daily_unique_user(redis_client, user_id, date_str):
    key = f"daily:unique_users:{date_str}"
    redis_client.pfadd(key, user_id)
    redis_client.expire(key, 604800)  # 7日後に自動削除

# 概算値の取得
def get_daily_unique_user_count(redis_client, date_str):
    key = f"daily:unique_users:{date_str}"
    return redis_client.pfcount(key)

HyperLogLogの標準誤差は約0.81%と非常に小さく、多くのユースケースで十分な精度を提供します。
さらに、複数のHyperLogLogを pfmerge で統合できるため、時間ベースシャーディングと組み合わせて使用することで、柔軟な集計が可能になります。

パターン名 適したユースケース 集計時のコスト 精度
時間ベースシャーディング リアルタイムカウンター 中(時間範囲による) 完全
ハッシュベースシャーディング ランキング・スコア更新 高(全シャード走査) 完全
HyperLogLog ユニーク数の概算 低 近似(誤差0.81%)

これらのパターンを組み合わせることで、ほとんどの書き込み集中シナリオに対応できます。
重要なのは、ユースケースの特性を正しく把握し、精度要件と性能要件のトレードオフを設計段階で決定することです。
次章では、運用フェーズでの監視指標と、スロット再配置の判断基準について解説します。

Redisクラスター運用時の監視指標と再配置の判断基準

Redisクラスターの各ノードのメトリクスを監視するダッシュボード画面

設計段階での工夫は重要ですが、運用フェーズにおける継続的な監視と適切な再配置が、長期的なクラスター健全性を維持する鍵となります。
本章では、監視すべき主要指標と、スロット再配置を判断するための基準について解説します。

Redisクラスターは動的なシステムです。
ワークロードの変化、データ量の増大、季節的なアクセス変動などにより、初期設計時に最適だった配置が次第に非効率になることは避けられません。
そのため、監視と再配置を組み合わせた継続的な最適化プロセスを確立することが不可欠です。

各ノードのメモリ使用率とCPU使用率の監視ポイント

Redisはインメモリデータベースであるため、メモリ使用率は最も重要な監視指標の一つです。
各マスターノードのメモリ使用量を定期的に確認し、ノード間に大きな差異が生じていないかを監視する必要があります。

具体的な監視ポイントとしては、以下が挙げられます。

  • 各マスターノードの used_memory と used_memory_rss の比率
  • maxmemory に対する使用率の推移
  • ノード間のメモリ使用量の標準偏差

used_memory はRedisが論理的に使用しているメモリ量を示し、used_memory_rss はOSが実際に割り当てている物理メモリ量を示します。
両者の差が大きい場合、メモリフラグメンテーションや大きな値の削除履歴が影響している可能性があります。

# 各ノードのメモリ情報を確認するコマンド
redis-cli -h node1 INFO memory | grep used_memory
redis-cli -h node2 INFO memory | grep used_memory
redis-cli -h node3 INFO memory | grep used_memory

# クラスター全体のキー分布を確認
redis-cli --cluster info node1:6379

CPU使用率の監視も同等に重要です。
Redisは単一スレッドでコマンドを処理するため、特定ノードのCPU使用率が100%に張り付く状態は、明確なボトルネックのサインです。
ただし、CPU使用率の監視には注意が必要です。
RedisサーバープロセスのCPU使用率だけでなく、システム全体のCPU使用率、特に us(ユーザーモード)と sy(システムモード)の比率も確認すべきです。
sy が高い場合は、カーネルレベルの処理、例えば大規模なデータ複製やページフォールトが発生している可能性があります。

# 各ノードのコマンド統計を確認
redis-cli -h node1 INFO commandstats

# 出力例から特定コマンドの負荷を特定
# cmdstat_get:calls=1000000,usec=500000,usec_per_call=0.50
# cmdstat_set:calls=500000,usec=300000,usec_per_call=0.60

さらに、ネットワーク帯域の監視も見逃せません。
特にレプリカノードが多数存在する場合、マスターからの複製トラフィックがネットワーク帯域を圧迫することがあります。
クラウド環境では、ネットワークのスループット上限に達するとパケットロスや遅延が増大し、複製遅延が生じます。

監視指標 確認コマンド・方法 異常時の対応
メモリ使用率 INFO memory スロット再配置、TTL見直し
CPU使用率 top、INFO stats ホットキー特定、ローカルキャッシュ導入
コマンド統計 INFO commandstats 特定コマンドの最適化
ネットワーク帯域 クラウド監視ツール レプリカ数の見直し、帯域拡張
レプリケーション遅延 INFO replication レプリカ配置の見直し

RESHARDコマンドによるスロット再配置の手順と注意点

監視結果に基づき、ノード間の偏りが顕在化した場合はスロットの再配置を行う必要があります。
Redisクラスターでは redis-cli --cluster reshard コマンドを使用して、実行中のクラスターに対してスロットを移動できます。

再配置を行う前に、以下の前提条件を確認してください。

  • クラスター全体が正常な状態であること(CLUSTER INFO で cluster_state:ok であること)
  • 再配置元と再配置先のノードに十分な空きリソースがあること
  • 再配置中の性能劣化を許容できる時間帯であること
# スロット再配置の実行例
# ノードA(node_id_a)からノードB(node_id_b)へ500スロットを移動
redis-cli --cluster reshard node1:6379 \
    --cluster-from node_id_a \
    --cluster-to node_id_b \
    --cluster-slots 500 \
    --cluster-yes

# ノードIDは以下のコマンドで確認
redis-cli -h node1 CLUSTER NODES

再配置処理中は、移動対象のスロットが一時的に読み書き不可となるミグレーション状態になります。
Redisはこの状態を自動的に管理しますが、大規模なスロット移動では以下の点に注意が必要です。

  • 移動対象のキー数が多い場合、再配置に時間がかかる:大きな値を持つキーが多数含まれるスロットの移動は、ネットワーク転送時間が増大します。可能であれば、負荷の低い時間帯に実行してください
  • 再配置中のクライアント影響:移動中のスロットに対するリクエストは、MOVED応答やASKINGリダイレクトが発生します。クライアントライブラリがこれを適切に処理できることを確認してください
  • 段階的な再配置を推奨:一度に大量のスロットを移動するのではなく、少しずつ移動して影響を最小化するのが賢明です

再配置後は、必ずクラスターの状態を確認し、全スロットが正しく割り当てられていることを検証してください。

# 再配置後のクラスター状態確認
redis-cli --cluster check node1:6379

# 各ノードのスロット数を確認
redis-cli -h node1 CLUSTER NODES | awk '{print $2, $8, $9}'

監視と再配置は、Redisクラスター運用における継続的なサイクルとして位置づけるべきです。
一度構築して終わりではなく、ワークロードの変化に応じて定期的に見直し、必要に応じて再配置を行うことで、長期的な安定運用を実現できます。
次章では、これまで解説してきた内容を総括し、シャード設計の基本を押さえた上での実践的なまとめを示します。

シャード設計の基本を押さえて、安定したRedisクラスターを構築する

均等に分散された健全なRedisクラスターのアーキテクチャ全体図

これまで、Redisクラスターのシャード設計における基本概念から、実践的な監視・運用までを体系的に解説してきました。
本章では、これらの知見を総括し、安定したRedisクラスターを構築・運用するための総合的な指針を示します。

Redisクラスターの構築は、単なるインフラ構築作業ではありません。
データの分散方式、アクセスパターン、障害時の挙動、そして将来のスケーラビリティを総合的に考慮したシステム設計の一部です。
その設計の質が、サービスの可用性と性能に直接影響を与えます。

まず、設計の出発点として、以下の3つの原則を常に念頭に置くことを推奨します。

  • データ分散の均一性:スロット数だけでなく、実際のデータ量とアクセス頻度の両面でノード間のバランスを追求する
  • 障害ドメインの分離:マスターとレプリカを物理的に分離し、単一障害点を排除する
  • 継続的な最適化:構築後も監視を継続し、ワークロードの変化に応じて再配置を行う

これらの原則は独立したものではなく、相互に関連しています。
例えば、データ分散の均一性を保つためには継続的な監視が必要であり、障害ドメインの分離を考慮したトポロジー設計は、再配置時の影響範囲にも関わってきます。

本記事で特に強調したいのは、「見た目の均等」と「中身の均等」の区別です。
16384スロットを単純にノード数で割った配置は、理論上は最も公平に見えますが、実際のワークロードでは全く異なる結果を生むことがあります。
CRC16ハッシュの性質、キー命名規則、ハッシュタグの使用状況、そしてアクセスパターンを総合的に分析した上で、スロット割り当てを決定する必要があります。

また、ホットキーへの対応についても、一つの手法に依存するのではなく、多層的なアプローチを取ることが重要です。
アプリケーション層でのローカルキャッシュ、レプリカノードへの読み取り分散、そしてデータモデリング段階での書き込み分散パターンを組み合わせることで、ほとんどの負荷集中シナリオに対応できます。
どの手法を優先するかは、ユースケースの読み書き比率、データの鮮度要件、そしてシステム全体のアーキテクチャから判断してください。

運用フェーズでは、監視指標の選定とアラート閾値の設定が鍵となります。
メモリ使用率とCPU使用率は当然のこととして、コマンド統計やレプリケーション遅延、ネットワーク帯域も定期的に確認してください。
特に、ノード間のメトリクス差異に注目し、相対的な偏りを早期に検出する体制を整えることが、重大障害の予防につながります。

スロット再配置は、クラスター運用において避けて通れない作業です。
RESHARDコマンドを使用した再配置は比較的シンプルですが、実行前の状態確認、実行中の影響監視、実行後の検証を徹底してください。
大規模な再配置は、サービス影響を最小化するために段階的に行い、可能であればメンテナンス時間帯を設定することを推奨します。

最後に、Redisクラスターの設計は一度きりの作業ではなく、進化的なプロセスであるという認識を持ってください。
初期設計が完璧であったとしても、サービスの成長とともにワークロードは変化し、当初の前提が崩れることは珍しくありません。
その変化に柔軟に対応できる監視体制と再配置プロセスを確立することで、長期的に安定したRedisクラスターの運用が実現します。

本記事が、Redisクラスターの構築におけるシャード設計の基本を理解し、ノード偏りと負荷集中を防ぐための実践的な指針として役立つことを願っています。
設計の段階で十分な検討を行い、運用フェーズでも継続的な最適化を続けることで、高可用性と高性能を両立したRedisクラスターを構築できるでしょう。

コメント

タイトルとURLをコピーしました