このページの目次

動的リバランス

クラスターのスケールアウトやノード障害時に、データは旧配置から新配置へ移行する必要があります。優れたリバランスは、必要な最小限のデータのみを移行し、読み書きを中断しません——一貫性ハッシュのバーチャルノードにより、このプロセスは分散化されます。

概要

一貫性ハッシュシャーディング戦略は、「データをノードにどのように分散させるか」を解決します。しかし、クラスターのスケールアウト(ノード追加)やスケールイン(ノード障害/オフライン)が発生すると、データは旧配置から新配置へ移行する必要があります——これがリバランス(rebalancing)です。悪いリバランス方案では、必要以上の大量のデータを移行したり、移行中にサービス拒否(DoS)状態に陥ったりする可能性があります。優れたリバランスは必要な最小限のデータのみを移行し、⁠読み書きを中断しません⁠。

3つのリバランス戦略

固定パーティション: シンプルだが粗雑

ノード数をはるかに超える数のパーティションを事前に作成します(例: ノード1つあたり100パーティション、10ノードで合計1000パーティション)。パーティション数は不変です。ノードを追加すると、各旧ノードから一部のパーティションを新ノードへ割り当てます。

ノード追加前: Node-1: [p1..p100],  Node-2: [p101..p200], ... Node-10: [p901..p1000]
ノード追加後: Node-1: [p1..p90],   Node-2: [p91..p180],  ... Node-11(新): [p901..p990]
  • 移行粒度: パーティション単位(N個のキーを一括で移動)。キー単位ではありません。
  • 欠点: パーティションサイズとノード数が強く結合されている⁠——初期推定がずれた場合(パーティションが大きすぎたり小さすぎたり)、後から調整が困難です。また、パーティション数は静的であるため、データ量の増加に応じて分割できません。

Redis Clusterはこの方案を採用しています: 16384個の固定スロット。

動的パーティション: データの増加に伴いパーティションが分割

各パーティションにサイズ上限(例: 10GB)を設定し、これを超過すると2つの新しいパーティションに分割されます。ノード数が増減すると、パーティション全体が新ノードへ移行します——固定パーティションと同様ですが、パーティション数自体が動的です。

パーティション p3 が 10GB に到達 → p3a と p3b に分割(それぞれ約 5GB)
移行が必要 → p3a 全体を新ノードへ移動
  • 利点: パーティション数は総データ量に比例するため、固定パーティションのような「事前に多すぎたり少なすぎたりする」という問題が発生しません。
  • 代償: パーティションのメタデータ(どのパーティションがどのノードにあるか、どのパーティションが分割中か)を追跡するための集中型コーディネーターが必要です。HBaseはこの方案を採用しています。

一貫性ハッシュ + バーチャルノード: 分散型リバランス

一貫性ハッシュでは、各物理ノードが複数のバーチャルノード(vnode、例: ノードあたり約100個)に対応し、これらはハッシュリング全体に分散されます。ノードを追加すると、新ノードがリングに挿入され、自身のvnodeがカバーするトークン範囲(隣接ノードのvnodeから一部を割り当て)を引き継ぎます。ノードを削除すると、そのvnodeに対応する範囲は隣接するvnodeが引き継ぎます。

ノード N11(v1101..v1200 の vnode を持つ)をリングに追加:
  元のトークン [5000, 5200] は N3 の v303 にあった
  現在 v1101 が [5000, 5200] をカバー → この範囲は N3 から N11 へ移行
  新ノードの範囲内にあるキーのみを移行し、他のキーは移動しない
  • 最小限の移行量⁠: 新vnodeの範囲内に落ちるキーのみを移行し、パーティション全体を移動しません。
  • 分散型⁠: 各ノードはGossipプロトコルを通じてグローバルなルーティングテーブルを知っており、コーディネーターに依存せずに、自身がどの範囲を引き継ぐべきかを独自に計算します。
  • 代償: 移行粒度は単一vnodeがカバーする範囲であるため、ホットスポット(特定のvnodeの範囲にデータが偏る)が発生する可能性があります——解決策はvnode数の動的調整または負荷に応じたvnodeの分割⁠(Cassandraのvirtual node splitting)です。

移行のエンジニアリング: 中断なくデータを移動する方法

どの戦略であっても、実際のデータ移動プロセスにはいくつかの一般的なプラクティスがあります。

ダブルライト(二重書き込み)による移行

移行期間中、新旧両方のノードがその範囲に対する書き込みを同時に受け付けます⁠:

1. メタデータ更新: 範囲 [5000, 5200] を N3 から N11 へ移行
2. 移行期間: 読み書きは引き続き N3 へ送信され、N3 は新しい書き込みデータをストリーミングで N11 へ転送
3. N11 が完了シグナルを受信 → メタデータ更新: [5000, 5200] は N11 の所有物となった
4. 以降の読み書きは直接 N11 へ送信

ダブルライトにより、移行中でも書き込みの消失を防ぎます:新ノードが完全に準備できていなくても、旧ノードがサービスを提供し続けます。Cassandraの nodetool move / decommission がこの方式を採用しています。

スロットルと優先度

データ移行はネットワークとI/Oを消費します——業務ピーク時にフルスピードで移行すると、通常の要求が遅延したり失敗したりする可能性があります:

  • スロットル⁠: 移行帯域幅を制限する(例: Cassandraの stream_throughput_outbound_megabits_per_sec)。
  • オフピーク移行⁠: 手動でトリガーし、低負荷時に実行する。
  • 増分再同期⁠: まずフルスナップショットを転送し、移行期間中に発生した新しい書き込みを転送する(MySQLのbinlog追従に類似)。

一貫性に関する落とし穴

移行期間中、1つのデータのN個のレプリカが新旧の配置を跨ぐ可能性があります——一部のレプリカは旧ルーティングに従い、一部は新ルーティングに従います。解決策はデータを先に移行し、ルーティングメタデータを切り替えることです(アトミックな切り替えであり、レプリカごとに切り替えるわけではありません)。あるいは、Cassandraのように、読み取り時に新旧両方の場所からデータを取得する方法もあります(移行期間中は短時間、二重ルーティングが存在します)。

トレードオフと失敗パターン

  • 全量再分散⁠: ノードを1つ追加するたびにすべてのキーを再ハッシュする → 移行量 = 全データ/N で、無意味な移行が大量に発生します。一貫性ハッシュまたは固定パーティション + パーティション移動を常に使用してください。
  • ホットスポットvnode: 特定のvnodeがカバーする範囲にデータが偏り、移行時にボトルネックとなる → vnodeのサイズを監視し、動的に分割する。
  • 移行中のデータ消失⁠: ルーティングを先に切り替えてからデータを移動する → 常にデータを先に移動し、ルーティングをアトミックに切り替える。
  • 移行嵐⁠: 一度に多数のノードを追加し、同時に移行が発生してクラスターが押しつぶされる → ノードを1つずつ追加し、毎回リバランスが完了するのを待ってから次のノードを追加する(Cassandra推奨)。

参考文献

  • 論文⁠: "Dynamo" (一貫性ハッシュによるリバランス, 2007)

Keywords: rebalancing, 固定パーティション, 動的パーティション, hash ring, virtual node, vnode, データ移行, 最小移行量, ダブルライト移行, 漸進的移行, スロットル, atomic routing switch, hotspot vnode