90. RetryとCatchはエラーの分類器である — 指数バックオフとジッターが宣言で書ける
エラー処理を「エラー名で分類 → 分類ごとに方針を宣言」という仕組みとして、Step Functionsのステートマシン定義の中で図で追っていきます。
① エラーは「大文字小文字を区別する名前」で識別される
- 公式の言い回し: "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: "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 — 指数バックオフとジッターが「宣言」で手に入る
- 対象状態: "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度出会った輻輳制御の原理が、ここでは実装するものではなく「宣言するもの」になる。
④ 複数リトライアは配列順に評価される — エラー名ごとに別方針
- 公式: "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になって走る
- 公式: "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呼び出しはサービス例外まで想定して宣言する
- 公式の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を使うなら単独で最後に置く。
一過性の失敗(再試行で直る)と恒久的な失敗(分類して別処理へ)を、エラー名で見分けて宣言する。「再試行して良いエラーか」の判断はメッセージング編で見た冪等性の議論に直結する——同じ呼び出しを繰り返しても安全なタスクだからこそ、指数バックオフで粘れる。