【実務・中級編】 一意性制約 – Cloud Spanner

Cloud Spannerにおける「一意性制約」の極意:分散環境で整合性を守るためのアーキテクチャ設計

Cloud Spannerは「分散データベースである」という事実を忘れた瞬間に、君たちのシステムは破綻する。

多くのエンジニアがRDBMSの感覚で一意性制約(UNIQUE constraint)を安易に設計し、後になって「パフォーマンスがスケールしない」「ホットスポットで死ぬ」と頭を抱える。Spannerにおいて一意性を保つことは、単なるDDLの記述ではない。それは「分散されたノード間で、いかにして最小のコストで整合性を保証するか」という高度な分散コンピューティングの設計そのものだ。

今日は、Spannerで「一意性」を実装する際の、現場で絶対に外してはならない勘所を伝授する。

—

1. なぜSpannerの一意性制約は「コスト」がかかるのか

Spannerの一意性制約は、バックグラウンドで自動的にユニークインデックスを生成することで実現される。

ここで重要なのは、Spannerのインデックスは「別のテーブル」として管理されているという点だ。メインのデータ行を書き込む際、Spannerは同時にそのインデックス行も更新しなければならない。これは分散トランザクションにおいて、「別の場所にあるデータ」に対して同期的な書き込みを発生させることを意味する。

実務における鉄則:

「一意性制約は無料ではない」。インデックスを貼れば貼るほど、書き込みのレイテンシは増大し、トランザクションの競合確率は跳ね上がる。これを理解せずに制約を乱用するのは、エンジニアとしての怠慢だ。

—

2. 実装パターン:DDLでの定義

基本はシンプルだ。`UNIQUE` キーワードを使うか、`INDEX` を明示的に定義する。

— パターンA: テーブル定義時に制約を付与(簡便だが制御しにくい)
CREATE TABLE Users (
UserId INT64 NOT NULL,
Email STRING(255) NOT NULL,
— Email列に一意性制約を定義
UNIQUE INDEX UsersByEmail (Email)
) PRIMARY KEY (UserId);

— パターンB: 後からインデックスとして追加(運用上のベストプラクティス)
— FORCE_INDEX_BACKFILLを指定することで、既存データとの整合性を保ちつつ非同期で構築可能
CREATE UNIQUE INDEX UsersByEmail ON Users (Email) STORING (Name);

チーフアーキテクトの視点:

`STORING` 句を有効活用せよ。インデックス検索だけで値を解決できれば、メインテーブルへの `JOIN` を防げる(インデックス・オンリー・スキャン)。「何を参照するために一意性が必要か」を考え、必要な属性を `STORING` に含めるだけで、クエリのレスポンスは劇的に改善する。

—

3. 分散環境特有の「罠」を回避する設計

① ホットスポットの回避(UUIDの活用)

もし、一意性を保つためのカラムが「連番」に近い場合、特定のノードに書き込みが集中し、Spannerの真価である水平スケールが無効化される。一意性制約を伴うキーには、ランダムな分散特性を持つUUID(特にv4やUUIDのビット反転など)を採用するのが定石だ。

② 「論理削除」と一意性の共存

よくある失敗例が、「削除済みフラグ」を無視して一意性制約を貼ることだ。

  • `User`テーブルで `Email` に一意性を貼る。
  • ユーザーが退会(論理削除)し、同じ `Email` で再登録しようとするとエラーになる。

解決策:
`Email` と `Status` (有効/無効)を組み合わせた「フィルタ付きインデックス」のような考え方、あるいはアプリケーション層での制御が必要だ。Spannerには部分インデックス(WHERE句を含むインデックス)という強力な武器があることを忘れるな。

— 有効なユーザー間だけで一意性を保つ(実務で多用するテクニック)
CREATE UNIQUE INDEX UsersByEmailActive
ON Users (Email)
WHERE Status = ‘ACTIVE’;

—

4. パフォーマンス上の注意点:読み取りと書き込みのトレードオフ

一意性制約の裏側では、常に `Paxos` プロトコルによる合意形成が行われている。

1. 書き込み時: インデックスの整合性を取るために、リーダーノード同士の通信が発生する。
2. 読み取り時: 強整合性(Strong Read)を要求すれば、インデックスが最新であることを確認するためのコストがかかる。

「どうしても一意性が必要か?」を常に自問自答せよ。もし数ミリ秒の遅延が許されない超高頻度書き込みシステムなら、一意性制約をDBに持たせず、Redisのような高速なKVSで排他制御を行い、DB側は非同期でバリデーションするという「Eventual Consistency」な設計を選択する勇気も必要だ。

—

最後に:プロフェッショナルの設計とは

Spannerの一意性制約は、強力な武器であると同時に、扱いを間違えればシステムの足を引っ張る足枷にもなる。

  • 無闇にインデックスを増やさない。
  • `STORING` 句で読み取り効率を極限まで高める。
  • `WHERE` 句を活用して、制約の範囲を論理的に最小化する。

これらを守るだけで、君たちのシステムはSpannerのパワーを最大限に引き出せるはずだ。設計は常に「何を犠牲にして、何を得るか」のトレードオフだ。その決断から逃げるな。

不明点があれば、またコードレビューの場を設けよう。期待している。

コメント

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