Cloud Spannerの「ロック」を制する者は、分散システムの極致を制す
Spannerを単なる「SQLが使えるNoSQL」や「巨大なMySQL」だと思っているなら、今すぐその認識を捨ててほしい。
Spannerの真価は、TrueTimeによる外部整合性の担保と、その上でいかにして「高い並行性」と「厳密な一貫性」を両立させているかという、ロック管理の妙にある。実務でSpannerを叩くエンジニアが直面する「なぜここで遅延するのか?」「なぜデッドロックが起きるのか?」という問いに対し、その深淵を解き明かそう。
—
1. ロックの正体:Spannerの並行制御の解像度
Spannerのロックは、単なるDBエンジン内部のフラグではない。各スプリット(Split)内の`Lock Table`で管理される論理的な制御機構だ。
共有ロック (Shared Lock) と 排他ロック (Exclusive Lock)
- 共有ロック (S): 読み取り操作(Read)で取得。複数トランザクションが同時に取得可能。
- 排他ロック (X): 書き込み操作(Write)で取得。他のいかなるロックも許容しない。
「極限の知見」:
多くのエンジニアは「読み取りはロックしない」というMVCCの幻想を抱く。しかし、Spannerのデフォルトのトランザクション(Read-Write)では、読み取り対象の行にもロックがかかる。これを理解していないと、読み取りが多いシステムで突如としてスループットが頭打ちになる現象を説明できない。
—
2. 実務で遭遇する「パフォーマンス・アンチパターン」
以下のコードは、一見問題なさそうだが、大規模アクセス時には地獄への入り口となる。
— よくあるアンチパターン:全行を読み取ってから更新する
— Read-Writeトランザクション内で行うと、全読み取り行に共有ロックがかかる
SELECT balance FROM accounts WHERE status = ‘ACTIVE’;
— … アプリケーションロジック …
UPDATE accounts SET balance = balance – 100 WHERE id = 1;
なぜこれがダメなのか?
1. ロックの肥大化: `SELECT`で取得した全行に共有ロックが保持され続ける。
2. 競合の誘発: 他のトランザクションがその範囲に書き込もうとすると、共有ロックと排他ロックが衝突し、待機が発生する。
3. デッドロックのリスク: 複数のトランザクションが互いに相手がロックしている行を待ち合う状況が生成される。
—
3. ロックの戦術的設計:堅牢なシステムを作るために
実務におけるSpannerの設計指針は、「ロックの寿命を最短にする」ことだ。
戦術①:読み取りと書き込みの分離
`ReadOnly`トランザクションを徹底せよ。`ReadOnly`トランザクションはロックを取得せず、TrueTimeを利用して一貫性のあるスナップショットを読み取る。これにより、書き込みの足を引っ張ることがない。
戦術②:ロック対象を限定する
クエリの範囲を可能な限り狭めろ。全行スキャンを伴うクエリをトランザクション内に入れるのは自殺行為だ。
戦術③:デッドロック検出と再試行
Spannerは内部的にデッドロック検出器を走らせている。もしデッドロックが発生すれば、`Aborted`エラーが返される。
ここで重要なのは、「クライアント側での指数バックオフ付きリトライ」を実装することだ。
// Goでのリトライ戦略の骨子
err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn spanner.ReadWriteTransaction) error {
// 1. ロック取得を伴うRead
// 2. 状態判断
// 3. BufferWriteによる更新
return nil
})
// Spannerのライブラリは適切に再試行を行うが、
// アプリケーション層でも「競合が発生した前提」の設計が必要。
—
4. チーフアーキテクトからの助言:設計の核心
Spannerにおいて、ロックは悪ではない。「整合性を守るためのコスト」だ。
- ホットスポットを避ける: 単一の行に対して高頻度で更新が走る設計(例:カウンターテーブル)は、どれだけSpannerでもロック待機で詰まる。カウンターをシャード化する(例えば10個の行に分散して書き込み、読み取り時に合計する)のが、Spanner使いの常套手段だ。
- トランザクションを跨がない: 外部システムとのAPI通信をトランザクション内で行うな。ロックの保持時間が伸び、システム全体が連鎖的に崩壊する。
結論:
Spannerを使いこなすということは、「どこでロックが発生し、それがどの程度持続するか」を脳内でシミュレートできることを意味する。
ロックを恐れるな。しかし、ロックを尊重せよ。無意味なロック保持を避け、最小限のスコープで整合性を担保する。その設計こそが、世界最高峰の可用性とスケーラビリティを実現する唯一の道だ。
次の設計レビューでは、「このトランザクション、ロック範囲はどれだけ絞れる?」とメンバーに問うてみてほしい。そこからが、本当のSpannerエンジニアリングの始まりだ。
コメント