0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

#8|「二人とも空いてる画面を見てたら、どっちが勝つの?」でRace Conditionが牙をむいた 〜在庫も予約も、画面に表示された「空き」を信用しただけでは守れなかった〜

0
Last updated at Posted at 2026-09-04

87DA3DB5-0217-4BA9-BC4E-ECE31A1AAD93.png

前回の最後に出てきた、「残り1個を二人が同時に取ろうとしたら?」という話。

A「じゃあ、ちゃんと聞こうか。」

私「何を?」

A「同時に二人が操作したらどうするの?」

私「そこか。」

A「在庫1個。」

私「うん。」

A「二人とも画面では『残り1』って見えてる。」

私「うん。」

A「ほぼ同時に取ったら?」

私「それはちゃんと確定するときに――」

B「これは典型的な競合状態、Race Conditionですね。複数の処理が同じ古い状態を見たまま、同時に成立しようとする。」

A「そう。それ、画面に『残り1』って出せたから終わりじゃない。」

B「技術的には、そこからトランザクション、排他制御、楽観的ロックなど、確定時の整合性をどう守るかという話につながります。」

私「当時はそんな言葉知らないよ。『最後の1個を二人が取れたらおかしい』だった。」

A「そこは後で詳しく聞く。絶対長くなるから。」

私「一回ちゃんと作れば後は――」

A「……またそれ言ってるな(笑)」

私「何が?」

A「『一回作れば後は楽』。」

私「だって楽になるじゃん。」

A「二回目だからな。まだ言わせておく(笑)」

当時の私は、技術用語から設計していたわけではなかった。先にあるのはいつも、「それだと現場で変じゃない?」だった。その違和感を潰していくと、後から技術の名前が付いてきた。そして在庫についても、まだ全部が実装済みというわけではない。将来的には、消費期限や使用期限が近い在庫を優先して候補に出すことも考えている。

B「期限情報を候補評価に使うなら、優先順位付け、ランキングの考え方ですね。」

A「それももう作ったの?」

私「そこはまだ将来構想。」

A「よかった。」

私「何が?」

A「一個くらい未来に残しておけ(笑)」

でも、同時操作の問題は、未来の話ではありませんでした。予約でも同じことが起きます。

A「じゃあ同じ車の同じ時間を、二人が見てる。」

私「うん。」

A「二人とも画面では『空いてる』。」

私「うん。」

A「ほぼ同時に予約ボタンを押す。」

私「うん。」

A「どうする?」

B「在庫と同じく、競合状態、Race Conditionですね。」

A「やっぱり来た。」

B「画面を開いた時点では二人とも空いていても、一人目の予約が成立した瞬間、二人目にとってはもう空きではありません。」

A「じゃあ画面で重複チェックしただけじゃダメ。」

私「確定するときに、もう一回確認する。」

B「サーバー側で現在の成立条件を再確認する必要がありますね。」

A「そこ、やった?」

私「やった。」

A「……即答した。」

私「そこはちゃんとやったよ。」

A「じゃあ二人が本当にほぼ同時だったら?」

私「それも――」

A「予約ボタン連打されたら?」

私「それも――」

A「通信遅くて、同じ処理がもう一回来たら?」

私「ちょっと待て(笑)」

A「ほら。『重複予約を防ぎました』だけじゃ終われないでしょ。」

B「ここからは単なる重複判定ではなく、競合制御や状態遷移そのものを守る必要がありますね。」

そして予約機能では、さらにキャンセル待ちも実装しました。キャンセル待ち登録から順番到来、確定・辞退、次候補への繰り上げまで実装しています。キャンセル待ち人数の上限や、順番が来た後の確定期限も、予約カテゴリごとに設定できるようにしました。

A「再度確認するが、最初は在庫を見たかっただけだよね?なんで今、お前はWrite Lockと状態遷移の破綻と戦ってんだよ!!(笑)」

私「本当に最初は『何が空いてるか在庫が見たいだけ』だったんだよ!!」

私「気づいたら周りは敵だらけ。」

A「お前がAIと敵のアジトに丸腰で突っ込んで、そこで勝手にシェルター建設してるんだよ(笑)」

私「AIに泣きついて、とりあえず土のう積んでもらっただけなんだけど……」

A「DIYっぽく言うんじゃねえよ(笑)」

B「そのシェルター、単一プロセス内のWrite Lockに、状態機械(State Machine)、確定直前の再チェック、Fail-Closedまで組み合わせて、整合性を守る設計になっています。」

