二重予約が起きるDB設計、起きないDB設計の違い

二重予約が起きるDB設計、起きないDB設計の違い

記事
IT・テクノロジー
予約や在庫を扱うシステムで、いちばん怖い不具合があります。同じ枠に2件入ってしまうことです。

画面では1件しか受け付けていないように見えるのに、データベースを開くと2件入っている。返金と謝罪が発生して、原因を追っても再現しない。この種の不具合は、コードの書き方ではなく設計で決まっています

私は有料通話サービスを個人で開発していて、予約と決済を実装しました。その過程で踏んだところを書きます。

「空きを確認してから入れる」は失敗する


いちばん自然に見える書き方が、これです。

1. その枠が空いているか確認する
2. 空いていたら予約を入れる

これで動いてしまうので、テストでも気づきません。問題は、1と2のあいだです。

利用者が2人同時に申し込むと、両方が「空いている」を見てから、両方が予約を入れます。あいだが0.1秒でも起きます。アクセスが増えるほど確率が上がるので、サービスが成長してから初めて出るのがたちの悪いところです。

直し方は2つある


ひとつは、データベースに「同じ枠は1件まで」を宣言してしまう方法です。

枠を一意キーにしておくと、2件目を入れようとした時点でデータベース側が拒否します。アプリ側で頑張らなくてよくなるのが利点で、実装を書き換えても効き続けます。

もうひとつは、予約を入れる処理を1件ずつ順番に通す方法です。

私のサービスでは、予約の確定処理をこの形にしています。同時に来ても、片方が終わるまでもう片方を待たせます。空き枠の計算が複雑で、一意キーだけでは表現しきれない場合はこちらになります。

どちらを選ぶかは、枠の決まり方によります。時間帯が固定なら前者で足ります。「前後30分は詰められない」のような条件が入ると後者が要ります。

決済がからむと、もう1段むずかしくなる


予約と決済の両方があるとき、次が起こりえます。

- 予約は入ったが決済が失敗した
- 決済は通ったが予約が入っていない
- 決済サービスからの通知が2回届いて、2回請求してしまった

3つ目は見落とされがちです。決済サービスは、通知が届いたか確信が持てないときもう一度送ります。受け取る側が同じ通知を2回処理しない作りになっていないと、二重計上します。

対策は、通知に付いている一意のIDを保存して、すでに処理したIDなら何もしないことです。私はこの形にしています。

残高をカラムで持たない


もうひとつ、後から効いてくる設計があります。残高を1つの数値として持たないことです。

「残高」というカラムを作って足し引きすると、計算が1回ずれただけで、いつどこでずれたのかを追えなくなります。

代わりに、入金・利用・返金の明細を1行ずつ積み上げて、残高は合計として出します。行数は増えますが、あとから1件ずつ検算できます。ずれたときに原因の行を特定できるかどうかは、運用に入ってから大きな差になります。

作る前に決めておくとよいこと


外注する場合も自分で作る場合も、着手前にこの3つを決めておくと後戻りが減ります。

1. 同じ枠に2件入らないことを、どこで保証するか(データベースか、アプリか)
2. 決済の通知が2回来たときに、何が起きるか
3. 金額がずれたとき、どうやって原因の行にたどり着くか

いずれも「動くかどうか」ではなく「間違ったときに気づけるかどうか」の話です。予約と決済のシステムでいちばん怖いのは、止まることではなく、間違ったまま動き続けることだと考えています。


DB設計のレビューをサービスとして出しています。すでに動いているシステムでも、上の3点がどうなっているかを見て、直し方を具体的にお伝えできます。設計段階のご相談も歓迎です。

▼ サービスページはこちら
WebアプリのDB設計をレビューします

※この記事は生成AIを併用して書き、内容は自分で確認しています。
サービス数40万件のスキルマーケット、あなたにぴったりのサービスを探す