冪等なリクエストにも再試行の予算が必要
目標
ステータスコード・リクエストの安全性・最大回数・全体の時間制限を、一緒に守る実行器を作ります。
なぜ重要なのか
正常なリクエストが1回成功しても、境界値と障害からの復旧は保証されません。このラボは、各関数の契約を小さく実装またはテストしたあと、実際の実行につなげます。コードが存在するかどうかやレポートの文言だけを見るのではなく、結果・例外・保存状態を検査します。前のステップのコードを維持しながら、次のステップに進んでください。
ステップ
/root/work/idem-retry-budget-lab/service.pyで、retryable(status)は429、502、503、504にだけTrueを返してください。残りの状態はFalseです。この一覧はラボのポリシーであり、すべてのサービスの正解ではありません。最初の準備は、次のコマンドで行います。
mkdir -p /root/work/idem-retry-budget-lab
cp /opt/fixtures/practice_depth/idem-retry-budget-lab/* /root/work/idem-retry-budget-lab/
cd /root/work/idem-retry-budget-lab
/root/work/idem-retry-budget-lab/service.pyで、replay_safe(method, key=None)は、大文字小文字を区別しないGET・HEAD・PUT・DELETE、または空でないkeyがあるPOSTだけにTrueを返してください。PATCHはこの契約では許可しません。/root/work/idem-retry-budget-lab/service.pyで、remaining(deadline, now)は、deadline-nowと0のうち大きいほうを返してください。引数は単調時計の秒単位の実数です。/root/work/idem-retry-budget-lab/service.pyで、backoff(attempt, base=0.5, cap=4.0)は、min(cap, base*2**attempt)を返してください。attemptは0–30、base・capは負であってはならず、違反するとValueErrorです。/root/work/idem-retry-budget-lab/service.pyで、jitter(delay, fraction)は、delay*fractionを返してください。delay>=0、0<=fraction<=1である必要があり、違反するとValueErrorです。/root/work/idem-retry-budget-lab/service.pyで、wait_delay(local, retry_after)は、localとRetry-Afterの整数秒のうち大きいほうを返してください。None・負の数・非整数・日付などサポートしない値は、0秒と解釈します。/root/work/idem-retry-budget-lab/service.pyで、can_attempt(attempts, maximum, wait, budget)は、attempts < maximum、wait>=0、budget>waitをすべて満たすときだけTrueです。attemptsは、すでに行った総呼び出し数です。/root/work/idem-retry-budget-lab/service.pyで、execute(send, clock, sleep, method='GET', key=None, timeout=5.0, maximum=3, random_fraction=lambda:0.5)を実装してください。send(残り秒数)は、{status: int, retry_after: 任意の値}を返します。最初にdeadlineを固定し、上のポリシーに従ってリトライし、それ以上できなければ最後の応答を返します。timeout<=0またはmaximum<1はValueError、呼び出しの直前に予算を使い切っていればTimeoutErrorです。
参考
- パッケージはイメージにインストールされています。インターネット接続やpip installは必要ありません。
- 準備のコピーは、最初に1回だけ行ってください。もう一度コピーすると、作業ファイルが初期化されます。
- 採点は、作成したservice.pyを読み込んで、別のidと一時ストレージで実行します。結果を定数として埋め込まないでください。
- 各ステップの採点は45秒以内で実行されます。無限ループや実際の待機を作らないでください。
リトライする応答を絞る
/root/work/idem-retry-budget-lab/service.pyで、retryable(status)は429、502、503、504にだけTrueを返してください。残りの状態はFalseです。この一覧はラボのポリシーであり、すべてのサービスの正解ではありません。最初の準備は、次のコマンドで行います。
mkdir -p /root/work/idem-retry-budget-lab
cp /opt/fixtures/practice_depth/idem-retry-budget-lab/* /root/work/idem-retry-budget-lab/
cd /root/work/idem-retry-budget-lab
400・401・409のようなエラーを繰り返しリクエストしても、入力・権限・競合は自然には解決されません。
採点はbash /opt/lab/checks/idem-retry-budget-lab/01-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
リクエストを再送してよいか判断する
/root/work/idem-retry-budget-lab/service.pyで、replay_safe(method, key=None)は、大文字小文字を区別しないGET・HEAD・PUT・DELETE、または空でないkeyがあるPOSTだけにTrueを返してください。PATCHはこの契約では許可しません。
サーバーがキーの重複排除を提供するという前提を区別してください。ヘッダーに文字列を入れただけで安全になるわけではありません。
採点はbash /opt/lab/checks/idem-retry-budget-lab/02-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
1つの締め切り時刻を共有する
/root/work/idem-retry-budget-lab/service.pyで、remaining(deadline, now)は、deadline-nowと0のうち大きいほうを返してください。引数は単調時計の秒単位の実数です。
試行のたびに新しいtimeoutを発行すると、全体の制限が延びてしまいます。
採点はbash /opt/lab/checks/idem-retry-budget-lab/03-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
バックオフに上限を置く
/root/work/idem-retry-budget-lab/service.pyで、backoff(attempt, base=0.5, cap=4.0)は、min(cap, base*2**attempt)を返してください。attemptは0–30、base・capは負であってはならず、違反するとValueErrorです。
attempt=0は、最初の失敗のあとの待機です。指数の計算が際限なく大きくならないように、範囲を検査します。
採点はbash /opt/lab/checks/idem-retry-budget-lab/04-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
ジッターを決定的にテストする
/root/work/idem-retry-budget-lab/service.pyで、jitter(delay, fraction)は、delay*fractionを返してください。delay>=0、0<=fraction<=1である必要があり、違反するとValueErrorです。
乱数の生成そのものを関数の中に隠さず、fractionを注入すれば、両端と中間を再現できます。
採点はbash /opt/lab/checks/idem-retry-budget-lab/05-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
サーバーの待機より早くリトライしない
/root/work/idem-retry-budget-lab/service.pyで、wait_delay(local, retry_after)は、localとRetry-Afterの整数秒のうち大きいほうを返してください。None・負の数・非整数・日付などサポートしない値は、0秒と解釈します。
サーバーが7秒を要求すれば、ローカルのバックオフが0.5秒でも7秒です。残りの予算が短ければ、次のステップで諦めます。
採点はbash /opt/lab/checks/idem-retry-budget-lab/06-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
試行回数と待機の予算を一緒に見る
/root/work/idem-retry-budget-lab/service.pyで、can_attempt(attempts, maximum, wait, budget)は、attempts < maximum、wait>=0、budget>waitをすべて満たすときだけTrueです。attemptsは、すでに行った総呼び出し数です。
待機のあとの残り時間がちょうど0なら、新しいリクエストを開始できません。
採点はbash /opt/lab/checks/idem-retry-budget-lab/07-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。
偽の時計で実行器全体を検証する
/root/work/idem-retry-budget-lab/service.pyで、execute(send, clock, sleep, method='GET', key=None, timeout=5.0, maximum=3, random_fraction=lambda:0.5)を実装してください。send(残り秒数)は、{status: int, retry_after: 任意の値}を返します。最初にdeadlineを固定し、上のポリシーに従ってリトライし、それ以上できなければ最後の応答を返します。timeout<=0またはmaximum<1はValueError、呼び出しの直前に予算を使い切っていればTimeoutErrorです。
応答のコストも時計に含めます。最初の失敗はbackoff(0)です。sleepのあとにも再び時間を確認し、sendに渡すtimeoutを縮めます。
採点はbash /opt/lab/checks/idem-retry-budget-lab/08-contract.shで直接再現できます。ファイルを保存してから、もう一度実行してください。