AWS Step Functions の Map ステートの挙動を調べてみた。
休日に自由に外出することもままならない昨今、おうち時間はたくさんあるけど何をすればいいのかわからない。
そんなときは、AWS のサービスの挙動を調べてみるのはいかがでしょうか。
新しい時代の新しい時間の使い方を提案するこのブログ、今回は AWS Step Functions の Map ステートの挙動を調べてみました。
AWS Step Functions の Map ステートとは
ドキュメント には Map ステートについて、次のように書いてあります。
Map 状態 ("Type": "Map") を使用して、入力配列の要素ごとに一連のステップを実行できます。Parallel 状態は同じ入力を使用して複数のステップのブランチを実行しますが、 Map 状態は状態入力の配列の複数のエントリに対して同じステップを実行します。
どうやら Map さんに配列を渡すと、配列の要素それぞれに対して同じことをしてくれるということのようですな。
例えば、コンビニで買い物をするとき、複数の商品をカゴに入れてレジに持っていくと、店員さんはそれぞれの商品に対してバーコードを読み取るみたいなことでしょうか。どうなんでしょうね。
例えは違ってるかもしれませんが、気にせず次に進もうと思います。
実際の Map ステートの例としては、下記のブログがわかりやすかったです。
[Step Functions]動的並列処理(Map)を使って60分×24時間=1440ファイルのバッチ処理を楽々実装してみた
このブログでは、[1, 2, 3, 4, 5]
という配列を Map ステートに入力すると、配列内の個々の値を 2乗した値を要素に持つ配列 [1, 4, 9, 16, 25]
が出力されています。
ここで気になったのが、Map ステートに入力する配列に、エラーになる値が含まれている場合はどうなるのかという点です。
上記ブログを例にとると、入力する配列が [1, 2, 'x', 4, 5]
で、2乗できない値 'x'
が含まれている場合などです。
ドキュメントを読んだ感じでは、なんとなくですが、2乗できない値はエラーになるけど、それ以外の値は正常に処理されて、正常に処理された分だけを要素に持つ配列が出力されるのかなと思いました。
実際どうなのか、やってみました。
やってみた
前記ブログの make_array.py
の内容を下記のように書き換えて、Serverless Framework を使用してデプロイしました。
def lambda_handler(event, context):
return [1, 2, 'x', 4, 5]
すると、下の画像のようなステートマシンや Lambda 関数が作成されました。ステートマシンの見た目は、前記ブログと変わりません。
少し気になったのが、make_array.py が返す配列の3つ目の要素はシングルクォーテーションで括ったはずなのですが、作成された Lambda 関数ではそうなっていない点です。
しかし、この状態でも 2乗するとエラーになるとは思うので、このまま進めることにしました。
この状態でステートマシンを実行した結果が下の画像です。実行ステータスは失敗になってしまいました。。
「グラフインスペクター」の「Map イテレーションの詳細」を見ると、成功:3、失敗:1、キャンセル済み:1 となってました。
実行イベント履歴を見ると、Map ステートに入ってから配列の各要素を入力とする「LambdaFunctionScheduled」というイベントがほぼ同時刻に 5回記録されていました。
その後、配列の各要素ごとに Lambda 関数の実行が開始され、配列の要素の値でいうと 5, 2, 1
の処理が成功で終了し、x
で失敗、わずかな時間差で処理がまだ終わっていなかった 4
がキャンセルされたということのようです。
しかし、こんなわずかな時間差で成功するかキャンセルされるかの違いが生まれるなんて厳しい世界ですね。。
それに、一つの要素が失敗するとステートマシン全体が失敗になってしまうので、成功した要素たちの努力も水の泡です。
連帯責任というやつでしょうか。世知辛い世の中です。。私にはとても耐えられそうにありません。。。
いや、諦めるのはまだはやい!きっとなにか手があるはずだ!!
というわけで、そんな世の中に希望の光を灯すべく、Map ステートで配列の一部の要素の処理が失敗しても、ステートマシン全体を成功させることはできないのか、調べてみました。
Map ステートで一部の処理が失敗してもステートマシン全体を成功させるには
Map ステートでも Catch フィールドや Retry フィールドを使用してエラー処理をおこなうことができるので、とりあえず Catch フィールドを使用したエラー処理を試してみました。
冒頭に紹介したブログに掲載されている serverless.yml ファイルの Step Functions を定義している部分に、下記のように Catch フィールドを追加してデプロイしました。
stepFunctions:
stateMachines:
StateMachine01:
definition:
StartAt: MakeArrayState
States:
MakeArrayState:
Type: Task
Resource:
Fn::GetAtt: [MakeArrayLambda, Arn]
Next: CalcSquareState
CalcSquareState:
Type: Map
Iterator:
StartAt: CalcSquare
States:
CalcSquare:
Type: Task
Resource:
Fn::GetAtt: [CalcSquareLambda, Arn]
Catch:
- ErrorEquals: ["States.ALL"]
Next: MapError
End: true
MapError:
Type: Pass
Result: "Error"
End: true
End: true
下の画像のようなステートマシンができあがりました。見た目が少し変わりました。イメチェンです。
そして、ステートマシンを実行してみると、見事成功しました!
「グラフインスペクター」の「Map イテレーションの詳細」を見ると、成功:5 になってました。
インデックス番号2 (値は x
) の処理では CalcSquare
のところでエラーになるけど、それが Catch フィールドで正常に処理されたので成功ということですね。
うまくいって一安心です。これでみんな幸せになれるでしょう。
まとめ
ドキュメントを読んでもわからない挙動を知るには、実際に自分で試してみるのが一番だなと思いましたとさ。