【実務・中級編】 負荷ベースの分割 – Cloud Spanner

負荷ベースの分割(Load-based Splitting)の真実:Spannerの自動スケーリングをハックする設計論

こんにちは。チーフアーキテクトの私だ。
今回は、Cloud Spannerのコア・アーキテクチャの根幹をなす「負荷ベースの分割(Load-based Splitting)」について、現場の設計レビューでそのまま使えるレベルの知見を授けよう。

世の中の入門書には「Spannerは自動でデータを分割してスケールします」としか書いていない。だが、シニアエンジニアならこう疑問に思うはずだ。
「自動って言うけど、どうやって高負荷を検知して、どのタイミングで、どこに割るんだ?」
「俺たちが書いたスキーマやクエリが原因で、その自動分割が完全に無効化されるケースはないのか?」

結論から言えば、ある。 そして、それを知らずにプライマリキーを設計すると、本番稼働直後のトラフィック急増でノードが融解する。
今回は、Spannerの分散原理の深層を覗き、実務で絶対に踏み抜いてはいけない設計アンチパターンと、それを回避するための極限のプラクティスを解説する。

—

1. 負荷ベースの分割とは何か?(内部メカニズムの解剖)

Cloud Spannerは、データを辞書順(Lexicographical order)にソートされた「スプリット(Split)」と呼ばれる単位で管理し、それを数百〜数千の分散ノード(SpandMS)に配置している。

ストレージ容量ベースの分割はどの分散DBにもあるが、Spannerの真骨頂は「リアルタイムの負荷(CPU使用率とリクエストレート)を監視し、ホットスポットを動的に切り裂く」という点にある。

分割と再配置のライフサイクル

1. モニタリング: 各スプリットは、自身が消費しているCPUリソースやQPSを常に計測している。
2. ホットスポット検知: 特定のスプリット(例:特定のユーザーID範囲、あるいは単一の行)にトラフィックが集中し、CPU閾値を超過すると、Spannerの内部バランサーがこれを「ホットスプリット」として検知する。
3. 境界の再計算(Splitting): データ量ではなく「負荷の偏り」を解消するために、スプリットの境界(Split Point)を動的に再計算し、より細かく分割する。
4. リバランス(Balancing): 分割された小さなスプリットは、クラスタ内の負荷が低い別のノードへとシームレスに移動(Rebalance)される。

この一連のプロセスは完全に自動であり、ダウンタイムはゼロだ。理論上、開発者はスケーリングを意識する必要がない。……適切なキー設計がされていれば、の話だが。

—

2. 現場で頻発する「最悪のアンチパターン」

負荷ベースの分割があるからといって、適当にテーブルを作って良いわけではない。Spannerの自動分割アルゴリズムを完全に無効化する悪手が、実務の現場では後を絶たない。

アンチパターン:単調増加キー(Sequential Keys)の罠

よくあるのが、主キーにインクリメンタルなIDや、タイムスタンプをそのまま使う設計だ。

— 【絶対にやってはいけないアンチパターンの例】
CREATE TABLE Transactions (
TransactionId STRING(64) NOT NULL, — UUIDv4ならセーフだが、もし連番やミリ秒タイムスタンプだと…
UserId STRING(64) NOT NULL,
Amount INT64,
CreatedAt TIMESTAMP NOT NULL,
) PRIMARY KEY(CreatedAt, TransactionId); — 致命的な時系列キー

何が起きるのか?

`CreatedAt` のような時系列データを主キーの先頭に置くと、「今この瞬間に入ってくるすべての書き込みデータ」が、辞書順で常に単一の末尾(最新のスプリット)に集中する。

負荷ベースの分割は「ホットスポットを割る」機能だが、「データが常に1点(今この瞬間)にしか書き込まれない」という物理的制約そのものは分割できない。
結果として、どれだけクラスタにノードを追加しても、最新の書き込みを担当する「たった1つのノード」だけがCPU 100%張り付きになり、他のノードは遊休状態(宝の持ち腐れ)という悲惨な状況が生まれる。これが「ホットスポットによるスケーリング不全」だ。

—

3. 堅牢な設計パターン:負荷分散を最大化するプラクティス

では、どう設計すべきか?
コードレビューで私が必ずチェックする、実戦投入可能な設計パターンを伝授する。

