Dev Study
AWSサービスの内部原理 コース

90. RetryとCatchはエラーの分類器である — 指数バックオフとジッターが宣言で書ける

エラー処理を「エラー名で分類 → 分類ごとに方針を宣言」という仕組みとして、Step Functionsのステートマシン定義の中で図で追っていきます。

① エラーは「大文字小文字を区別する名前」で識別される

状態が実行時に失敗PassとWait以外のすべての状態が失敗しうる
2つの名前空間に分かれる
エラー名大文字小文字を区別する文字列
エラー名に対する方針を書かなければ
States.で始まる組み込みエラーの予約名前空間
例: States.Timeout / States.TaskFailed / States.Permissions
States.で始まらないユーザー/サービス由来
例: HandledError / java.lang.Exception / Lambda.ServiceException
実行全体が失敗既定の挙動(entire executionの失敗)
  • 公式の言い回し: "Step Functions identifies errors using case-sensitive strings, known as error names."
  • ユーザー定義のエラー名は States. で始めてはいけない("the error names cannot begin with the States. prefix")。
  • 何も書かなければ "Step Functions defaults to failing the entire state machine execution."

エラーは大文字小文字を区別する文字列の名前になり、`States.`接頭辞は組み込みエラーの予約名前空間。この「名前」こそが、RetryとCatchが分類に使う唯一の材料である。

② 分類の道具箱 — ワイルドカードには「捕まえられない例外」がある

States.ALL既知のエラー名すべてに合致するワイルドカード
配列に単独で・かつ最後に置く決まり
合致範囲の例
States.TaskFailedStates.Timeout「以外」のあらゆる既知のエラー名に合致
TaskFailedが除外する唯一の名前
States.Permissions権限不足で実行できない
States.HeartbeatTimeoutハートビート途絶
HandledError などユーザー/サービス例外
✗ States.ALL でも捕まえられない ✗
States.TimeoutTimeoutSeconds超過 / ハートビート途絶
States.ALLには合致する
States.DataLimitExceededペイロード超過 = 終端エラー
States.Runtime再試行不能・必ず実行が失敗する
典型例: nullへのInputPath/OutputPath適用
  • States.ALL: "A wildcard that matches any known error name. ... must appear alone in a Catcher and cannot catch the States.DataLimitExceeded terminal error or Runtime error types." Retry/Catch配列ではErrorEqualsに単独で・かつ配列の最後のリトライア/キャッチャーに置く決まり。
  • States.TaskFailed: "acts as a wildcard that matches any known error name except for States.Timeout." 除外されるのはStates.Timeoutだけで、States.HeartbeatTimeoutやサービス例外は合致範囲に入る。
  • States.Timeout: TimeoutSeconds超過、またはHeartbeatSecondsより長くハートビートが途絶したときに報告される。ステートマシン実行全体がTimeoutSecondsを超えたときにも報告される。
  • States.HeartbeatTimeout: HeartbeatSecondsより長くハートビートを送れなかったTaskの失敗。CatchとRetryの中で使える。
  • States.Runtime: "isn't retriable, and will always cause the execution to fail. A retry or catch on States.ALL won't catch States.Runtime errors." 典型例はnullのJSONペイロードにInputPath/OutputPathを適用したとき。
  • States.DataLimitExceeded: 終端(terminal)エラー。States.ALLでは捕まらないが、ErrorEqualsに明示すればCatch/Retryできる。
  • States.Permissions: Taskが権限不足で実行できなかった場合。

`States.ALL`は「既知のエラーの全部」に見えて、終端エラーの`States.DataLimitExceeded`と、再試行不能で必ず実行を失敗させる`States.Runtime`は取りこぼす。これは例外階層で言えば「catchできない致命的エラー」に相当し、捕まえる粒度を選ぶ設計そのもの(プログラミング言語の例外階層と同じ)。

③ Retry — 指数バックオフとジッターが「宣言」で手に入る

リトライアRetry配列の1要素 = 1つの再試行方針
Retryを書けるのはTask・Parallel・Mapの3状態
ErrorEquals合致するエラー名の集合(必須)
IntervalSeconds初回再試行までの待ち(既定1秒)
MaxAttempts最大再試行回数(既定3・0=再試行しない)
例: Interval=2, BackoffRate=2, MaxAttempts=3
BackoffRate毎回この倍率で待ちが伸びる(既定2.0)
MaxDelaySeconds待ちの上限(伸びすぎを頭打ちに)
JitterStrategyFULL / NONE(既定NONE)
JitterStrategy=FULL を付けると
失敗
再試行12秒待って
再試行24秒待って
再試行38秒待って
倍々に伸びる = 指数バックオフ
各間隔をランダム化0〜その間隔の一様乱数で待つ(0〜2秒、0〜4秒、0〜8秒)
全員一斉の再試行 = thundering herd を避ける
  • 対象状態: "Task, Parallel, and Map states can have a field named Retry."
  • 既定値(公式): IntervalSecondsは1(最大99,999,999)、MaxAttemptsは3(最大99,999,999、0は"never retried")、BackoffRateは2.0。
  • MaxDelaySeconds: 0より大きく31622401未満。指定しなければ待ち時間に上限はかからない。
  • JitterStrategy: 値はFULLまたはNONE、"The default value is NONE." FULLで各間隔を「0〜その間隔」の一様乱数に散らす(2/4/8秒の例は公式の例そのまま)。
  • 小ネタ: "Retries are treated as state transitions."(再試行も状態遷移として課金対象になる——公式がこの一文からPricingへ誘導している)

