Redisクラスタ内部:クライアントによるシャードの特定方法
背景:なぜ Redis Cluster が必要なのか
Sentinel モードはマスター・スレーブ構成に基づいており、読み書きの分離を実現しています。また、自動フェイルオーバー機能も備えているため、システムの可用性が高くなります。しかし、各ノードが同じデータを保持するため、メモリの無駄が生じ、オンラインでのスケールアウトが難しいという課題があります。
そのため、Redis Cluster クラスタ(シャーディングクラスタの実装方式)が登場しました。Redis 3.0 で導入され、Redis の分散ストレージを実現しています。データをシャーディング(分割)することで、各 Redis ノードが異なるデータを保持し、オンラインスケーリングの課題を解決しています。また、データを各 Redis インスタンスに分散して保存できるため、大容量データの保存も可能で、レプリケーションやフェイルオーバー機能も提供されます。
たとえば、1 つの Redis インスタンスに 15GB 以上のデータを保存すると、応答が非常に遅くなります。これは Redis の RDB 永続化メカニズムによるもので、Redis は RDB 永続化操作を完了するために子プロセスを fork します。この fork 処理の所要時間は Redis 内のデータ量に正比例します。
このとき、「15GB のデータを分散保存すればいいのでは?」と考えるのは自然です。これこそが Redis シャーディングクラスタの目的です。シャーディングクラスタとは何か、例を見てみましょう。Redis で 15GB のデータを保存する必要がある場合、単一の Redis インスタンスを使う方法と、3 台の Redis インスタンスで構成されるシャーディングクラスタを使う方法があります。以下に比較します:
シャーディングクラスタと Redis Cluster の違い:Redis Cluster は Redis 3.0 以降で公式に提供されたシャーディングクラスタの実装方式です。
データが複数の Redis インスタンスにシャーディングされている場合、クライアントはどのインスタンスにアクセスすればよいのか、どうやって判断しているのでしょうか?以下に Redis Cluster の仕組みを見ていきましょう。
クライアントはどのようにしてアクセスすべきシャードを知るのか?
Redis Cluster は、データとインスタンス間のマッピング関係を処理するために「ハッシュスロット(Hash Slot)」という方式を採用しています。
シャーディングクラスタは、全体で 16384 個のスロット(slot)に分割されます。Redis に送信される各キー・バリューのペアは、キーに基づいてハッシュ処理され、16384 個のスロットのいずれかに割り当てられます。使用されるハッシュ関数は比較的単純で、CRC16 アルゴリズムで 16bit の値を計算し、それを 16384 で割った余り(mod)を使ってスロットを決定します。データベース内のすべてのキーはこの 16384 個のスロットのいずれかに属しており、クラスタ内の各ノードはこのスロットの一部を処理します。
クラスタ内の各ノードは、ハッシュスロットの一部分を担当しています。仮に現在のクラスタにノード A、B、C の 3 台があるとすると、各ノードが担当するハッシュスロット数は 16384 ÷ 3 になります。例として、以下のような割り当てが可能です:
- ノード A:スロット 0 ~ 5460 を担当
- ノード B:スロット 5461 ~ 10922 を担当
- ノード C:スロット 10923 ~ 16383 を担当
クライアントが Redis インスタンスにデータの読み書きを要求したとき、そのインスタンスに対象のデータが存在しない場合はどうなるのでしょうか?ここで登場するのが、MOVED リダイレクトとASK リダイレクトです。
3. インスタンスに対象データが存在しない場合はどうなるか?
Redis Cluster モードでは、ノードがリクエストを処理する流れは以下のようになります:
- ハッシュスロットに基づき、現在の Redis キーが自ノードに存在するか確認
- スロットが自ノードの担当でない場合は、MOVED リダイレクトを返す
- スロットが自ノードの担当であり、キーがスロット内に存在する場合は、該当キーの結果を返す
- キーがそのスロットに存在しない場合、そのスロットが「MIGRATING(移行中)」か確認
- キーが移行中であれば、ASK リダイレクトを返してクライアントを移行先ノードに誘導
- スロットが移行中でなければ、スロットが「IMPORTING(インポート中)」か確認
- スロットがインポート中で ASKING フラグが立っていれば処理を継続、なければ MOVED を返す
3.1 MOVED リダイレクト
クライアントが Redis インスタンスに対して読み書き操作を行った際に、対象スロットがそのノードに属していない場合、Redis は MOVED リダイレクトエラーを返します。このエラーには、対象スロットを保持している新しいインスタンスの IP アドレスとポート番号が含まれています。これが Redis Cluster の MOVED リダイレクトメカニズムです。
3.2 ASK リダイレクト
ASK リダイレクトは主にクラスタのスケーリング(拡張・縮小)時に発生します。スケーリングによりスロットが移動するため、元のノードにアクセスすると、データはすでに移行先のノードに存在している可能性があります。この場合、ASK リダイレクトを使うことで正しいノードへ誘導できます。
4. 各ノード間の通信はどう行われるか?
Redis クラスタは複数のノードで構成されており、それぞれのノードはどのように通信しているのでしょうか?答えは Gossip プロトコルです。Gossip はメッセージ伝播プロトコルの一種で、各ノードは周期的にノードリストの中から k 個のノードを選び、自ノードが持つ情報を送信します。これを繰り返していくことで、すべてのノードの情報が一致し、アルゴリズムが収束します。
Gossip プロトコルの基本的な考え方:あるノードがネットワーク内の他のノードに情報を共有したいとき、周期的にランダムにいくつかのノードを選び、情報を送ります。情報を受け取ったノードも同様に、他のランダムなノードにその情報を送信します。通常、情報は一度に 1 ノードではなく N 個のターゲットノードに送信されます。この N のことを **fanout(ファンアウト)**と呼びます。
Redis Cluster はこの Gossip プロトコルを使って通信し、ノード同士が常に情報を交換しています。交換される情報には、ノードの障害、新ノードの参加、マスター・スレーブの変更、スロット情報などが含まれます。Gossip プロトコルでは、以下のような複数のメッセージタイプが使われます:
- meet メッセージ:新ノードの参加を通知します。送信ノードは受信ノードに対し、現在のクラスタに参加するように通知します。meet メッセージが正常に通信されると、受信ノードはクラスタに参加し、以後は周期的に ping および pong メッセージの交換を行うようになります。
- ping メッセージ:各ノードは毎秒、他のノードに ping メッセージを送信します。このメッセージには、ノードが把握している他の 2 つのノードのアドレス、スロット、状態情報、最終通信時間などが含まれます。
- pong メッセージ:ping や meet メッセージを受け取ったとき、応答として pong メッセージを返します。このメッセージにも、送信ノードが把握している他の 2 つのノードの情報が含まれます。
- fail メッセージ:あるノードが別のノードをダウンと判断した場合、その情報をクラスタ全体に fail メッセージとしてブロードキャストします。他のノードがこの fail メッセージを受信すると、該当ノードを「ダウン状態」として更新します。
特に、各ノードは クラスタバス(cluster bus) を通じて他のノードと通信します。この通信には、通常のサービス用ポートとは異なる特別なポート番号が使用されます。たとえば、あるノードのポートが 6379 の場合、クラスタ間の通信には 16379(6379 + 10000)番ポートが使われます。ノード間通信は特別なバイナリプロトコルを用いて行われます。
5. クラスタ内ノードに障害が発生した場合はどうなるか?
Redis クラスタは高可用性を実現しており、クラスタ内のノードに障害が発生しても、フェイルオーバーによってクラスタの正常なサービス提供が維持されます。
ダウン(オフライン)
Redis クラスタは ping/pong メッセージを用いて障害を検出します。この検出には「主観的ダウン」と「客観的ダウン」の 2 種類があります:
- 主観的ダウン(Subjective Down、略称:pfail):あるノードが他のノードを「利用不可」と判断した状態です。この状態は最終的な障害判定ではなく、あくまで 1 ノードの意見であり、誤判定の可能性があります。
- 客観的ダウン(Objective Down、略称:fail):複数のノードがあるノードを「利用不可」と認識し、クラスタ内で合意された正式なダウン状態です。もしダウンしたノードがスロットを保持するマスターノードであれば、フェイルオーバーが必要になります。
- たとえば、ノード A がノード B を主観的ダウンと判断し、一定時間経過後にその情報を他のノード(例:ノード C)に伝えます。ノード C が受信したメッセージを解析してノード B の状態が「pfail」であると確認した場合、「客観的ダウン」のプロセスが発動されます。
- ダウンしたのがマスターノードである場合、Redis Cluster はスロットを保持しているマスター数の過半数による投票を行い、その票数が過半数を超えたときに正式に「客観的ダウン」となります。
障害復旧(フェイルオーバー)
障害が検出された後、ダウンしたノードがマスターノードであれば、そのノードに紐づいたスレーブノードの中から 1 台を昇格させてマスターの代替とします。これによりクラスタの高可用性が保たれます。フェイルオーバーの流れは以下のとおりです:
- 資格確認:スレーブノードがマスターの代替となる資格を持っているか確認します。
- 選挙準備時間の設定:資格確認が通過した後、フェイルオーバー選挙のトリガー時間を更新します。
- 選挙の開始:トリガー時間に達すると、選挙を開始します。
- 投票の実施:スロットを保持するマスターノードだけが投票権を持ちます。スレーブノードが十分な票(過半数)を得られた場合、マスター交代が実行されます。
私たちはLeapcell、バックエンド・プロジェクトのホスティングの最適解です。
Leapcellは、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです:
複数言語サポート
- Node.js、Python、Go、Rustで開発できます。
無制限のプロジェクトデプロイ
- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。
比類のないコスト効率
- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。
洗練された開発者体験
- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。
簡単なスケーラビリティと高パフォーマンス
- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。
Xでフォローする:@LeapcellHQ
Discussion