【実務・中級編】 外部キー制約 – Cloud Spanner

Cloud Spannerの外部キー:その「便利さ」の裏側にある物理的現実を理解せよ

Spannerの設計レビューで最も議論が紛糾するポイントの一つが、外部キー(Foreign Key)制約の扱いだ。

「RDBなんだから当然外部キーを貼るべきだ」という保守的な主張と、「分散DBのパフォーマンスを最大限に引き出すために、整合性はアプリケーションで担保すべきだ」というモダンな主張。どちらも一理ある。だが、Spannerにおける外部キーは、単なるデータ整合性のツールではなく、分散トランザクションの挙動を左右する極めて強力な「制約」であることを忘れてはならない。

今日は、チーフアーキテクトの視点から、Spannerにおける外部キーの「真の設計論」を叩き込む。

—

1. 外部キーは「分散トランザクション」のコストを増幅させる

まず前提を共有しよう。Spannerにおいて、外部キー制約を定義するということは、「関連するテーブル間での同期的な整合性チェックを、分散トランザクションのパイプラインに組み込む」という宣言だ。

  • INSERT/UPDATE時: 親テーブルのキー存在確認(または子テーブル側の整合性チェック)が必須となる。
  • DELETE時: `ON DELETE CASCADE` を有効にしていれば、単一のトランザクション内で芋づる式に削除対象をスキャンし、ロックを取得し、変更をコミットしなければならない。

これが何を意味するか? ホットスポットの発生確率が上がるということだ。特に高頻度でアクセスされる親テーブルに対して、多数の子レコードが紐づく構造で、かつ頻繁な更新・削除が発生する場合、外部キー制約はトランザクションの待ち時間を増幅させるボトルネックとなる。

2. `ON DELETE CASCADE` という名の諸刃の剣

実務で最も注意すべきは `ON DELETE CASCADE` だ。

— ユーザーIDをキーとする注文履歴テーブル
CREATE TABLE Orders (
UserId STRING(MAX) NOT NULL,
OrderId STRING(MAX) NOT NULL,
— 外部キー定義:ユーザーが消えたら注文も消える
CONSTRAINT FK_Orders_Users FOREIGN KEY (UserId) REFERENCES Users (UserId) ON DELETE CASCADE
) PRIMARY KEY (UserId, OrderId);

この定義は確かにコードを簡潔にする。しかし、以下の事実に直面したことがあるか?

  • ロック範囲の拡大: 親レコードを削除しようとした際、関連するすべての子レコードに対して削除操作が行われる。もし子レコードが数万件存在する場合、その削除操作は単一のトランザクションとして実行され、その間、関連するキー範囲はすべてロックされる。
  • トランザクションサイズの制限: Spannerには「1トランザクションあたりの書き込み量(変異の合計)」に上限がある。あまりに巨大なCASCADEは、上限に抵触して `Aborted` を引き起こす原因となる。

【教訓】: 削除対象が予測不可能なほど増大する可能性がある場合は、`CASCADE` に頼らず、アプリケーション側でバッチ削除するか、論理削除を採用することを強く勧める。

3. パフォーマンスを最適化する「インターリーブ(Interleave)」との使い分け

Spannerには、外部キーよりも強力な物理設計である 「インターリーブ(Interleaving)」 が存在する。

  • インターリーブ: 親テーブルのデータと同じスプリット(物理ノード)に子テーブルを物理的に配置する。検索速度は爆速になるが、子テーブル単体での柔軟なスケーリングは難しくなる。
  • 外部キー: テーブルは論理的に離れていても良い。結合や整合性維持が容易だが、物理配置は独立しているため、結合や制約チェックにはネットワークコストが発生する。

設計基準:
1. 「1:N」で、常に親とセットで取得・削除するなら迷わず「インターリーブ」を選べ。
2. テーブル間で疎結合を保ちたい、または将来的に個別の負荷分散が必要なら「外部キー」を選べ。

4. 堅牢な設計のためのベストプラクティス

現場で事故を起こさないために、以下の鉄則を厳守せよ。

1. 制約の付与は「計画的」に: 稼働中の巨大テーブルに後から外部キー制約を追加する場合、Spannerは全データの検証を行う。これは非常に重い処理だ。設計フェーズで決定しておくこと。
2. ロック競合を意識せよ: 外部キー制約によるロックは、対象となる行(キー範囲)を排他制御する。高頻度アクセスが行われるキーに対して、複雑なCASCADEを組むのは自殺行為だ。
3. 整合性は「ビジネスルール」と「DB」の両面で: DB側で外部キー制約を張ることは最低限の防波堤だが、パフォーマンスが理由で制約を外す場合は、必ずアプリケーションレイヤーで整合性を担保するロジックを実装せよ。これを怠ると、分散システムにおける「データ不整合」という悪夢が待っている。

最後に:エンジニアへの問い

外部キーを定義するか否か。それは単なる「設計の好み」ではなく、「システムにどれだけのトランザクション待ちを許容させるか」というアーキテクチャの決断だ。

ツールとしての便利さに溺れず、それが物理的にどう作用し、どのようなロック範囲を生み出すのか。その光景が脳内でシミュレーションできるようになった時、君は真のSpanner使いと言えるだろう。

さあ、コードを開け。その外部キーは、本当にそこにあるべきか?

コメント

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