最後の在庫を二人が予約した
目標
在庫の減算・予約id・キャンセルをアトミックにまとめ、過剰販売を防ぎます。
なぜ重要なのか
在庫が1つ残っているのに、2つのリクエストが同時に参照して、どちらも予約に成功しました。その後、同じキャンセルメッセージが2回届くと、在庫が元より増えました。予約の重複排除とキャンセルの重複排除は、それぞれ別の状態遷移であり、残りの数量の検査も、減算と同じトランザクションの中にある必要があります。
ステップ
/root/work/idem-stock-reservation-lab/service.pyで、quantity(value)は、boolを除く正のintだけを返し、それ以外はValueErrorです。
最初に1回だけ準備してください。既存のファイルは上書きしません。
mkdir -p /root/work/idem-stock-reservation-lab
test -e /root/work/idem-stock-reservation-lab/service.py || cp /opt/fixtures/ten_labs/idem-stock-reservation-lab/service.py /root/work/idem-stock-reservation-lab/service.py
cd /root/work/idem-stock-reservation-lab
-
/root/work/idem-stock-reservation-lab/service.pyで、init_db(path)は、stock(sku TEXT PRIMARY KEY,available INTEGER NOT NULL)とreservations(id TEXT PRIMARY KEY,sku TEXT NOT NULL,qty INTEGER NOT NULL,cancelled INTEGER NOT NULL DEFAULT 0)を、冪等に作成します。 -
/root/work/idem-stock-reservation-lab/service.pyで、add_stock(path,sku,amount)は、boolを除く0以上のintを検査し、新しい商品だけをINSERTします。重複した商品は、IntegrityErrorです。 -
/root/work/idem-stock-reservation-lab/service.pyで、available(path,sku)は、保存された数量を返し、商品がなければKeyErrorです。 -
/root/work/idem-stock-reservation-lab/service.pyで、reserve(path,rid,sku,qty,fault=lambda:None)は、数量を検証します。既存の予約idは、同じ商品/数量ならFalse、違えばValueErrorです。新しい予約は、商品の存在と在庫を確認して、減算→fault→予約の挿入のあとにTrueです。不足・ない商品はValueErrorです。 -
/root/work/idem-stock-reservation-lab/service.pyで、cancel(path,rid)は、ない予約・すでにキャンセルされた予約ならFalseです。activeの予約なら、同じトランザクションで、cancelled=1と在庫の返却を行ってTrueです。 -
/root/work/idem-stock-reservation-lab/service.pyで、reservation(path,rid)は、(sku,qty,cancelled)のタプル、またはNoneを返します。 -
/root/work/idem-stock-reservation-lab/service.pyで、compete(path,sku)は、reserveを、互いに異なるid first/second、数量1で、2つのスレッドで実行します。ValueErrorだけをFalseに変えた、入力順の結果のリストを返します。
参考
- インターネットやパッケージのインストールなしで、既存のlab-dev環境で行います。
- 各ステップは、45秒の採点予算の中で実行されます。実際のsleepやネットワーク呼び出しを追加しないでください。
- 採点は、提出されたモジュールを新しく読み込み、独立した入力と一時DBで検査します。期待値を定数として返す代わりに、契約を実装してください。
- FastAPI公式ドキュメント・pytest公式ドキュメント・Python sqlite3
- 限界: 決済の承認と在庫の予約を、1つの分散トランザクションにまとめるラボではありません。予約の期限切れと、決済の失敗の補償は、別の流れです。取得した時点の在庫の数字を信じず、書き込むトランザクションの中で確認する原理を、小さなSQLite DBで学びます。
数量を整数の契約に固定する
/root/work/idem-stock-reservation-lab/service.pyで、quantity(value)は、boolを除く正のintだけを返し、それ以外はValueErrorです。
最初に1回だけ準備してください。既存のファイルは上書きしません。
mkdir -p /root/work/idem-stock-reservation-lab
test -e /root/work/idem-stock-reservation-lab/service.py || cp /opt/fixtures/ten_labs/idem-stock-reservation-lab/service.py /root/work/idem-stock-reservation-lab/service.py
cd /root/work/idem-stock-reservation-lab
負の予約が、在庫を増やせないようにします。
保存したあと、bash /opt/lab/checks/idem-stock-reservation-lab/01-contract.shで確認してください。
在庫と予約を分けて保存する
/root/work/idem-stock-reservation-lab/service.pyで、init_db(path)は、stock(sku TEXT PRIMARY KEY,available INTEGER NOT NULL)とreservations(id TEXT PRIMARY KEY,sku TEXT NOT NULL,qty INTEGER NOT NULL,cancelled INTEGER NOT NULL DEFAULT 0)を、冪等に作成します。
キャンセルされた予約も残しておいて初めて、同じキャンセルと再予約を区別できます。
保存したあと、bash /opt/lab/checks/idem-stock-reservation-lab/02-contract.shで確認してください。
商品の初期化を、在庫の変更と区別する
/root/work/idem-stock-reservation-lab/service.pyで、add_stock(path,sku,amount)は、boolを除く0以上のintを検査し、新しい商品だけをINSERTします。重複した商品は、IntegrityErrorです。
同じ初期化コマンドが、使用中の在庫を上書きしないようにします。
保存したあと、bash /opt/lab/checks/idem-stock-reservation-lab/03-contract.shで確認してください。
残りの数量を取得する
/root/work/idem-stock-reservation-lab/service.pyで、available(path,sku)は、保存された数量を返し、商品がなければKeyErrorです。
ない商品を売り切れと区別して、間違った商品idを隠さないようにします。
保存したあと、bash /opt/lab/checks/idem-stock-reservation-lab/04-contract.shで確認してください。
予約と在庫の減算をまとめる
/root/work/idem-stock-reservation-lab/service.pyで、reserve(path,rid,sku,qty,fault=lambda:None)は、数量を検証します。既存の予約idは、同じ商品/数量ならFalse、違えばValueErrorです。新しい予約は、商品の存在と在庫を確認して、減算→fault→予約の挿入のあとにTrueです。不足・ない商品はValueErrorです。
検査と減算を同じトランザクションに置き、障害のときはすべてロールバックします。
保存したあと、bash /opt/lab/checks/idem-stock-reservation-lab/05-contract.shで確認してください。
キャンセルも1回だけ反映する
/root/work/idem-stock-reservation-lab/service.pyで、cancel(path,rid)は、ない予約・すでにキャンセルされた予約ならFalseです。activeの予約なら、同じトランザクションで、cancelled=1と在庫の返却を行ってTrueです。
キャンセルメッセージの再送で、在庫が増え続けてはいけません。
保存したあと、bash /opt/lab/checks/idem-stock-reservation-lab/06-contract.shで確認してください。
予約の状態を確認する
/root/work/idem-stock-reservation-lab/service.pyで、reservation(path,rid)は、(sku,qty,cancelled)のタプル、またはNoneを返します。
戻り値だけでなく、キャンセルの状態がDBに残ったかを読みます。
保存したあと、bash /opt/lab/checks/idem-stock-reservation-lab/07-contract.shで確認してください。
最後の在庫の競合を再現する
/root/work/idem-stock-reservation-lab/service.pyで、compete(path,sku)は、reserveを、互いに異なるid first/second、数量1で、2つのスレッドで実行します。ValueErrorだけをFalseに変えた、入力順の結果のリストを返します。
在庫1と在庫2の2つの状況を比べて初めて、無条件に1つだけを成功させる実装も、見分けられます。
保存したあと、bash /opt/lab/checks/idem-stock-reservation-lab/08-contract.shで確認してください。