技術面接や実務のコードレビューで、よくある形式がある。
「ジュニアエンジニアが書いたコードです。問題点をレビューしてください」
一見すると普通に動いて見える。
テストも通っている。
ローカルでも再現しない。
でも、本番でだけ壊れる。
その代表例が race condition(競合状態) だ。
今回は、実際のレビューでかなりありそうなコードを例に、「何が問題なのか」「なぜ危険なのか」「どう直すべきか」を実務目線で整理してみる。
例えば、ECサイトのポイント交換機能。
ユーザーが100ポイント使って報酬を受け取る処理がある。
ジュニアエンジニアがこう書いた。
user = db.get_user(user_id)
if user.points >= 100:
user.points -= 100
user.save()
send_reward(user)ぱっと見、問題なさそうに見える。
ポイント確認している
100引いている
保存している
報酬送っている
ローカルで試しても普通に動く。
でも、このコードには本番でかなり危険な問題がある。
このコードの危険性は、「複数リクエストが同時に来た時」を想像すると見えてくる。
例えば:
ユーザーが連打した
通信リトライが起きた
フロントが二重送信した
API gateway が retry した
すると、同じ処理が同時に走る可能性がある。
例えばこんな順番。
points = 100 を読むpoints = 100 を読む100 >= 100 OK
points = 0
save100 >= 100 OK
points = 0
save結果:
報酬が2回送られる
実際には100ポイントしかない
二重交換成功
つまり、「チェック」と「更新」の間に別処理が割り込めてしまう。
これが race condition。
race condition が厄介なのは、再現しづらいこと。
普通にクリックした程度では起きない。
でも本番では:
高トラフィック
retry
非同期処理
複数サーバー
モバイル通信不安定
などで、同時実行は普通に起きる。
だからレビューでは、
「正常系」ではなく「競合時」を想像できるか
がかなり重要になる。
ここでジュニアエンジニアがよく言う。
「でも save() してますよ?」
問題はそこではない。
危険なのは:
if user.points >= 100:この判定と、
user.save()この更新が分離していること。
間に別リクエストが割り込める。
解決方法はいくつかある。
まず基本。
with transaction():
user = db.get_user_for_update(user_id)
if user.points >= 100:
user.points -= 100
user.save()
send_reward(user)重要なのは:
FOR UPDATE行ロック。
これで他トランザクションが同じ行を触れなくなる。
つまり:
Aが処理中
Bは待たされる
状態になる。
さらに実務的には、更新条件までDBに寄せることが多い。
UPDATE users
SET points = points - 100
WHERE id = 1
AND points >= 100;更新件数が:
1 -> 成功
0 -> ポイント不足になる。
これはかなり強い。
理由は:
check と update が分離しない
DBがatomicに処理する
race condition が起きにくい
から。
実務ではかなりよく見る。
Python の threading.Lock を使えば防げると思う人も多い。
確かに単一プロセスでは動く。
でも実務では:
Server A
Server B
Server Cみたいに複数サーバーがある。
すると:
AのLock
BのLockは別物。
つまり防げない。
だから本番では:
DB lock
Redis lock
transaction
が重要になる。
面接で race condition を聞かれる時、単に「Lock知ってる?」を見ているわけではない。
本当に見られているのは:
本番で壊れる状況を想像できるか
だったりする。
例えば:
二重決済
在庫超過
duplicate job
二重メール送信
ポイント重複
retry地獄
こういう「現実の事故」を想像できる人は強い。
逆に、
「ローカルで動きました」
だけだと危険。
多くのエンジニアが、最初は race condition を軽視する。
でも本番障害を一度経験すると、かなり意識が変わる。
「この処理、同時に来たら壊れないか?」
を自然に考えるようになる。
そして実際、バックエンド設計のかなり多くは、
「競合をどう防ぐか」
との戦いだったりする。
コードレビューでも面接でも、race condition を見抜けるかは、単なる知識より「本番感覚」に近いのかもしれない。
コメントはまだありません
読み込み中...