変更が待ち続けるとき
一言でいうと
ロック待ち、SQLの実行、再試行の回数は、それぞれ別のバジェットです。タイムアウトを受け取ったという事実だけで、業務変更がなかったとか、もう一度実行しても安全だとか、判断してはいけません。
なぜ必要なのか
祭りの注文2件のキャンセルを元に戻そうとすると、画面がずっと回り続けています。最初の注文は修正されましたが、2つ目の注文を別の担当者が握っています。このとき闇雲に新しい要求を送ると、待機する実行が増えます。ボタンを素早く押す行動が問題を解決するのではなく、同じリソースを待つ列を長くするだけです。顧客にいつ再び押してよいかを説明するには、どこでどれだけ待ち、何をすでに確定したのかを知っている必要があります。
前のラボは、承認したバージョンをUPDATEの条件に入れて、他の変更を上書きしませんでした。しかし、条件が安全であることと、応答が速いことは、別の性質です。合わない条件も、ロックが解けないと評価できません。無限に待たないように上限を決め、失敗したあと、同じ接続を次の要求に渡してよい状態かどうかを確認する必要があります。設定を変えた接続がプールに戻ったあと、他のユーザーの要求まで短い時間で切ってしまうのも事故です。
どう動くのか
PostgreSQLのlock_timeoutは、ロックを取ろうとする個々の試行の待機を制限します。statement_timeoutは、サーバーが文を処理する時間を制限します。1つのバッチにUPDATEが複数あると、それぞれの文とロック取得があるので、どちらの設定も、バッチ全体の経過時間の上限とは同じではありません。API全体の時間バジェットには、接続、複数のSQL、再試行の間の待機、応答の送信も含まれます。
このラボは、小さなバッチのDB待機と実行を、まず分離して観測します。ロックの上限は50–500ms、文の上限はそれより大きく2000ms以下にします。ロックの上限より文の上限が短いと、先にSQL全体がキャンセルされて、ロック専用の上限が何をしたのかを区別しにくくなります。この数字は学習環境の契約であり、本番でそのまま使うことを勧める値ではありません。
設定は、トランザクションの中で、set_configの3つ目の引数をtrueにして適用します。関数が成功したり、エラーでロールバックしたりしたあとは、借りてきた接続の元の設定が残っていなければなりません。セッション範囲の設定を使うと、成功したトランザクションのあとにも上限が残る可能性があります。失敗したときにロールバックされるというテスト1つでは、そのリークを見逃します。そのため、成功・失敗の両方の経路で、設定値と開いたトランザクションの有無を検査します。
2つの行を順に元に戻している途中で、2行目でタイムアウトが出たら、1行目もロールバックしなければなりません。すでに実行した1つ目のUPDATEを元に戻すのは、Pythonの例外の名前ではなく、外側のDBトランザクションです。エラーが出たコンテキストを正常に抜けられるように例外を伝播させ、接続がIDLE状態かどうかを確認します。失敗したトランザクションを開いたまま、次のSQLから再試行すると、エラーが重なるだけになることがあります。
現場での姿
今回は、ロック取得失敗のSQLSTATE 55P03だけを限定して再試行します。文のキャンセル57014、承認内容の衝突、入力エラー、原因のわからない接続エラーは、呼び出し元に伝えます。エラーメッセージを韓国語または英語の文字列で比較せず、ドライバーのエラー分類を使います。分類だけで、あらゆる状況での再試行の安全性が保証されるわけではありません。この例では、補償要求IDと承認内容が固定され、失敗した試行のトランザクションがロールバックされるという契約を、一緒に使います。
retry_undoのattemptsは、最初の呼び出しを含む総試行数です。3なら、最初の1回と再試行2回です。待機関数は失敗した試行の番号1、2を受け取り、最後の失敗のあとには呼び出しません。サービスでは、この待機関数に上限付きの遅延とジッターを入れられますが、このレッスンは回数の制限だけを実装します。全体の経過時間の締め切りや、すべてのサーバーの同時リクエスト数の制限を実装したとは言いません。
ロックが解けたあとも、承認した値が同じだという保証はありません。待たせていた担当者が値を変更してコミットしていたなら、次の試行はConflictで止まる必要があります。再試行を成功させようとして期待するバージョンを現在の値に差し替えると、元の承認の保護がなくなります。待たされたために失敗した要求と、承認内容が古くなって拒否された要求とで、次の行動が違うべき理由です。
次の確認ですること
クイズで、ロック・文・試行のバジェットと設定のリークを区別します。次のモジュールの総合ラボでは、別の接続に2行目を握らせて、実際の55P03を観測し、遅延トリガーで57014も発生させます。同じ接続が片付けられたか、1行目が残っていないか、ロック解除のあとの同じ補償IDの再試行が1回だけ効果を出したかを、SQLで確認します。
参考: PostgreSQLの接続のデフォルト設定、エラーコード、psycopgのトランザクション管理。2026-09-13に確認したもので、実際の実行環境はPostgreSQL 16とpsycopg 3.2.3です。