この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは。サービス部の武田です。
AWS Step Functionsはステートマシンとしてワークフローを定義し、効率よく実行できるサーバーレスオーケストレーションサービスです。Step FunctionsではMap
という、入力配列を並行実行するためのステートが提供されています。
さてこのMap
ステートですが、普通に使用すると、あるブランチ(並行実行されている処理のうちのひとつ)がエラーなどで中断してしまうと、他のブランチも止まるという特徴があります。
例として次のようなステートマシンを定義してみます。
ASL(Amazon States Language)は次のようになっています。Pass
ステートは次のMap
ステートに渡すための配列データを作っています。Map
ステート内の処理は、値が3
であれば3秒待機してFail
ステートで失敗する。それ以外であれば5秒待機してSuccess
ステートに遷移し成功する。という単純なものです。
state-machine-01.json
{
"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は次のようになります。
state-machine-02.json
{
"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
ステートのデフォルトの挙動では困る場面をエラーハンドリングを少し工夫することで回避できました。ただしエラーが起きた際の通知処理なども考慮すると、構成が複雑になることが懸念されます。その場合は無理にステートマシンの中で並列化するのではなく、そもそもステートマシンを複数回実行することなども検討するとよいでしょう。