代表的な面接トピック

技術面接全般:Serializable 分離レベルは何を保証するのか、そしてなぜトランザクションのリトライが必要なのか?

一般難しい
Offer.cc 編集チーム公開日 更新日

質問

Repeatable Read と Serializable の違いを説明してください。Serializable は何を保証し、なぜアプリケーションは直列化失敗(Serialization Failure)を処理してリトライしなければならないのでしょうか?

プロンプトとコンテキスト

面接官から、同時に実行される2つのトランザクションが提示されます。これらは同じビジネスルールを読み取りますが、異なるレコードを更新します。双方が十分なキャパシティを確認したにもかかわらず、最終的な状態はルールに違反してしまいます。より弱い分離レベルが何を許容してしまうのか、Serializable がどのようにその結果を防ぐのか、そしてアプリケーションがどのように障害を処理するのかを説明してください。

これはデータベースの並行性制御の境界をテストするものです。4つの分離レベルの名前を暗記して暗唱するのではなく、スナップショットの可視性、ロック待ち、競合検出、アプリケーションのリトライを明確に区別してください。

面接官が見ているポイント

面接官は、アノマリーを説明する具体的なインターリービング(交互実行)、『ある直列な順序(serial order)と同等』と『すべてのトランザクションが順番待ちする』の違い、そしてなぜアボートすることが保護の一部であるのかの説明を求めています。Amazon のプロダクト面接ガイダンスでは、決定をメトリクスや根拠と結びつけることが求められますが、技術的な回答においても同様に、保証、コスト、アプリケーションのアクションを述べる必要があります。

まず確認すべき明確化のための質問

対象のデータベースと実装、デフォルトの分離レベル、読み取り/書き込みパターン、不変条件(インバリアント)をデータベースの制約として表現できるか、そして呼び出し側が安全にトランザクションを再実行できるかを確認します。PostgreSQL の Repeatable Read はトランザクションスナップショットを使用し、Serializable は発生し得る直列化アノマリーの検出を追加して一方のトランザクションをアボートします。デフォルト値や実装はデータベースによって異なります。

30秒で答える構成案

結論から始めます:Serializable はコミットされた結果が「何らかの直列実行と同等」であることを要求しますが、すべてのトランザクションがキューイングされることを意味するわけではありません。2つのトランザクションによる Write Skew のインターリービングを用いて Repeatable Read の限界を示し、その後 Serializable が危険な依存関係を検出して直列化失敗を返す仕組みを説明します。最後に、トランザクション全体のリトライ、試行回数の制限、冪等性、競合のモニタリングで締めくくります。

ステップ・バイ・ステップ解説

ステップ 1: インターリービングを用いてアノマリーを説明する

「少なくとも1人の当直医がアクティブな状態を維持する」というルールがあるとします。トランザクション A は医師 B がアクティブであることを読み取り、トランザクション B は医師 A がアクティブであることを読み取ります。A は自身を非番に変更し、B も同様にします。各トランザクションは自身の行のみを更新するため、直接的な Write-Write 競合はありませんが、最終的な状態では当直医が誰もいなくなります。この Write Skew は、個々には有効なスナップショットであっても、それらを組み合わせたビジネスインバリアントを常に維持できるとは限らないことを示しています。

ステップ 2: スナップショットの一貫性と直列等価性を区別する

Repeatable Read はトランザクションに安定したビューを提供し、その後に並行してコミットされた変更を不可視にします。しかし、読み取りと書き込みの完全なセットを、あらゆるビジネスルールを維持できる直列な順序に並べ替えられることまでは約束しません。スナップショット分離(Snapshot Isolation)は並行性を向上させることができますが、アプリケーションはそれがどのアノマリーを防ぎ、どのアノマリーを発生させ得るのかを把握しておく必要があります。

ステップ 3: Serializable の保証内容を述べる

Serializable は、コミットされた結果がトランザクションを1つずつ順番に実行した場合と同等になることを目指します。実装にはロック、競合検出、または Serializable Snapshot Isolation が用いられる場合があり、すべての読み取りが他のすべてのトランザクションをブロックする必要はありません。PostgreSQL は読み書きの依存関係を監視し、直列化アノマリーを形成する可能性がある場合にトランザクションを失敗させます。