A「じゃあ、どうやってキャンセル待ちの順番と確定競合を守ってるんだ?」

B「単一プロセス前提ですが、更新経路を一本に寄せて、Write Lock → 状態再確認 → 更新 → 保存の順序を固定しています。さらに候補順も決定的な並び順(deterministic ordering)で揃えています。」

A「……土のうじゃねえじゃん(笑)」

例えば、二人が同じ在庫数を見ている。一人目が先に数量を変更した。二人目は、その変更前の古い画面のまま操作する。

私「そのまま保存したら、一人目の変更を潰す可能性あるじゃん。」

B「そこで楽観的ロックです。」

自分が見た時点から、そのデータが更新されていないかを確認する。

A「更新されてなかったら?」

私「そのまま進める。」

A「誰かが先に更新してたら?」

私「そのまま上書きしない。」

B「競合として返す。HTTPで表すなら、409 Conflictですね。」

私「今は409って言われたら分かる。」

A「成長した(笑)」

私「当時は『誰か先に触ってるから、一回見直して』くらい。」

A「利用者から見れば、それでいい。」

そして、同時操作とは少し違うけれど、もう一つあります。二重送信。

A「予約ボタン押しました。」

私「うん。」

A「反応が遅い。」

私「うん。」

A「もう一回押しました。」

私「やりそう。」

A「二回ともサーバーに届きました。」

私「……。」

A「二回予約される。」

私「それは困る。」

B「ここでは冪等性、Idempotencyという考え方が重要になります。」

A「同じ要求が複数回来ても、意図しない二重処理を成立させない。」

B「はい。」

私「当時の俺なら『ボタン二回押しただけで二個減ったらダメじゃん』。」

A「また技術用語にすると急に格好いい(笑)」

さらに、「在庫があるか確認する」「予約できると判断する」「実際に数量を変える」という処理が分かれていたら、その途中に別操作が入る可能性もある。

B「在庫1個を確認した直後、数量を減らす前に別処理がその1個を使う、といったケースですね。」

A「確認した意味なくなる。」

私「だから一連の処理を途中まで成立させたくない。」

B「考え方としては、トランザクションにつながりますね。」

A「確認して、更新して、関連データも変えて、全部成功したら確定。途中で失敗したら戻す。」

B「ロールバックですね。」

A「これはさすがに知ってる(笑)」

私「誰も疑ってないよ(笑)」

A「さっきから『今知った?』みたいに見るから(笑)」

A「じゃあ『同時に何かされたらどうする』編も整理しよう。」

私「どうぞ。」

複数人が同じ古い状態を見て操作する。
B:「競合状態・Race Condition。」
自分が見た時点から更新されていないか確認する。
B:「楽観的ロック。」
他者が先に更新していたら、そのまま上書きしない。
B:「409 Conflict。」
連打・通信再送で同じ処理を二重成立させない。
B:「冪等性。」
関連する複数処理を途中まで成立させない。
B:「トランザクションという考え方。」

※ただし、ここで説明したのはトランザクションという考え方です。現行版はJSONベースなので、一般的なRDBMSのような完全なトランザクションを、すべての更新処理で実現しているわけではありません。一部の処理では、原子的ファイル保存、スナップショットを使った補償ロールバック、競合検知などを使って、途中状態を残しにくくしています。

A「……これ全部、利用者から見たらボタン一個なんだよね?」

私「そうだね。」

A「エンジニアが『そのボタン、裏では結構大変なんですよ』って言いたくなる理由が詰まってる(笑)」

B「見えない部分ほど、データ整合性へ直結しますからね。」

この頃から、私自身も少しずつ、「画面で動いた=完成」ではないと思うようになっていった。画面に表示できた。ボタンが押せた。保存できた。それでも、同時操作されたら? 古い画面だったら? 連打されたら? APIを直接呼ばれたら? 権限が変わっていたら? 会社設定がおかしかったら?見るところが、少しずつ増えていきました。そして次に作ったのが、

マイ在庫。

A「……まだ在庫増やすの?」私「増やす。」

次回会社の在庫がある。現場にも在庫がある。でも、現場仕事をしていると、自分で持っている材料もあります。車に積んでいる物。自分の棚に置いている物。だったら、それも自分専用の在庫として持てばいい。

最初は、それだけのつもりでした。――「どこにあるか」と「誰の物か」が、別の問題だと気づくまでは。

連載公開分(クリックで開く)
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?