`IntervalSeconds`×`BackoffRate`が指数バックオフを、`JitterStrategy:FULL`がジッターを宣言で与える。API Gateway編のスロットリング対処、メッセージング編・Lambda編の再試行で3度出会った輻輳制御の原理が、ここでは実装するものではなく「宣言するもの」になる。

④ 複数リトライアは配列順に評価される — エラー名ごとに別方針

リトライア1ErrorEquals:[ErrorA, ErrorB] Interval=1, Backoff=2.0, MaxAttempts=2
リトライア2ErrorEquals:[ErrorC] Interval=5
キャッチャーErrorEquals:[States.ALL] → 状態Zへ
1回目 ErrorAリトライア1に合致
1秒待って再試行(この方針の1回目)
2回目 ErrorBリトライア1に合致
2秒待って再試行(この方針の2回目 = MaxAttempts到達)
リトライア1は使い切り済み
3回目 ErrorCリトライア2に合致
5秒待って再試行
リトライア失敗 → Catchへ
4回目 ErrorBMaxAttempts=2 を消費済みで再試行できない
状態Zへ遷移States.ALLのキャッチャーが受け取る
  • 公式: "Step Functions scans through the retriers in the order listed in the array." 合致した最初のリトライアの方針が適用される。
  • 重要な挙動: "A retrier's parameters apply across all visits to the retrier in the context of a single-state execution." つまりMaxAttemptsの消費は「エラー名ごと」ではなく「そのリトライアへの訪問回数」で数える。ErrorAとErrorBはどちらもリトライア1を訪れるので、2回で使い切る。
  • States.ALLは「単独で・かつRetry/Catch配列の最後」に置くのが決まり。States.TaskFailedもStates.Timeout以外に合致する準ワイルドカードとして最後尾に使える。

リトライアの配列は「エラー名 → 方針」の分岐表であり、`MaxAttempts`は各リトライアへの訪問回数で消費される。尽きた瞬間にCatchへ引き継がれる——この受け渡しが次のセクション。

⑤ Catch — リトライが尽きたら、例外のcatch節がJSONになって走る

状態が失敗
リトライア尽きた / Retryが無い
Retryを先に評価合致し解決すれば通常フロー(Next)へ
合致
Catchキャッチャーを配列順に走査し、ErrorEqualsに合致した最初のキャッチャーで確定
各キャッチャー: ErrorEquals(必須) / Next(必須) / ResultPath(任意)
ResultPathの書き方で渡し方が変わる
エラー出力を作る通常 Cause フィールドを含む = 人間可読のエラー説明
Nextで指定した状態へ
ResultPath指定"$.error-info" なら入力にエラー出力を「添えて」渡す
後段の状態が「何が起きたか」を知って動ける
ResultPath省略JSONPath既定"$" = 入力全体をエラー出力で上書き
遷移先の状態例: fallback / RecoveryState
  • 公式: "When a state has both Retry and Catch fields, Step Functions uses any appropriate retriers first. If the retry policy fails to resolve the error, Step Functions applies the matching catcher transition."
  • キャッチャーのフィールド: ErrorEquals(必須)、Next(必須、状態名に厳密一致)、ResultPath(任意、JSONPath)。
  • エラー出力: "the object usually contains the field Cause. This field's value is a human-readable description of the error. This object is known as the error output."(Causeは「通常」含まれる)
  • ResultPathを省略するとJSONPathでは既定$となり、入力全体をエラー出力で上書きする。ResultPath:"$.error-info"のように書けば、元の入力にエラー情報を1フィールドとして添えられる(公式のjava.lang.Exception→RecoveryStateの例そのまま)。
  • Cause文字列をJSONにしたい場合はStates.StringToJson($.Cause)で変換できる(サービス統合向けの実務ネタ、公式に例あり)。

Catchは「例外のcatch節がJSONになった姿」で、`ResultPath`を使えばCauseを入力に添えて後段へ渡せる。後段の状態が失敗理由を知って補償処理を選べる——この「失敗を知って動く」設計が、次のSaga回の伏線になる。

⑥ 実務の型 — Lambda呼び出しはサービス例外まで想定して宣言する

Task: LambdaをinvokeLambdaが一過性の500系エラーを返しうる
ErrorEqualsに並べる4つのエラー名
推奨RetryIntervalSeconds: 2 / MaxAttempts: 6 / BackoffRate: 2
指数バックオフで6回まで粘る。解決しなければ
Lambda.ClientExecutionTimeoutException
Lambda.ServiceException
Lambda.AWSLambdaException
Lambda.SdkClientException
Catchへ補償/通知の状態へ遷移
取りこぼしは Lambda.Unknown / Sandbox.Timedout / States.TaskFailed を並べて塞ぐ(States.ALLを使うなら単独で最後)
  • 公式のRetryブロック(exact): ErrorEqualsにLambda.ClientExecutionTimeoutException, Lambda.ServiceException, Lambda.AWSLambdaException, Lambda.SdkClientException、IntervalSeconds: 2, MaxAttempts: 6, BackoffRate: 2。
  • Lambdaのエラーは Lambda.{ErrorName} の形で報告される。
  • 補足(公式Note): 古いランタイムの未処理エラーはLambda.Unknown、新しいランタイムのタイムアウトはSandbox.Timedout、呼び出し過多はLambda.TooManyRequestsException。Lambda.Unknown / Sandbox.Timedout / States.TaskFailedを並べて備える。States.ALLを使うなら単独で最後に置く。

一過性の失敗(再試行で直る)と恒久的な失敗(分類して別処理へ)を、エラー名で見分けて宣言する。「再試行して良いエラーか」の判断はメッセージング編で見た冪等性の議論に直結する——同じ呼び出しを繰り返しても安全なタスクだからこそ、指数バックオフで粘れる。

公式ドキュメントで詳しく ↗