テクノロジー · 実用ガイド

PostgreSQL SQLSTATE 40001?失敗した文ではなくトランザクション全体を再試行する

SQLSTATE、分離レベル、失敗境界を記録し、ロールバック後に読み取りと判断をすべて制限付きで再実行します。不可逆な副作用はコミット後へ移します。

オリジナル技術イラスト: オリジナル図:40001 後は BEGIN から全体を再生し、外部副作用はコミット後に置く。
オリジナル技術イラスト: オリジナル図:40001 後は BEGIN から全体を再生し、外部副作用はコミット後に置く。 Watch at Ease

SQLSTATE 40001 は PostgreSQL の serialization_failure です。Repeatable Read と Serializable で起こり、COMMIT まで返らない場合もあります。拒否された単位はトランザクション全体であり、最後の文だけの再試行は無効なスナップショットから作った判断を再利用します。

安全な処理はトランザクション callback と COMMIT 全体を囲みます。40001 を確認し、ロールバックし、上限付きのバックオフとジッター後に BEGIN から読み取り、判断、書き込みを再実行します。DB は SQL と値を選んだアプリケーションロジックを自動再生できません。

再試行で競合を隠してはいけません。回数か時間の上限と指標を設け、継続する失敗を表面化します。メール、課金、公開などの不可逆処理はコミット後に置き、outbox や冪等キーで配信へ接続します。

再試行前に失敗境界を記録

SQLSTATE、分離レベル、試行回数、経過時間、文か COMMIT かを記録します。制御はメッセージ文字列ではなく SQLSTATE を使います。

COMMIT 時の失敗は想定内です。安全な直列順序の判断に完全な読み書き集合が必要な場合があります。

BEGIN から読み取りと判断を再生

ロールバック後、最初の読み取りから再開します。存在確認、在庫、価格、権限、派生値を再計算します。失敗 UPDATE や savepoint だけでは古い仮定が残ります。

callback 一回を DB 試行一回とし、callback と COMMIT の両方を同じ経路で捕捉します。

再試行を制限し競合を可視化

少ない最大回数か総時間、バックオフ、ジッターを設定します。各回で状態をリセットし、中止済みトランザクションを再利用しません。

操作別に計測し、枯渇が続けば長い取引、ホットキー、索引、プール、分離要件を調べます。単純に分離レベルを下げません。

副作用をコミット安全境界の外へ

callback 内は複数回動きます。コミット前にメール、課金、公開、非冪等 API を実行しません。

同じ取引で outbox を書き、別 worker がコミット後に送信します。外部では安定した業務要求から冪等キーを作ります。

近い SQLSTATE を別経路へ

40P01 はデッドロックで、再試行と同時にロック順序を調査します。23505 の自動再試行にはアプリ固有の証明が必要です。

40003 は完了状態不明なので再実行前に照合します。class 40 全体を一括再試行すると重複の危険があります。

オリジナル技術イラスト: オリジナル表:40001、40P01、23505、40003 を SQLSTATE ごとに分岐する。
オリジナル技術イラスト: オリジナル表:40001、40P01、23505、40003 を SQLSTATE ごとに分岐する。 Watch at Ease