冪等キーを消すと保証も終わる
目標
完了の記録と進行中の記録を区別し、保管期間とバッチでの整理を実装します。
なぜ重要なのか
テーブルが大きくなったので、古い冪等キーをすべて削除しました。まだ決済中のリクエストの記録も消え、リトライが新しいリクエストとして入ってきました。完了した応答の保管期間と、進行中の作業の所有権は、同じ期限切れのポリシーではありません。整理は、単純なDELETEではなく、保証の範囲を変える状態遷移です。
ステップ
/root/work/idem-retention-lab/service.pyで、init_db(path)は、keys(id TEXT PRIMARY KEY,fingerprint TEXT NOT NULL,status TEXT NOT NULL,response TEXT,expires REAL NOT NULL)を、冪等に作成します。
最初に1回だけ準備してください。既存のファイルは上書きしません。
mkdir -p /root/work/idem-retention-lab
test -e /root/work/idem-retention-lab/service.py || cp /opt/fixtures/ten_labs/idem-retention-lab/service.py /root/work/idem-retention-lab/service.py
cd /root/work/idem-retention-lab
-
/root/work/idem-retention-lab/service.pyで、reserve(path,key,digest,expires)は、ないキーをpending、response=NULLで入れてTrueです。既存のキーは、期限切れに関係なく、変更せずにFalseです。 -
/root/work/idem-retention-lab/service.pyで、finish(path,key,digest,response)は、一致するkey・fingerprintで、pendingの行だけに、responseをJSONとして保存してdoneに変え、Trueです。それ以外はFalseです。 -
/root/work/idem-retention-lab/service.pyで、fetch(path,key,now)は、doneでexpires>nowの行のresponseをJSONとしてパースして返します。それ以外はNoneです。 -
/root/work/idem-retention-lab/service.pyで、expired(path,now,limit=10)は、boolを除くintの1–100であるlimitを検査します。doneでexpires<=nowのidを、id昇順で最大limit個返します。 -
/root/work/idem-retention-lab/service.pyで、purge(path,now,limit=10)は、expiredと同じlimitの検証・条件・ソートで候補を選んだあと、1つのトランザクションで削除して、削除したidのリストを返します。pendingは消しません。 -
/root/work/idem-retention-lab/service.pyで、counts(path)は、{pending:個数, done:個数}です。該当する状態がなくても、キーと0が必要です。 -
/root/work/idem-retention-lab/service.pyで、retention_cycle(path,key)は、expires=10、digest='v1'で予約して、{receipt:1}で完了します。now=10でpurgeしたあと、同じキーをdigest='v2'、expires=20で新しく予約して、その結果のboolを返します。関数は、空のDBを前提とします。
参考
- インターネットやパッケージのインストールなしで、既存のlab-dev環境で行います。
- 各ステップは、45秒の採点予算の中で実行されます。実際のsleepやネットワーク呼び出しを追加しないでください。
- 採点は、提出されたモジュールを新しく読み込み、独立した入力と一時DBで検査します。期待値を定数として返す代わりに、契約を実装してください。
- FastAPI公式ドキュメント・pytest公式ドキュメント・Python sqlite3
- 限界: 無期限のexactly-onceの効果は保証しません。外部システムは古いリクエストを再送することがあるので、保管期間は、プロバイダーのリトライポリシーと合わせる必要があります。abandoned pendingを復旧するリース・補償のポリシーは、別のラボの責任であり、この整理関数では、推測して消しません。
完了の有無と期限切れを分ける
/root/work/idem-retention-lab/service.pyで、init_db(path)は、keys(id TEXT PRIMARY KEY,fingerprint TEXT NOT NULL,status TEXT NOT NULL,response TEXT,expires REAL NOT NULL)を、冪等に作成します。
最初に1回だけ準備してください。既存のファイルは上書きしません。
mkdir -p /root/work/idem-retention-lab
test -e /root/work/idem-retention-lab/service.py || cp /opt/fixtures/ten_labs/idem-retention-lab/service.py /root/work/idem-retention-lab/service.py
cd /root/work/idem-retention-lab
期限切れの時刻だけを見て、業務が終わったと判断しません。
保存したあと、bash /opt/lab/checks/idem-retention-lab/01-contract.shで確認してください。
進行中のキーを予約する
/root/work/idem-retention-lab/service.pyで、reserve(path,key,digest,expires)は、ないキーをpending、response=NULLで入れてTrueです。既存のキーは、期限切れに関係なく、変更せずにFalseです。
記録の削除とキーの再利用は、分離して、明示的に制御します。
保存したあと、bash /opt/lab/checks/idem-retention-lab/02-contract.shで確認してください。
現在のリクエストだけを完了する
/root/work/idem-retention-lab/service.pyで、finish(path,key,digest,response)は、一致するkey・fingerprintで、pendingの行だけに、responseをJSONとして保存してdoneに変え、Trueです。それ以外はFalseです。
別の本文の作業者が、完了の応答を上書きできないように、フィンガープリントまで比較します。
保存したあと、bash /opt/lab/checks/idem-retention-lab/03-contract.shで確認してください。
有効な完了の応答だけを再生する
/root/work/idem-retention-lab/service.pyで、fetch(path,key,now)は、doneでexpires>nowの行のresponseをJSONとしてパースして返します。それ以外はNoneです。
now==expiresの境界で、もう再生しないという契約を固定します。
保存したあと、bash /opt/lab/checks/idem-retention-lab/04-contract.shで確認してください。
整理の候補を制限する
/root/work/idem-retention-lab/service.pyで、expired(path,now,limit=10)は、boolを除くintの1–100であるlimitを検査します。doneでexpires<=nowのidを、id昇順で最大limit個返します。
テーブル全体を一度に消すと、ロックの時間が長くなり、進行中の行を巻き込みやすくなります。
保存したあと、bash /opt/lab/checks/idem-retention-lab/05-contract.shで確認してください。
選択と削除を同じトランザクションに置く
/root/work/idem-retention-lab/service.pyで、purge(path,now,limit=10)は、expiredと同じlimitの検証・条件・ソートで候補を選んだあと、1つのトランザクションで削除して、削除したidのリストを返します。pendingは消しません。
候補の取得と削除の間に、状態が変わる隙間を作りません。
保存したあと、bash /opt/lab/checks/idem-retention-lab/06-contract.shで確認してください。
状態ごとの個数を実際に数える
/root/work/idem-retention-lab/service.pyで、counts(path)は、{pending:個数, done:個数}です。該当する状態がなくても、キーと0が必要です。
整理のあとに、未完了の行が消えていないかを観測できる必要があります。
保存したあと、bash /opt/lab/checks/idem-retention-lab/07-contract.shで確認してください。
削除のあとに保証の範囲が終わることを確認する
/root/work/idem-retention-lab/service.pyで、retention_cycle(path,key)は、expires=10、digest='v1'で予約して、{receipt:1}で完了します。now=10でpurgeしたあと、同じキーをdigest='v2'、expires=20で新しく予約して、その結果のboolを返します。関数は、空のDBを前提とします。
整理のあと、キーが新しいリクエストになるという限界を隠さず、直接再現します。
保存したあと、bash /opt/lab/checks/idem-retention-lab/08-contract.shで確認してください。