指数バックオフ・ジッタ・DLQの実装
目標
指数バックオフとジッターを備えたリトライを実装し、リトライ禁止のエラーを区別し、DLQへの投入と再処理のスクリプト、リトライポリシー表まで作れるようになります。
なぜ重要なのか
リトライはタダではありません。ゲートウェイ4回 × サービスA 4回 × サービスB 4回なら、ユーザーの1クリックが、最終システムでは64回のリクエストになります。回復中だった相手は、その爆撃で再び倒れます。そして、ジッターのない指数バックオフは、1万個のクライアントがまったく同じ瞬間にリトライするようにしてしまいます(thundering herd)。普段は問題がないので、本当に大きな障害が起きたときに初めて発見されます。リトライを「入れた/入れなかった」ではなく、設計として扱うのが、このラボです。
ステップ
- 不安定なAPIを起動してください。
python3 /opt/lab/fixtures/eai/rest/flaky_api.py 9300(バックグラウンド)/flaky?key=<키>(プレースホルダーはキーです): 同じキーで3回目の呼び出しまで503、4回目から200/bad: 常に400/dead: 常に503
/flaky?key=t1を1回呼び出した結果を、/root/r/first.txtに保存してください。http_code=503という行が必要です。/root/r/retry.shを作成してください。2つの引数(URL 최대시도수。プレースホルダーは、URLと最大試行回数です)を受け取り、固定間隔でリトライし、最後の行にresult=<ok|fail> attempts=<n>を出力します。成功したら終了コード0です。/root/r/backoff.shを作成してください。2つの引数(URL 최대시도수。プレースホルダーは、URLと最大試行回数です)を受け取り、指数バックオフ(1、2、4、8秒、上限30秒)でリトライします。- 各試行の前に
attempt=<n> sleep=<초>(プレースホルダーは秒数です)を1行出力します。 - 最後の行には
result=<ok|fail> attempts=<n>を出力します。 - 環境変数
DRY=1なら、実際には待機せず、計画だけを出力します。
- 各試行の前に
backoff.shにジッターを追加してください。sleepの値は、計算値の50%以上100%以下のランダムな値である必要があり、DRY=1で2回実行すると、値がお互いに異なる必要があります。backoff.shが、リトライ禁止のエラーを区別するようにしてください。HTTP 400/401/403/404/409を受け取ったら、ただちに中止して、attempts=1で終わる必要があります。(/badで確認)/root/r/senddlq.shを作成してください。2つの引数(URL 메시지ID。プレースホルダーは、URLとメッセージIDです)を受け取り、リトライして最大試行回数を超えたら、/root/r/dlq/<메시지ID>.json(プレースホルダーはメッセージIDです)を作成します。 JSONにmsg_id、url、reason、attempts、first_failed_atの5つのキーが必要です。(urlは、ステップ7の再処理で、再度呼び出す対象です。)/deadとメッセージIDM-001で実行して、ファイルを作成してください。 (同じIDで再度実行すると、同じファイルを上書きします。)/root/r/dlq-replay.shを作成してください。引数を1つ(DLQのディレクトリ)受け取り、その中の各.jsonについて再処理を試み、失敗したら、該当ファイルのattemptsを1増やして再度保存します。 最後の行にreplayed=<시도수> succeeded=<성공수>(プレースホルダーは、順に試行数と成功数です)を出力します。ディレクトリがなければ、0以外の終了コードで終了します。/root/r/policy.csvを作成してください。1行目はerror,retry,max_attempts,backoff,finalです。 次の6種類がすべて必要です。connection-refused、timeout、http-500、http-400、http-401、http-429。retryはY/N/조건부、finalはDLQ/중단/통보のいずれかです(韓国語の値は、順に条件付き、中止、通知を意味します)。
参考
- ステータスコードだけを取得:
curl -s -o /dev/null -w '%{http_code}' <URL> - bashの乱数:
$RANDOM(0–32767)。範囲の変換に注意してください。 - JSONの作成:
jq -n --arg a "$A" '{msg_id:$a}'、またはprintfで直接 - よくあるミス1: リトライのループで、成功しても回り続けることです。成功したら、すぐにbreakしてください。
- よくあるミス2: ジッターを計算値より大きくすることです。上限を超えると、バックオフの意味が薄れます。
- よくあるミス3: DLQのファイル名をタイムスタンプで作り、再実行のたびにファイルが増えることです。 メッセージIDを基準にして初めて、再処理を管理できます。
失敗の再現
/flaky?key=t1を1回呼び出した結果を、/root/r/first.txtに保存してください。
http_code=503という行が必要です。
リトライを作る前に、失敗がどのような姿なのかを先に見る必要があります。HTTPステータスコードとレスポンス本文を一緒に確認してください。
固定間隔のリトライ
/root/r/retry.shを作成してください。2つの引数(URL 최대시도수。プレースホルダーは、URLと最大試行回数です)を受け取り、固定間隔でリトライし、最後の行にresult=<ok|fail> attempts=<n>を出力します。
成功したら終了コード0です。
リトライ回数を引数で受け取ると、再利用できます。成功したらすぐに抜け出し、何回目で成功したかを出力してください。
指数バックオフ
/root/r/backoff.shを作成してください。2つの引数(URL 최대시도수。プレースホルダーは、URLと最大試行回数です)を受け取り、指数バックオフ(1、2、4、8秒、上限30秒)でリトライします。
- 各試行の前に
attempt=<n> sleep=<초>(プレースホルダーは秒数です)を1行出力します。 - 最後の行には
result=<ok|fail> attempts=<n>を出力します。 - 環境変数
DRY=1なら、実際には待機せず、計画だけを出力します。
実際に眠ると、採点が遅くなります。計画された待機時間を先に出力し、ドライランモードでは眠らないように作ってください。
ジッターの追加
backoff.shにジッターを追加してください。sleepの値は、計算値の50%以上100%以下のランダムな値である必要があり、DRY=1で2回実行すると、値がお互いに異なる必要があります。
同じルールに従うクライアントが多いと、同時にリトライします。計算値にランダム性を混ぜますが、範囲を外れてはいけません。
リトライ禁止エラーの区別
backoff.shが、リトライ禁止のエラーを区別するようにしてください。
HTTP 400/401/403/404/409を受け取ったら、ただちに中止して、attempts=1で終わる必要があります。(/badで確認)
形式エラーは、100回送っても100回失敗します。ステータスコードでリトライの可否を判断するルールを、スクリプトに入れてください。
DLQへの投入
/root/r/senddlq.shを作成してください。2つの引数(URL 메시지ID。プレースホルダーは、URLとメッセージIDです)を受け取り、リトライして最大試行回数を超えたら、/root/r/dlq/<메시지ID>.json(プレースホルダーはメッセージIDです)を作成します。
JSONにmsg_id、url、reason、attempts、first_failed_atの5つのキーが必要です。(urlは、ステップ7の再処理で、再度呼び出す対象です。)
/deadとメッセージIDM-001で実行して、ファイルを作成してください。
(同じIDで再度実行すると、同じファイルを上書きします。)
DLQに原本だけを入れると、あとで人が判断できません。理由・試行回数・最初の失敗時刻が一緒にある必要があります。
DLQの再処理スクリプト
/root/r/dlq-replay.shを作成してください。引数を1つ(DLQのディレクトリ)受け取り、その中の各.jsonについて再処理を試み、失敗したら、該当ファイルのattemptsを1増やして再度保存します。
最後の行にreplayed=<시도수> succeeded=<성공수>(プレースホルダーは、順に試行数と成功数です)を出力します。ディレクトリがなければ、0以外の終了コードで終了します。
再処理は、対象のディレクトリを引数で受け取ると安全です。再処理の履歴を残さないと、何回試したかわからなくなります。
リトライポリシー表
/root/r/policy.csvを作成してください。1行目はerror,retry,max_attempts,backoff,finalです。
次の6種類がすべて必要です。connection-refused、timeout、http-500、http-400、http-401、http-429。
retryはY/N/조건부、finalはDLQ/중단/통보のいずれかです(韓国語の値は、順に条件付き、中止、通知を意味します)。
エラーの種類ごとに、リトライの可否と最大回数が違います。429には特に注意が必要で、相手が「ゆっくり来てください」と言っているからです。