目次
残り一つの商品を二人が同時に注文したとき、先に在庫を読んだ処理が両方とも購入可能と判断する場合があります。画面の二度押しを防いでも、別の端末や別の要求は同時に到着します。アプリが守る業務の条件を、更新の確定まで維持する必要があります。
本記事は2026年10月18日にPostgreSQLの公式資料を確認した実務解説です。事件の速報ではなく、注文とクーポンは仮想例です。既存のWebhookの重複受信の記事とは分け、注文を作る側の同時実行とDBの整合性を中心に扱います。設計と試験の順序は本サイトの提案です。
スポンサーリンク
最初に破ってはいけない業務条件を書く
本サイトの提案は、実装の方式を選ぶ前に条件を文章へすることです。たとえば確定した販売数が販売可能数を超えないこと、利用者が同じクーポンを一度だけ使えること、決済済みの注文を再び課金しないことは、似ていても別の条件です。
どの時点で在庫を確保し、いつ解放するかも決めます。注文ボタンを押した時点、決済を始めた時点、決済が確定した時点では、キャンセルや障害の扱いが変わります。残数を表示することと、購入を確定する権利を確保することを区別します。
業務条件が曖昧なままロックを増やすと、処理は遅くなっても守りたい条件を満たさない場合があります。複数の行や外部サービスが関係する条件かどうかを、台帳へ明示します。
出典:PostgreSQL Transaction Isolation。
確認と更新の間へ別の処理が入る
在庫を取得し、アプリで残数を判断し、少し後に値を書き戻す流れでは、その間に別の要求が同じ在庫を使う場合があります。HTTPの要求を順番に試すだけでは、この間の競合が見えません。
DBのトランザクションを使うことは重要ですが、囲めばあらゆる競合が防げるわけではありません。PostgreSQLの既定のRead Committedでは、同じトランザクションの複数の参照が異なる確定済みの状態を見る場合があります。
利用するDBの分離レベルと更新の動作を公式資料で確認します。他のDBへ同じ名前の設定を当てはめても、すべての挙動が同じとは限りません。ORMを使っている場合も、実際にどの問い合わせと境界が作られるかを把握します。
スポンサーリンク
単純な残数なら条件と更新をDBで結び付ける
一つの在庫行で判断できる構成では、十分な残数がある場合だけ数量を減らす条件付き更新を検討できます。更新できた行数などを確認し、条件を満たさなかった場合は購入を確定しない設計にします。
これはすべての在庫管理への完成形ではありません。倉庫、予約、組み合わせ商品など複数の状態が関係すると、一行だけでは業務条件を表せない場合があります。アプリが先に読んだ値だけで最終判断せず、確定時にどの条件を守れるかを検証してください。
更新に失敗した後、エラーを無視して注文や決済だけを進めないようにします。残数の表示と確定結果が違う場合はあり得るため、利用者へ購入可否を伝える画面と、再選択の手段も必要です。
ロックには順番と待ち時間の設計が必要になる
複数の状態を確認してから更新する場合、適切な行のロックなどを検討します。ロックを取る対象が業務条件をカバーしているか、同じ処理のすべての入口が同じ規則を使うかを確認します。管理画面の修正だけが規則を迂回しないようにします。
本サイトの提案として、複数の対象を扱うときは取得する順番をそろえ、待ち時間と失敗時の扱いを決めます。ロックを持ったまま外部の決済やメール配信を待つと、遅延が他の利用者へ広がる場合があります。
より強い分離レベルを選ぶ場合も、処理が競合で中断される条件を想定します。PostgreSQLの資料は、関連する失敗でトランザクション全体を再試行する必要を説明しています。再試行の回数と待ち方を限定し、無期限に繰り返さない設計にします。
スポンサーリンク
一意性の制約と冪等性は守る条件が違う
同じ処理の再送を同じ結果へまとめる冪等性と、業務上許されない組み合わせを作らない一意性は別です。要求ごとに新しいキーを付けられるなら、冪等性のキーだけでは同じクーポンの複数利用を防げない場合があります。
本サイトの提案は、利用者とクーポンの組み合わせなど、業務に合う一意性をDBの制約で表せるか検討することです。制約違反が起きたときの業務上の応答も用意し、単なる内部エラーで終わらせないようにします。
PostgreSQLのCHECK制約は、別の行やテーブルを自由に参照して整合性を保証する仕組みではありません。行をまたぐ条件には、その条件に合う制約やロックなどを選びます。NULLの扱いも含め、利用する制約の仕様を確認してください。
外部決済はDBのロールバックだけでは戻らない
DBのトランザクションが失敗しても、すでに外部で成立した課金が自動的に取り消されるとは限りません。DBの中と外の処理を一つの原子的な操作と見なさず、状態と照合の方法を設計します。
本サイトの提案として、決済要求の識別、実行中、結果不明、確定、取消などを区別し、外部の公式仕様に沿って状態を問い合わせられるようにします。応答を受け取れなかったから失敗と決め、別の課金を始めないことが重要です。
後続の処理を確実に届けるためのアウトボックスなども、用途に応じた選択肢です。ただし、一つの方式名だけで全体の一度きり実行を保証しないでください。受け手の重複処理、再送、運用者による照合まで含めて確認します。
二つの要求を実際に重ねて検証する
テスト用の在庫と利用者を用意し、別の接続から同じ条件を同時に更新します。単純に二回連続で実行するのではなく、確認と更新の境界で処理が重なる条件を作ります。第三者のサービスへ大量の要求を送る必要はありません。
同じ冪等性のキーを持つ要求と、異なるキーだが業務上競合する要求を分けます。成功の件数、在庫、クーポンの利用記録、注文の状態を照合し、エラー表示だけでなく保存結果で判断します。
外部処理はテスト環境や模擬の実装を使い、外部で成功した直後にアプリが止まる条件も試します。復旧後に照合でき、再実行で追加の副作用が起きないことを確認します。
在庫の予約期限を業務の状態として扱う
決済の前に在庫を確保する仮想の設計では、確保したまま利用者が離脱する場合があります。本サイトの提案は、予約中と確定済みを分け、期限と解放の条件を明示することです。解放と決済の確定が同時に進んだとき、どちらが最終の状態を決めるかも定義します。
定期ジョブが期限切れの在庫を戻すとき、古い一覧を読んだだけで処理せず、現在も解放できる状態かを更新時に確認します。解放処理の再試行で在庫が何度も増えないことと、正規の確定で不足しないことを合わせて検証します。
画面の残数は多少古くても、購入の確定時に条件を守れる設計が必要です。表示を完全に同期させる難しさと、確定した業務の整合性の必要性を分けると、どこへ強い制御を置くべきか判断しやすくなります。
スポンサーリンク
冪等性のキーの持ち主と内容を結び付ける
同じキーなら何でも以前の結果を返す設計では、別の利用者や別の操作の結果を返す問題が起きる場合があります。本サイトの提案は、キーを利用者や業務上の操作と対応させ、同じキーで内容が異なる要求をどう扱うかを決めることです。
保持期間、実行中の要求への応答、途中で結果が不明になった場合の照合も仕様へ含めます。キーを保存するだけでは、業務の更新とその記録が同時に確定する保証はありません。両者の確定の境界がどこにあるかをレビューします。
保存済みの結果には、利用者へ返す権限のない機密情報を混ぜないようにします。重複を止めるための記録が、新しい情報漏えいの場所にならないことも確認します。これらは特定の決済APIの仕様ではなく、アプリ側で決める条件の整理です。
デッドロックと正常な売り切れを別の結果にする
競合で再試行すべき失敗と、在庫がないので購入できない結果は異なります。すべてを自動で再試行すると、成功しない要求で負荷を増やす場合があります。使うDBと業務の条件に基づき、再試行できる結果だけを選びます。
試験では、複数の商品を異なる順で注文する場合、途中で接続が切れる場合、制約違反が起きた場合も代表させます。処理の一部だけを繰り返すのか、全体をやり直すのかを曖昧にせず、外部の副作用を重ねないことを確認してください。
スポンサーリンク
競合を記録して正常な混雑と区別する
監視では、条件付き更新の不成立、制約違反、ロック待ち、再試行の回数、結果不明の状態を関連付けます。残数が尽きて拒否した正常な結果と、長時間の待ちや状態の不整合を区別すると、運用者が対応しやすくなります。
機密の決済情報を記録せず、注文や処理の安全な識別子で追えるようにします。利用者からの二重課金の問い合わせを、HTTPの成功回数だけで判断しないことも必要です。
同時実行の対策は、ボタンを無効化するUIからDB、外部サービスまでの役割分担です。業務条件を決め、それを確定時に守る仕組みを選び、途中で止まっても照合できる状態を残すことが基準になります。
参考資料
以下の一次資料を2026年10月18日に確認しました。本文の仮想例と運用手順の提案は、資料の引用とは区別しています。









