ディレクトリベースのシャーディング:Cloud Spannerの物理律をハックし、極限のスキャン性能を引き出す技術
こんにちは。テックリードの私だ。
今日のコードレビュー、あるいはアーキテクチャ設計レビューで、君たちはこんなテーブル定義を持ち込んでいないか?
— 良くあるアンチパターン
CREATE TABLE Tenants (
TenantId STRING(64) NOT NULL,
— その他のテナント情報…
) PRIMARY KEY(TenantId);
CREATE TABLE TenantData (
TenantId STRING(64) NOT NULL,
DataId INT64 NOT NULL,
Payload STRING(MAX),
) PRIMARY KEY(TenantId, DataId);
「お、ちゃんとインターリーブ(Interleave)を使って親子関係を定義していますね。これで物理的にデータが近くに配置されますよね?」――そう思ったなら、Cloud Spannerの底知れぬ分散原理の表面しか見えていない。
今日は、マルチテナントや大規模な時系列データなど、「特定のキー空間にデータが集中し、かつそれらを高速にスキャンしなければならない」という極限の要件に対し、Cloud Spannerのコアアーキテクチャであるディレクトリベースのシャーディング(Directory-based Sharding)をどうハックし、実務の現場で爆速のパフォーマンスを叩き出すか。その全知見を伝授する。
—
1. 原理の深掘り:なぜ通常のインターリーブだけでは不十分なのか
Cloud Spannerは、テーブルの行をプライマリキーの辞書順でソートし、連続するキー範囲(Range)ごとにスプリット(Split)と呼ばれる物理的なシャードに分割してストレージと計算ノードに分散配置する。
ここでインターリーブ(Interleaving)を使うと、親行(例:`TenantId`)の直下に、子行(例:`DataId`)のデータが物理的に近接して(Colocate)配置される。これによって、単一の親テナントに紐づくデータを取得する際、ネットワークを跨ぐラウンドトリップを劇的に削減できる。
インターリーブの「物理的な限界」
しかし、ここで一つの物理律の壁にぶつかる。
Cloud Spannerのオートスプリット機構は、データ量が一定サイズ(通常は数GB〜数十GB)を超えると、キー範囲でスプリットを分割する。
もし、ある巨大な単一テナントのデータ量がこのスプリットの閾値を超えた場合、どうなるか?
そのテナントの子孫データは、複数のスプリットに分断されてストレージの別ノードに配置される可能性がある。結果として、そのテナントの全データをスキャンするクエリ(Full Tenant Scan)は、複数ノードをまたぐ分散トランザクションや並行スキャンの調整コストを支払うことになり、レイテンシが跳ね上がるのだ。
「特定の関連データを物理的に一つの単位(ディレクトリ)としてカプセル化し、絶対に分断させない、あるいは効率的にルーティングする」――この課題を解決するためにSpannerの内部で高度に抽象化されている概念こそが、ディレクトリ(Directory)である。
—
2. ディレクトリの正体とスプリット制御
Cloud Spannerのストレージ層(Colossus)とトランザクション層において、ディレクトリとは「自己管理されるデータの論理的なグループ」であり、実質的な物理ストレージの最小単位(あるいはきめ細やかなシャーディングの単位)のベースとなる。
通常のインターリーブテーブルにおいて、ルートテーブルの各行(または特定のプレフィックスを持つ行群)は、暗黙的に「ディレクトリ」として扱われる。Spannerの分散トランザクションマネージャーとバランサーは、このディレクトリ単位でデータの移動(スプリットの分割・結合・マイグレーション)を行う。
ディレクトリ・バウンダリの設計思想
実務で意識すべきは、「クエリのアクセスパターンと物理的なディレクトリの境界を完全に一致させる」ことだ。
例えば、SaaSプラットフォームにおいて「テナントID」をディレクトリの境界として機能させたい場合、スキーマ設計において親テーブルのプライマリキーが綺麗にディレクトリのルートとして機能するように仕向ける必要がある。
しかし、データが巨大化するホットスポット(特定の大口顧客テナントなど)に対しては、デフォルトのオートスプリット任せにするのではなく、ディレクトリの特性を理解した上でデータアクセスの局所性を担保しなければならない。
—
3. 実践:極限のスキャン性能を引き出すスキーマ設計パターン
では、具体的にどう設計すべきか。
マルチテナント環境において、単一テナントの全データを亜音速でスキャンしつつ、全体のスループットを最大化する「ディレクトリ指向インターリーブ設計」のコードを見てみよう。
— 【設計パターン】テナントをルートとした厳格なディレクトリ構造
CREATE TABLE Tenants (
TenantId STRING(36) NOT NULL,
CreatedAt TIMESTAMP NOT NULL,
Tier STRING(20) NOT NULL,
) PRIMARY KEY(TenantId);
— テナントに完全に従属するトランザクションデータ
— このテーブル群は、物理的にTenantIdのディレクトリ内に極限まで近接配置される
CREATE TABLE TenantEvents (
TenantId STRING(36) NOT NULL,
EventTimestamp TIMESTAMP NOT NULL,
EventId STRING(64) NOT NULL,
Payload BYTES(MAX),
) PRIMARY KEY(TenantId, EventTimestamp DESC, EventId),
INTERLEAVE IN PARENT Tenants ON DELETE CASCADE;
— 集計用の子テーブル(必要に応じてさらに階層化)
CREATE TABLE TenantMetrics (
TenantId STRING(36) NOT NULL,
MetricDate DATE NOT NULL,
MetricName STRING(64) NOT NULL,
Value FLOAT64,
) PRIMARY KEY(TenantId, MetricDate, MetricName),
INTERLEAVE IN PARENT Tenants ON DELETE CASCADE;
チーフアーキテクトのコードレビュー視点:
1. プライマリキーの順序:`TenantEvents` のプライマリキーに `EventTimestamp DESC` を入れている点に注目せよ。これにより、最新のイベントデータが物理的に先頭(親テナント行のすぐ近傍)に配置され、直近のデータをスキャンする際のディスクシーク(キャッシュヒット率)が最大化される。
2. カスケード削除の活用:`ON DELETE CASCADE` を指定することで、テナント削除時のゴミ掃除がSpannerの分散ストレージエンジン内部で効率的なディレクトリ単位のガベージコレクションとして処理される。アプリ側でバッチを組む必要は一切ない。
—
4. パフォーマンス上の注意点とアンチパターン
最後に、現場でよく見られる致命的な誤設定と、その回避策を共有する。これを知らないと、せっかくのディレクトリ構造が完全に破壊される。
1. 連番やタイムスタンプをルートのキーにした「ホットスポット」の爆誕
よくある失敗が、`TenantId` の代わりに単調増加するIDやミリ秒単位の現在時刻を親テーブルの先頭(=ディレクトリのルートキー)にすることだ。
これをしてしまうと、すべての新しいデータが単一の物理スプリット(最新のキー範囲)に集中し、書き込みのホットスポットが発生してCPU使用率が100%に張り付く。Spannerの自動負荷分散が追いつく前にスループットが頭打ちになる。
- 対策:ルートキーには必ずUUIDv4や、ハッシュ化されたプレフィックス、あるいは十分に分散されたテナントIDを使用すること。
2. インターリーブの深さの過剰化(3階層以上の罠)
「綺麗に階層化したい」というエンジニアの悪癖で、`Tenants -> Projects -> Tasks -> SubTasks` のように4階層も5階層もインターリーブを深くする者がいる。
ストレージの局所性は上がるが、クエリプランナーの最適化コストが増大し、何より深い階層の行に対する単体更新・挿入のロック競合が激化する。
- 対策:インターリーブは原則として「親・子の2階層(Root & Interleaved)」に留めよ。それ以上の関係性は、親のIDを冗長に持たせたフラットな子テーブルにするか、JSONB/BYTESによるペイロード格納を検討すべきだ。
—
5. まとめ
Cloud Spannerにおけるディレクトリベースのシャーディング、そしてそれを具現化するインターリーブ機構は、単なる「便利な機能」ではない。これは物理レイヤのデータをエンジニアが論理的意図通りにコントロールするための最強の武器である。
設計レビューでこの仕組みを理解している者とそうでない者とでは、大規模トラフィックを浴びた際のシステム生存率が天と地ほど変わる。
次に君がデータベースのスキーマを書くときは、単に「リレーションがあるから」ではなく、「データがどのディレクトリに属し、どのスプリットとして物理的に流れるか」を脳内でビジュアライズしながらキーボードを叩いてほしい。
健闘を祈る。
コメント