ステップ 4: なぜトランザクション全体をリトライしなければならないのかを説明する

失敗はコミット時またはコミット直前に発生する可能性があり、データベースはアプリケーションロジックを安全に再実行できるかどうかを推測できません。最後の UPDATE のみを再送するのではなく、ロールバックして最初の読み取りから再実行してください。リトライ回数を制限し、バックオフを使用します。外部への副作用はコミット後に移動するか、冪等性キー(Idempotency Key)と永続的なイベントレコードを用いて隔離します。

ステップ 5: コストを比較して選択する

Serializable は、アクセスパターンに応じて競合、リトライ、メモリやロック管理のオーバーヘッド、レイテンシを増加させる可能性があります。強いインバリアントを伴う残高、在庫、クォータなどの場合、そのコストは正当化される可能性があります。近似値が許容される読み取り専用レポートでは、より弱い分離レベルを使用できます。インバリアント、並行性、目標レイテンシ、障害処理能力を総合的に考慮して選択してください。

質の高い模範解答

Serializable は、コミットされたすべての結果が何らかの直列な順序と同等であることを保証しますが、データベースがすべてのトランザクションをキューイングすることを要求するものではありません。「少なくとも1人の医師が当直である」というルールの下では、2つの Repeatable Read トランザクションがそれぞれもう一方の医師が当直中であることを確認し、自分自身を非番としてマークすることができます。両者は同じ行を更新しませんが、結果として Write Skew を引き起こし、インバリアントに違反します。

Serializable は、このアノマリーを引き起こす可能性のある読み書きの依存関係を検出し、一方のトランザクションを直列化失敗(Serialization Failure)として終了させます。アプリケーションはロールバックし、上限回数の設定とバックオフを伴って最初からリトライしなければなりません。メール送信、課金、その他の外部への副作用は、コミット後に行うか、冪等性キーの配下に置く必要があります。私なら、在庫、残高、クォータにはより強い分離レベルを使用し、近似値レポートにはアノマリー率やリトライ率を監視しつつ、より弱い分離レベルを検討します。

よくあるミスと改善点

  • Serializable をグローバルなキューイングと説明してしまう:『何らかの直列な順序と同等』と表現します。
  • ロックについてしか言及しない:競合検出や実装の違いも含めます。
  • 最後の SQL 文のみをリトライする:トランザクション全体を再実行します。
  • 副作用を無視する:コミット後イベント、冪等性キー、または重複排除レコードを使用します。
  • Repeatable Read にはアノマリーが存在しないと言ってしまう:Write Skew や直列化アノマリーの存在を認めます。

フォローアップ質問と回答

読み取り専用トランザクションが Serializable で失敗することがあるのはなぜですか?

その読み取り操作が、他のトランザクションのコミット結果を非直列化可能にしてしまう依存関係に関与する場合があるためです。データベースはグローバルな保証を維持するためにトランザクションをアボートすることがあります。アプリケーションは、操作を安全に繰り返すことができる場合に、このエラーをリトライ可能なものとして扱う必要があります。

すべてのリクエストで Serializable を使用すべきですか?

いいえ。ビジネスインバリアントとワークロードから検討を始めてください。アノマリーが許容されない箇所ではより強いレベルを使用し、近似的な読み取りが許容されパフォーマンスのトレードオフが測定されている箇所ではより弱いレベルを選択します。

なぜリトライにすべての読み取りを含めなければならないのですか?

競合の判定は、読み取りと書き込みの完全なセットに依存しているためです。最後の書き込みのみを再実行すると、古い前提条件が使用され、同じアノマリーが再発する可能性があります。

リトライが健全に行われているかをどのように観測しますか?

トランザクションタイプごとに、直列化失敗、リトライ成功率、リトライレイテンシ、リトライ上限到達数、ユーザーに見えるエラーを追跡します。リトライ率の高さは、競合の激しいインバリアントや、再設計が必要なアクセスパターンを示している可能性があります。

公開情報ソース

関連する質問