Redis Clusterのシャーディング:限界突破のアーキテクチャと実務設計の全知見
こんにちは。テックリードの私だ。
きみのチームがいま設計しているRedis、そろそろ単体ノードのメモリ上限やCPUバウンドのボトルネックに直面していないか? 「とりあえず大盛りインスタンスを立てました」という設計は、大規模トラフィックの波が来た瞬間に音を立てて崩壊する。
今回は、Redis Clusterの根幹である「シャーディングとハッシュスロット」のメカニズムを解剖し、本番環境で絶対に踏み抜いてはいけないアンチパターンと、堅牢な可用性を担保するための実務設計を伝授する。
—
1. ハッシュスロットによるデータ分散の深層
Redis Clusterは、コンシステント・ハッシュ法ではなく、「ハッシュスロット(Hash Slot)」という固定の空間分割モデルを採用している。
- 全スロット数: 16,384(固定)
- 分散アルゴリズム: `CRC16(key) % 16384`
なぜ「16,384」なのか? ここを理解しているかどうかが、アーキテクトとしての最初の分水嶺だ。
なぜ 16384 なのか?
1. 通信最適化: Redis Clusterのノード間通信(Gossipプロトコル)では、どのノードがどのスロットを持っているか(Cluster Busの状態)をBitmapで他のノードに伝播させる。16384スロットの場合、このBitmapのサイズは `16384 / 8 = 2KB` となり、ネットワーク帯域を圧迫しない絶妙なサイズになる。
2. クラスタースケールの限界: 作者のアンテリェ(Salvatore Sanfilippo)の設計思想として、Redis Clusterは「1,000ノードを超えるような超巨大クラスター」を想定していない(通常は数十〜数百ノード)。16384という分割数は、数千ノードまでのスケールにおいて、ノードあたり十分な数のスロットを割り当てられる粒度である。
ハッシュタグ(Hash Tag)の罠と活用
マルチキー操作(例: `MGET` やトランザクション)を行いたい場合、異なるキーは通常異なるハッシュスロットに割り振られるため、Redis Clusterではエラー(`CROSSSLOT Keys in request don’t hash to the same slot`)になる。
これを回避するのがハッシュタグだ。
「{user1000}」という文字列の部分だけがCRC16の計算対象になる
SET user{1000}:profile “Alice”
SET user{1000}:settings “DarkMode”
これにより、`user{1000}:profile` も `user{1000}:settings` も強制的に同じハッシュスロットにルーティングされ、マルチキー操作が可能になる。
しかし、注意しろ。 すべてのキーを同じハッシュタグにしてしまうと、特定のノードにデータとトラフィックが集中する「ホットスポット(Hotspot)」が発生し、シャーディングの意味が完全に失われる。本当に必要なドメイン境界でのみ使用せよ。
—
2. ノード間通信:Gossipプロトコルと障害検知の裏側
Redis Clusterは、中央集権的なコーディネーター(ZookeeperやEtcdなど)を持たない完全な分散P2P(Peer-to-Peer)アーキテクチャだ。各ノードはTCPの「Cluster Bus(通常、クライアント用ポート + 10000)」を使って常にお見合いをしている。
クラスタの状態を維持するメカニズム
- Gossipメッセージ: 各ノードは定期的にランダムな数個のノードへPingを送り、Pongを受け取ることで、クラスタ全体のトポロジー(誰が生きているか、どのスロットを持っているか)を共有する。
- 障害検知(PFAIL と FAIL):
- PFAIL (Possible FAIL): あるノードからのPingが無応答になり、設定されたタイムアウト(`cluster-node-timeout`)を超過した場合、検知したノードは心の中で「こいつ落ちたか?」とマークする(主観的ダウン)。
- FAIL: 過半数のマスターノードが「あいつ落ちてるよね」と合意(Gossip経由で伝播)すると、正式に `FAIL`(客観的ダウン)に昇格し、クラスタ全体にブロードキャストされる。
【実務上の鉄則】
`cluster-node-timeout` のデフォルト(通常15秒程度)を安易に短くするな。クラウド環境(AWS等)の一時的なパケットロスや、CPU高負荷時のGCポーズで誤検知(False Positive)が起き、不必要なフェイルオーバーが連発してシステムが崩壊する。実環境では、ネットワークの特性に合わせて 15000〜30000ms(15〜30秒) を死守し、リトライ設計でカバーするのがプロのやり方だ。
—
3. リシャーディング(Re-sharding)の手順と可用性
ビジネスの成長に伴い、ノードを追加してスケールアウト(リシャーディング)が必要になる日は必ず来る。
`redis-cli –cluster reshard` を叩けば裏側で自動的にやってくれるが、「本番稼働中にどう安全にデータを移すか」のフローをエンジニアなら完全に理解しておかなければならない。
リシャーディングの内部シーケンス
1. 移行先ノードの準備: 新規ノードをクラスタに参加させる(`CLUSTER MEET`)。
2. スロットの移行状態変更: 移行元ノードのスロット状態を `MIGRATING` に、移行先を `IMPORTING` に変更する。
3. データのバルク転送: `DUMP` と `RESTORE` コマンドを使い、キー単位でデータを移行先へコピーする。
4. クライアントのリダイレクト (-ASK):
移行中の過渡期にクライアントから移行中のキーへアクセスがあった場合、移行元ノードは `-ASK` リダイレクト を返す。
クライアントは `ASKING` コマンドを移行先ノードに送り、その一時的なリクエストを完遂する(通常の `-MOVED` リダイレクトとは異なり、スロットの所有権が完全に移動するまでは次回以降のアクセスは移行元を叩く)。
[Client] —> (GET key) —> [Node A (MIGRATING)]
|
v -ASK redirect
[Client] —> (ASKING -> GET key) —> [Node B (IMPORTING)]
運用上の注意点
- 巨大なキー(Fat Key)の存在: ハッシュの中に数百万の要素を持つHash型やSorted Setが存在する場合、`MIGRATING` 中の `DUMP`/`RESTORE` でそのキーの転送に時間がかかり、Redisのシングルスレッドイベントループがブロックされる。結果としてレイテンシが跳ね上がり、タイムアウトの嵐になる。
- 対策: リシャーディングを実行する時間帯の選定はもちろん、そもそも「1つのキーにデータを詰め込みすぎない」データ構造の設計が前提となる。
—
4. 堅牢な設計パターンとパフォーマンス最適化
最後に、コードレビューや設計レビューで私が必ずチェックするポイントをまとめる。
① クライアントのスマート・ルーティング
安物のクライアントライブラリを使うな。Redis Clusterのクライアントは、接続時に `CLUSTER NODES` を取得し、「どのスロットがどのノードにあるか」のキャッシュをクライアント側で持っていなければならない。
愚直にランダムなノードに叩き、`-MOVED` エラーを受けてからリトライするような実装では、ネットワークホップが倍増し、レイテンシが致命的に悪化する。 Lettuce(Java)や ioredis(Node.js)、go-redis(Go)など、Clusterトポロジーを自動追従する成熟したライブラリを選択すること。
② レプリケーションとフェイルオーバーのトポロジー
- 最低構成: 最低でも「3つのマスターノード」が必要(スロットを分割するため)。可用性を担保するため、各マスターに最低1台のレプリカ(スレーブ)を配置し、計6ノードがプロダクション環境の絶対的なスタートラインだ。
- クロスAZ配置: クラウド(AWS ECS / EKS や EC2)で構築する場合、マスターとレプリカが同一のアベイラビリティゾーン(AZ)に配置されないよう、アフィニティ/アンチアフィニティルールを厳格に適用しろ。AZ障害時にマスター・レプリカが同時に沈黙し、データが消失する惨劇を防ぐためだ。
—
結びにかえて
Redis Clusterのシャーディングは、魔法の杖ではない。
「なんとなくスロットが分散されるから大丈夫」という甘い認識で設計すると、クロススロットエラー、ホットスポット、リシャーディング時のブロック地獄に足元をすくわれる。
ハードウェアやミドルウェアの挙動を低レイヤーのレイヤーから逆算し、「データ構造」「トラフィックの偏り」「障害時の挙動」の3点をコントロールし切ること。それこそが、我々エンジニアが追求すべきプロフェッショナルなアーキテクチャだ。
次の設計レビューでは、これらのポイントが完璧にクリアされていることを期待する。
コメント