AWS Step FunctionsのMapステート内でエラーが起きても全体を停止しない方法
こんにちは。サービス部の武田です。
AWS Step Functionsはステートマシンとしてワークフローを定義し、効率よく実行できるサーバーレスオーケストレーションサービスです。Step FunctionsではMap
という、入力配列を並行実行するためのステートが提供されています。
さてこのMap
ステートですが、普通に使用すると、あるブランチ(並行実行されている処理のうちのひとつ)がエラーなどで中断してしまうと、他のブランチも止まるという特徴があります。
例として次のようなステートマシンを定義してみます。
ASL(Amazon States Language)は次のようになっています。Pass
ステートは次のMap
ステートに渡すための配列データを作っています。Map
ステート内の処理は、値が3
であれば3秒待機してFail
ステートで失敗する。それ以外であれば5秒待機してSuccess
ステートに遷移し成功する。という単純なものです。
{ "Comment": "A description of my state machine", "StartAt": "Pass", "States": { "Pass": { "Type": "Pass", "Next": "Map", "Result": { "values": [ 1, 2, 3, 4, 5 ] } }, "Map": { "Type": "Map", "End": true, "Iterator": { "StartAt": "Choice", "States": { "Choice": { "Type": "Choice", "Choices": [ { "Variable": "$", "NumericEquals": 3, "Next": "3 Sec Wait" } ], "Default": "5 Sec Wait" }, "3 Sec Wait": { "Type": "Wait", "Seconds": 3, "Next": "Fail" }, "Fail": { "Type": "Fail" }, "5 Sec Wait": { "Type": "Wait", "Seconds": 5, "Next": "Success" }, "Success": { "Type": "Succeed" } } }, "ItemsPath": "$.values" } } }
実行してみると #2(=値が3) のブランチはFail
ステートに到達して失敗となっています。
一方で他のブランチはどうかというと、たとえば #1(=値が2) はキャンセルとなり、途中で処理が終わっています。
通常はこのように停止してしまった方がよいのですが、場合によってはあるブランチに左右されず、正常実行できるものは実行し切れた方がよい場合もあるでしょう。
途中でステートマシンを停止しないようにする
それでは先ほどのステートマシンを少し改変し、あるブランチのエラーで止まらないようにしてみましょう。改変後のステートマシンは次のようになります。
グラフだけではどう変わったのか分かりにくいですね。ASLは次のようになります。
{ "Comment": "A description of my state machine", "StartAt": "Pass", "States": { "Pass": { "Type": "Pass", "Next": "Map", "Result": { "values": [ 1, 2, 3, 4, 5 ] } }, "Map": { "Type": "Map", "End": true, "Iterator": { "StartAt": "Parallel", "States": { "Parallel": { "Type": "Parallel", "Branches": [ { "StartAt": "Choice", "States": { "Choice": { "Type": "Choice", "Choices": [ { "Variable": "$", "NumericEquals": 3, "Next": "3 Sec Wait" } ], "Default": "5 Sec Wait" }, "3 Sec Wait": { "Type": "Wait", "Seconds": 3, "Next": "Fail" }, "Fail": { "Type": "Fail" }, "5 Sec Wait": { "Type": "Wait", "Seconds": 5, "Next": "Success" }, "Success": { "Type": "Succeed" } } } ], "Catch": [ { "ErrorEquals": [ "States.ALL" ], "Next": "Catch Success" } ], "End": true }, "Catch Success": { "Type": "Succeed" } } }, "ItemsPath": "$.values" } } }
ポイントは24行目のParallel
ステートおよび60行目のCatch
定義です。Parallel
ステートは通常異なる処理を並行実行するためのステートですが、ここでは単なるコンテナ(入れ物)として使っています。Catch
はこのParallel
ステートの中で何らかのエラーが起きた場合の例外処理を定義しています。ここではエラーが起きたら問答無用でSuccess
ステートになり処理を正常終了させています。
ではこのステートマシンを実行してみましょう。まずは先ほどステートマシンを停止する原因となっていた #2 です。Fail
ステートに遷移して一度は失敗していますが、それがキャッチされ正常終了されています。
続いてキャンセルされていた #1 です。こちらは問題なくSuccess
ステートに遷移し正常実行できています。
まとめ
Map
ステートのデフォルトの挙動では困る場面をエラーハンドリングを少し工夫することで回避できました。ただしエラーが起きた際の通知処理なども考慮すると、構成が複雑になることが懸念されます。その場合は無理にステートマシンの中で並列化するのではなく、そもそもステートマシンを複数回実行することなども検討するとよいでしょう。