タイムセール時の在庫引当てタイムアウト ― 行ロック競合の切り分け
ある小売チェーンが運営するECサイトは、Web/APサーバ3台とDBサーバ1台(8コア)で構成され、平常時は同時接続約300で安定している。年2回のタイムセールでは同時接続が約1,200まで増え、開始直後から在庫引当て処理のタイムアウトが毎分400件発生する。在庫テーブルは商品IDを主キーとし、引当て処理は「SELECT ... FOR UPDATE」で行を更新ロックした後、与信チェックと決済APIの同期呼び出し(平均0.6秒、タイムアウト30秒・リトライ3回)を同じトランザクション内で実行し、最後に数量をUPDATEする。トランザクションの平均保持時間は0.8秒で、リトライにより保持時間が伸びるケースも観測されている。監視ではDBサーバのCPU使用率は30%前後、APサーバも40%前後で、DBの待機イベントは行ロック待ちが大半を占める。注文は上位3商品に全体の65%が集中している。アプリ側に在庫キャッシュはなく、緊急予算でAPサーバの台数追加のみ可能で、DBのスケールアップ予算は確保できていない。
1. タイムアウトの主因を特定し、監視結果のどの数値がその根拠になるか説明せよ。
Read the worked answer
主因は「同一商品行に対する更新ロックの保持時間が長いこと」であり、処理能力不足ではない。
根拠は次の4点で説明できる。
- DBのCPU使用率が30%前後で飽和していない。CPUやI/Oがボトルネックなら使用率が高い側に張り付く。低いのに遅い場合は「待ち」が支配的である。
- 待機イベントの大半が行ロック待ちである。つまりトランザクションはDB内の計算で待っているのではなく、他のトランザクションが同じ行のロックを解放するのを待っている。
- 注文の65%が上位3商品に集中している。ロックの競合は同一主キー行に集中して初めて深刻化する。アクセスが全商品に分散していれば行ロック待ちはこれほど増えない。
- トランザクション保持時間0.8秒のうち0.6秒が決済APIの同期呼び出しである。ロックを取ったまま外部通信を待っている構造なので、決済先の応答が遅れるほどロック保持が延び、待ち行列が増幅する。タイムアウト30秒・リトライ3回の設定は、失敗したトランザクションがロックを握ったまま再試行する時間をさらに延ばす。
APサーバのCPUも40%で余裕があり、AP台数を増やせば同時実行数が増えてロック待ちの列が長くなるだけである。インデックス追加も、主キーによる1行更新が既に効いているため改善しない。切り分けの結論は「DB性能」ではなく「ロック保持区間の設計」にある。
2. 恒久対策を立案し、実施順序とあわせて述べよ。
Read the worked answer
対策は「ロック保持区間から外部通信を追い出す」ことを軸に、影響の小さい順に積む。
第1段階(即時・設定変更のみ)
- 決済API呼び出しを在庫更新トランザクションの外に出す。処理を「仮引当(短いトランザクションで確保)→決済→確定(または取消)」の3段階に分割し、引当レコードにステータスと有効期限を持たせる。これでロック保持は数ミリ秒に短縮される。
- リトライ回数を下げ、指数バックオフを導入する。ロック保持中の再試行は競合を悪化させるだけである。
第2段階(設計変更)
- 引当と決済確定の間の不整合を防ぐため、決済APIの冪等キー(注文ID+試行番号)を導入し、確定処理を再実行可能にする。
- 仮引当の期限切れを回収するバッチを用意し、二重販売を防ぐ。
第3段階(必要なら)
- 上位商品の在庫行を分割(商品ID+バケット番号)してロックの粒度を細かくする。ただし在庫の合計管理が複雑になるため、第1・第2段階の効果測定後に判断する。
- APサーバ増強はこの順序の最後、かつ効果が限定的であることを前提に置く。
実施順序は「ロック保持区間の短縮→整合性の担保→粒度の細分化」とする。第1段階だけで待ち行列の大半は解消する見込みで、第2段階を省くと決済失敗時に在庫が戻らない事故が起きる。
3. この対策によって新たに生じる業務上・設計上のリスクを2つ挙げ、その対応を述べよ。
Read the worked answer
リスク1:仮引当の期限切れによる二重販売・機会損失 引当と決済を分離すると、決済に時間がかかった注文の引当が期限切れになり、その在庫が別の客に売れる。逆に、決済が成功したのに引当が切れていた場合は、売り越し(オーバーブッキング)になる。 対応は、引当期限を決済APIの最悪応答時間(タイムアウト30秒×リトライ)より十分長く設定すること、期限切れ予定の引当を延長する仕組みを用意すること、そして売り越しが発生した場合の業務フロー(代替品提案・優先案内・キャンセル補償)を事前に顧客窓口と合意しておくことである。
リスク2:決済成功・確定失敗による不整合と、その再実行時の二重課金 第1段階では「決済は成功したが確定処理が落ちた」状態が起こり得る。ここで単純にリトライすると二重課金になる。 対応は、決済APIの冪等キーと、引当ステータス(仮引当/決済済/確定/取消)を永続化し、確定処理を何度実行しても同じ結果になるようにすることである。加えて、決済済みで確定していないレコードを検出する監視クエリを用意し、一定時間経過したものをアラートにする。
見落としやすい第3のリスクとして、仮引当という新しい状態が増えることで、在庫照会や CS 向け画面の表示定義が変わる点がある。「引当中」「確定済」を区別して表示しないと、現場が在庫数を誤認する。画面定義と運用マニュアルの更新をリリース条件に含めるべきである。