【実務・中級編】 テーブルのインターリーブ – Cloud Spanner

【Cloud Spannerの極意】インターリーブ徹底解説:物理レイアウトを支配し、JOINを「無かったこと」にせよ

こんにちは。テックリードの私だ。
今日のコードレビュー、あるいはアーキテクチャ設計レビューで、こんなスキーマを見かけたとしよう。

— よくあるアンチパターン:論理的な外部キーだけに頼ったテーブル定義
CREATE TABLE Users (
UserId INT64 NOT NULL,
UserName STRING(MAX),
) PRIMARY KEY(UserId);

CREATE TABLE UserOrders (
UserId INT64 NOT NULL,
OrderId INT64 NOT NULL,
OrderDate DATE,
TotalAmount NUMERIC,
) PRIMARY KEY(UserId, OrderId),
CONSTRAINT FK_UserOrders_Users FOREIGN KEY(UserId) REFERENCES Users(UserId);

「ほう、`Users` と `UserOrders` に外部キーが張ってあって綺麗ですね」――なんて思ったとしたら、君のSpannerの習熟度はまだ「初級」を抜け出せていない。

Cloud Spannerは、デフォルトではテーブルごとにデータを分散配置する。つまり、`Users`の行と、それに対応する`UserOrders`の行が、ストレージ層(スプリット)の全く異なる物理ノードの遠く離れた場所に置かれている可能性がある。この状態で `JOIN` を発行すれば、ノード間ネットワークを跨いだシャッフルが発生し、レイテンシは跳ね上がる。

ここで登場するのが、Spannerの真骨頂「テーブルのインターリーブ(Interleave)」だ。
これを使いこなせば、親子のデータを物理的に同じストレージブロックに極限まで密着させ、JOINの概念そのものを過去のものにできる。

本稿では、実務の現場で絶対に外せないインターリーブ階層構造の設計、制約、そしてパフォーマンスを極限まで引き出すためのデザインパターンを叩き込む。

—

1. インターリーブの本質:物理レイアウトの支配

インターリーブとは一言で言えば、「子テーブルのレコードを、親テーブルの対応するレコードの物理的な直下に配置する」というストレージ最適化のメカニズムだ。

通常のRDBであれば、テーブルはテーブルごとに独立した領域(B+樹など)に格納される。しかし、Spannerのインターリーブ構造(親-子関係)を定義すると、次のような物理的配置になる。

  • 親テーブルのプライマリキーのプレフィックスと、子テーブルのプライマリキーが組み合わされ、単一のソート済みデータ構造としてストレージにストアされる。
  • 結果として、親の1行(例: `User(1)`)と、それに紐づく子の複数行(例: `UserOrders(1, 100)`, `UserOrders(1, 101)`)が、物理的に同じスプリット(Spannerの分散ストレージの単位)に常時コロケーション(共存)される。

DDLの書き方:インターリーブの構文

実際にDDLを見てみよう。先ほどのスキーマをSpanner流の正しいインターリーブ構造に書き換える。

— 親テーブル
CREATE TABLE Users (
UserId INT64 NOT NULL,
UserName STRING(MAX),
CreatedAt TIMESTAMP,
) PRIMARY KEY(UserId);

— 子テーブル:INTERLEAVE IN PARENT で親を指定する
CREATE TABLE UserOrders (
UserId INT64 NOT NULL,
OrderId INT64 NOT NULL,
OrderDate DATE,
TotalAmount NUMERIC,
) PRIMARY KEY(UserId, OrderId),
INTERLEAVE IN PARENT Users ON DELETE CASCADE;

ここで注目すべきポイントは2つある。
1. プライマリキーの継承: 子テーブルのプライマリキーの先頭カラムは、必ず親テーブルのプライマリキーと完全一致させなければならない(ここでは `UserId`)。
2. ON DELETE CASCADE: 親が削除されたとき、子も自動的に消滅する。実務上、親子関係が強いドメイン(ECの注文と明細、アカウントと設定など)ではほぼ必須のオプションだ。

—

2. なぜインターリーブが「最強」なのか?(パフォーマンスの恩恵)

正しく設計されたインターリーブ階層は、システムに圧倒的な優位性をもたらす。

① 局所的なルックアップとインメモリ同等のJOIN

親のキーと子のキーが物理的に同じ場所にあるため、`Users` と `UserOrders` の JOIN クエリは、ネットワークを跨ぐデータ転送(RPC)を劇的に削減できる。Spannerのオプティマイザはこれを検知し、極めて効率的なローカル読み取りを実行する。

② 範囲クエリの高速化

