並行・再送下の正しさはアプリ層のcheck-then-actでなくDB制約と原子操作・冪等経路で担保する
データベース
並行処理
API設計
冪等性
判断
原則
同時実行や再送がある経路では、read してから write する判定(check-then-act)を信用しない。一意性・上限・一度きりの消費・冪等性は、最終防壁を DB の制約や原子的な1文・条件付き更新に置く。最適化するのは「アプリのロジックがどうであれ破綻しない不変条件」、避けるのは「判定と記録が原子的でない隙にレースで突破される設計」。
判断基準
- 「あれば既存・なければ作成」は先行 read でなく、一意制約を張ったうえで「競合時は無視(ON CONFLICT DO NOTHING 相当)」で挿入し、作成されなければ取り直す。アプリ層チェックだけでは原理的に重複を防げない。
- レート制限・クォータなど上限系は、判定と記録を原子化する(増分と判定を1文にする、または記録を同期化して応答前に書く)。記録を fire-and-forget にすると窓が広がる。
- 認可コードやトークンの一度きり消費は、未使用・未失効・期限内を条件に含めた更新を1文で行い、影響行数が1のときだけ後続を進める。読み取り後に別更新する方式はレースで再利用される。
- at-least-once な webhook など再送前提の入口は、一意な event ID を一意制約付きで記録し、影響行数で初回/既処理を原子的に判定して既処理は副作用なく早期 return する。
- 外部副作用を伴う多段 write は、最初の副作用で生成された ID をレスポンスに含め、再実行時はその ID を受けて副作用をスキップする冪等経路を用意する。
落とし穴 一意制約を後付けする場合は既存重複の解消が前提。関数インデックスに対する競合解決は式を一致させないと効かない。
検証 上限境界や同一キーで同時並行・二重送信を起こし、許可数が上限を超えない・重複行が作られない・二回目が副作用なく終わることを実 DB で固定する。mock では制約破綻を検出できない。
根拠(synthesize 元)
- 522 get-or-create の race はユニーク制約 + 競合時無視 + 再取得で吸収する
- 531 クォータ/レート制限の check-then-act は TOCTOU で並行突破される
- 525 Webhook の冪等性は event ID を一意制約付きテーブルに記録して担保する
- 405 OAuth code と refresh token の消費は条件付き更新で再利用レースを防ぐ
- 488 リフレッシュトークン運用の正しい設計と失敗パターン
- 567 分割 write は最初の副作用 ID を返して冪等 retry 経路を作る