TT Lab
はじめる
学ぶ 学習パス コース

冪等性 — 二度押しても決済は一度だけ

冪等キーを消すと保証も終わる

TT Labで続きを見る

目標

完了の記録と進行中の記録を区別し、保管期間とバッチでの整理を実装します。

なぜ重要なのか

テーブルが大きくなったので、古い冪等キーをすべて削除しました。まだ決済中のリクエストの記録も消え、リトライが新しいリクエストとして入ってきました。完了した応答の保管期間と、進行中の作業の所有権は、同じ期限切れのポリシーではありません。整理は、単純なDELETEではなく、保証の範囲を変える状態遷移です。

ステップ

  1. /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
  1. /root/work/idem-retention-lab/service.pyで、reserve(path,key,digest,expires)は、ないキーをpending、response=NULLで入れてTrueです。既存のキーは、期限切れに関係なく、変更せずにFalseです。

  2. /root/work/idem-retention-lab/service.pyで、finish(path,key,digest,response)は、一致するkey・fingerprintで、pendingの行だけに、responseをJSONとして保存してdoneに変え、Trueです。それ以外はFalseです。

  3. /root/work/idem-retention-lab/service.pyで、fetch(path,key,now)は、doneでexpires>nowの行のresponseをJSONとしてパースして返します。それ以外はNoneです。

  4. /root/work/idem-retention-lab/service.pyで、expired(path,now,limit=10)は、boolを除くintの1–100であるlimitを検査します。doneでexpires<=nowのidを、id昇順で最大limit個返します。

  5. /root/work/idem-retention-lab/service.pyで、purge(path,now,limit=10)は、expiredと同じlimitの検証・条件・ソートで候補を選んだあと、1つのトランザクションで削除して、削除したidのリストを返します。pendingは消しません。

  6. /root/work/idem-retention-lab/service.pyで、counts(path)は、{pending:個数, done:個数}です。該当する状態がなくても、キーと0が必要です。

  7. /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を前提とします。

参考

完了の有無と期限切れを分ける

/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で確認してください。