Cloud Spannerのテーブル設計:データ型を「ただの型」で終わらせるな
Cloud Spannerを「ただのSQLが動く分散DB」だと思っているなら、今すぐその認識を捨ててほしい。Spannerは「地球規模の整合性を担保する分散システム」であり、そのテーブル設計は、物理的なデータ配置、そしてクエリの実行計画と不可分に結びついている。
今日は、Spannerにおけるデータ型の選択とテーブル設計の極意について、現場で血を流しながら学んだ「生存戦略」を授ける。
—
1. データ型を「サイズ」で選ぶな、「コスト」で選べ
Spannerのデータ型選択において最も重要なのは、単なる格納能力ではない。「その選択が、将来のエンコーディング効率と読み取りコストにどう影響するか」だ。
STRING vs BYTES
多くの場合、ID系には`STRING`を使いがちだが、もしそれがUUIDのような固定長かつバイナリ表現が可能なデータなら、`BYTES(16)`を強く推奨する。
- 理由: `STRING`は可変長UTF-8エンコーディングを伴うため、ストレージ効率と比較パフォーマンスにおいて`BYTES`に劣る。インデックスのサイズ肥大化は、レイテンシとコストの双方を直撃する。
JSON型の落とし穴
`JSON`型は非常に便利だが、スキーマレスな「ゴミ箱」にしてはいけない。
- 実務的知見: 頻繁にフィルタリング条件(WHERE句)に使うカラムをJSON内に隠蔽するな。インデックスが効かない、あるいは関数インデックスが必要となり、計算コストが跳ね上がる。JSONはあくまで「柔軟なメタデータ」として使い、検索のキーとなるフィールドは必ず実カラムとして物理設計に落とし込むこと。
ARRAYの「隠れた制約」
`ARRAY`はリレーションを一つ減らせる強力な武器だが、「巨大な配列」は毒だ。
- 注意点: 1行あたりの制限(10MB)に達しなくても、配列要素の増減は行全体の書き込みコストに直結する。配列内の要素を頻繁に更新する必要があるなら、それは本来「別テーブルへの分離(1対多のリレーション)」が正しい。
—
2. 「プライマリキー」こそがSpannerの心臓部である
Spannerのテーブル設計で最も残酷な失敗は、プライマリキー(PK)の設計ミスだ。これはデータ型以前の問題だが、型の選択にも直結する。
— 悪い例:単調増加するINT64をPKにする
— これを行うと、挿入負荷が特定のノードに集中する「ホットスポット」が発生し、
— Spannerの分散性能を自ら殺すことになる。
CREATE TABLE Orders (
OrderId INT64 NOT NULL, — ここが単調増加だと死ぬ
…
) PRIMARY KEY (OrderId);
— 良い例:UUID v4 やビット反転させたハッシュ値を複合キーにする
CREATE TABLE Orders (
OrderId BYTES(16) NOT NULL, — ランダムなID
CreatedAt TIMESTAMP NOT NULL,
…
) PRIMARY KEY (OrderId);
原則: PKには「分散を阻害しない値」を選ぶこと。タイムスタンプをPKの先頭に置くのは、時系列データで書き込みが集中するため厳禁だ。
—
3. TIMESTAMP型と整合性の神話
Spannerの`TIMESTAMP`は単なる日付データではない。`spanner.commit_timestamp()`と組み合わせることで、「外部整合性(External Consistency)」を担保する最強の武器になる。
- 設計のヒント: 監査ログや更新履歴には必ず`COMMIT_TIMESTAMP`オプションを付与せよ。これにより、アプリケーション側で時刻を生成する手間が省け、システム全体で厳密な順序付けが可能になる。
CREATE TABLE AuditLogs (
LogId STRING(36) NOT NULL,
EventData JSON,
— 書き込み時のコミット時刻を自動補完
UpdatedAt TIMESTAMP OPTIONS (allow_commit_timestamp=true)
) PRIMARY KEY (LogId);
—
4. パフォーマンスを最適化する「テーブルインターリーブ」
Spannerの真骨頂は、親テーブルと子テーブルを物理的に隣接させる「インターリーブ(Interleaving)」にある。
もし「ユーザー」と「その注文履歴」を別のテーブルとして定義しても、それらが物理的に離れた場所に配置されていれば、JOINはネットワークを跨ぐコストの高い処理になる。
- 設計パターン: 関連性の高い親子関係は、必ず`INTERLEAVE IN PARENT`を指定せよ。これにより、親子データが同じスプリット(物理領域)に格納され、JOINのパフォーマンスが劇的に向上する。
—
結論:アーキテクトからの提言
Spannerの設計において、以下の3点を常に自問自答してほしい。
1. 「このデータ型は、検索時のインデックス効率を最大化しているか?」
2. 「このプライマリキーは、書き込みを全ノードに分散させているか?」
3. 「このテーブル構成は、JOINコストを物理層で回避できているか?」
Spannerは、あなたの設計が「分散システム」であることを理解しているか否かを、レイテンシという形で容赦なく突きつけてくる。便利なツールを使うのではない。Spannerという巨大なエンジンの特性を理解し、それに調和する「書き方」を身につけること。それこそが、世界最高峰のエンジニアへの近道だ。
さあ、コードを開け。あなたの設計は、地球規模の負荷に耐えられるか?
コメント