例えば、「特定のユーザーの、特定の期間の注文を取得する」といったクエリにおいて、プライマリキーの順序通りにデータが並んでいるため、シークコストが最小化される。

—

3. 現場で血を流さないための「厳格な制約」とアンチパターン

チーフアーキテクトとして最も伝えたいのは、「インターリーブは万能の銀の弾丸ではない。誤用すればシステムの拡張性を破壊する劇薬である」ということだ。以下の制約を頭に叩き込んでほしい。

制約1: 深すぎる階層(Deep Hierarchy)の罠

Spannerでは、親-子-孫-ひ孫…と何段階もインターリーブを重ねることができる(最大68階層まで理論上可能だが、実務での推奨は最大でも2〜3階層だ)。
階層が深くなりすぎると、スキーマ変更やデータ移行の際のロック競合が複雑化し、メンテナンス性が最悪になる。

制約2: ホットスポット(Hotspotting)の致命傷

これこそが最大の罠だ。インターリーブされた子テーブルにデータをインサートする際、親のプライマリキーが単調増加(例: 時刻ベースのUUIDや、連番IDなど)していると、すべての書き込みが特定の1つのスプリットに集中し、レイテンシが爆発する(ホットスポットの発生)。

  • 対策: 親テーブルのプライマリキーには、必ずハッシュ化されたIDや、ランダム性を帯びたUUIDv4などを用いるか、「ビット反転(Bit-reversal)」機能を利用して書き込みを分散させなければならない。

— 悪い例:連番やタイムスタンプを親のPKにすると、新規作成時に特定のノードが焼き切れる
— 良い例:書き込みが全スプリットに分散するようなPK設計にする、あるいはCloud Spannerのバケット化を検討する

—

4. 実務で使える設計パターン:「親・子・孫」の黄金比

では、実際のエンタープライズシステムではどう設計すべきか。
分かりやすい例として、「テナント > プロジェクト > タスク」というSaaSの階層構造を考えてみよう。

— 1. テナントテーブル(親)
— 組織ごとにデータが完全に分離されるため、テナントIDをPKにする。
CREATE TABLE Tenants (
TenantId STRING(36) NOT NULL,
TenantName STRING(100) NOT NULL,
Tier STRING(20),
) PRIMARY KEY(TenantId);

— 2. プロジェクトテーブル(子)
— テナントに属するプロジェクト。テナントIDをプレフィックスに持つ。
CREATE TABLE Projects (
TenantId STRING(36) NOT NULL,
ProjectId STRING(36) NOT NULL,
ProjectName STRING(200) NOT NULL,
) PRIMARY KEY(TenantId, ProjectId),
INTERLEAVE IN PARENT Tenants ON DELETE CASCADE;

— 3. タスクテーブル(孫)
— プロジェクトに属するタスク。テナントIDとプロジェクトIDをプレフィックスに持つ。
CREATE TABLE Tasks (
TenantId STRING(36) NOT NULL,
ProjectId STRING(36) NOT NULL,
TaskId STRING(36) NOT NULL,
Title STRING(500),
Status STRING(50),
) PRIMARY KEY(TenantId, ProjectId, TaskId),
INTERLEAVE IN PARENT Projects ON DELETE CASCADE;

この設計が美しい理由

  • マルチテナント分離: あるテナント(`TenantId`)に関するすべてのデータ(プロジェクトもタスクも)が、ストレージ上で物理的に一箇所にまとまる。
  • 効率的な一括削除: `Tenants` のレコードを1行削除するだけで、その下にある数百万件のプロジェクトもタスクも、Spannerのストレージエンジンがバックグラウンドで高速かつ安全にゴミ収集(GC)してくれる(`ON DELETE CASCADE` の真価)。

—

5. チーフアーキテクトからの最終提言

Cloud Spannerにおけるテーブルのインターリーブは、「物理ストレージのレイアウトを開発者が意図的にデザインする」という、リレーショナルデータベースの枠を超えた高度なエンジニアリングだ。

設計レビューで子テーブルを見かけたら、必ずこう問うてほしい。

  • 「この子は、本当に親と物理的に運命を共にするべき(同じスプリットにいるべき)か?」
  • 「親のプライマリキーは、書き込みのホットスポットを生み出さないか?」
  • 「階層が深すぎて、将来のメンテンスで窒息しないか?」

これらをクリアしたインターリーブ設計は、君のシステムを数千万QPSに耐えうる「モンスターシステム」へと昇華させるだろう。
理論を理解したなら、今すぐスキーマを見直し、物理レイアウトを支配せよ。健闘を祈る。

コメント

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