🔍

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

Leapcellは、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです:

複数言語サポート

  • Node.js、Python、Go、Rustで開発できます。

無制限のプロジェクトデプロイ

  • 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。

比類のないコスト効率

  • 使用量に応じた支払い、アイドル時間は課金されません。
  • 例: $25で6.94Mリクエスト、平均応答時間60ms。

洗練された開発者体験

  • 直感的なUIで簡単に設定できます。
  • 完全自動化されたCI/CDパイプラインとGitOps統合。
  • 実行可能なインサイトのためのリアルタイムのメトリクスとログ。

簡単なスケーラビリティと高パフォーマンス

  • 高い同時実行性を容易に処理するためのオートスケーリング。
  • ゼロ運用オーバーヘッド — 構築に集中できます。

ドキュメントで詳細を確認!

Try Leapcell

Xでフォローする:@LeapcellHQ


ブログでこの記事を読む

Discussion