プラクティス 1: ハッシュ化プレフィックス(Hash Prefixing)

時系列データや連番IDで分散させたい場合、キーの先頭に「ハッシュ値の一部」を付与し、強制的にデータを空間全体に散らす。

— 【推奨される設計:ハッシュプレフィックスによる分散】
CREATE TABLE Transactions (
— HashShardは 0〜7 などの適当な整数値(クライアント側で計算して挿入)
HashShard INT64 NOT NULL,
TransactionId STRING(64) NOT NULL,
UserId STRING(64) NOT NULL,
Amount INT64,
CreatedAt TIMESTAMP NOT NULL,
) PRIMARY KEY(HashShard, CreatedAt, TransactionId);

【アーキテクトの解説】
クライアント側で `HashShard` を `MOD(ABS(FARM_FINGERPRINT(TransactionId)), 8)` のように計算して挿入する。これで書き込み負荷は8つの異なるスプリットに綺麗に分散され、負荷ベースの分割エンジンが健全に機能する。
範囲検索をする際は `HashShard` ごとにクエリを投げる(Scatter-Gatherパターン)か、インターリーブ構文を適切に組み合わせる必要があるが、高スループットな書き込み性能の前には十分な見返りがある。

プラクティス 2: インターリーブ(Interleave)による局所性の担保

親子関係を持つエンティティでは、親のキーを子のプライマリキーの先頭に含めることで、物理的に同じスプリット(または近傍)にデータを配置できる。

CREATE TABLE Customers (
CustomerId STRING(64) NOT NULL,
Name STRING(MAX),
) PRIMARY KEY(CustomerId);

— 子テーブル:Customerのデータ物理的に同じ領域に近接配置される
CREATE TABLE Orders (
CustomerId STRING(64) NOT NULL,
OrderId STRING(64) NOT NULL,
OrderDate TIMESTAMP NOT NULL,
) PRIMARY KEY(CustomerId, OrderId),
INTERLEAVE IN PARENT Customers ON DELETE CASCADE;

これにより、特定ユーザーに関する読み書きの局所性(Locality)が高まり、無駄なクロスノード通信を抑制しつつ、顧客ごとの負荷分散が自動で行われるようになる。

—

4. パフォーマンス上の注意点:スプリットの「分裂病」に気をつけろ

負荷ベースの分割は万能ではない。誤った運用をすると、逆にシステムのパフォーマンスを劣化させる罠がある。それが「スプリットの過剰分割(Over-splitting)」だ。

スプリット数が増えすぎることの弊害

一時的なバッチ処理などで極端な高負荷がかかると、Spannerはスプリットを細かく切り刻む。
しかし、バッチが終わった後、その細切れになったスプリットはどうなるか? すぐには統合(Merge)されない。

  • メタデータの肥大化: 管理すべきスプリットの数が数百万を超えると、内部のルーティングメタデータの保持・同期コストが増大する。
  • クエリプランの非効率化: 単一の範囲スキャンであっても、あまりに多くのスプリットにまたがると、コーディネーションのオーバーヘッドが無視できなくなる。

対策

  • スパイク負荷の予測と事前ウォームアップ(Pre-splitting):

大規模なセールやイベント(ブラックフライデー等)が事前に分かっている場合、あらかじめダミーデータを投入してスプリットを強制的に事前分割(Pre-split)させておくテクニックがある。これにより、トラフィック急増時の「分割が追いつかない瞬間」のレイテンシスパイクを防ぐことができる。

—

5. チーフアーキテクトからの総括

Cloud Spannerの「負荷ベースの分割」は、分散データベースの歴史における傑作のメカニズムだ。しかし、それは「物理法則を無視して魔法のように何でも解決してくれる銀の弾丸」ではない。

設計レビューでコードを見るとき、私はこう自問する。
「このキー設計は、Spannerに綺麗にデータを散らせているか?」
「書き込みの矢印が、常に1点に集中していないか?」

データベースの分散原理を理解し、そのエンジンの特性に寄り添ったスキーマを設計すること。それこそが、真にスケールする堅牢なシステムを作り上げる唯一の道だ。

次の設計レビューでは、これらの知見がコードに体現されていることを期待する。健闘を祈る。

コメント

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