Serverless FrameworkでStep Function (Map + エラーハンドル)
Step FunctionでMapを使った動的なタスク実行ができるようになりました。そしてそうなってくると、そのステップ内でエラーが起きた時にリトライさせたくなってきます。 ということで、リトライのあるMapのタスクを作 […]
広告ここから
広告ここまで
目次
Step FunctionでMapを使った動的なタスク実行ができるようになりました。そしてそうなってくると、そのステップ内でエラーが起きた時にリトライさせたくなってきます。
ということで、リトライのあるMapのタスクを作ってみました。
タスクの流れ
- 数字の配列(1 ~ 5)をreturnするLambdaを実行
- 各数字それぞれをMapで同時に処理する
- 偶数のタスクは失敗する
- 失敗したタスクは数字の1を渡してリトライさせる
- 奇数のタスクはさらに+1する
- 最後に処理された結果をCloudWatch Logsへ吐き出す
Lambda1つでやれよという案件ですが、Step Functionであえてやってみます。
各Lambda Function
先に関数を実装しておきましょう。
import { Handler } from 'aws-lambda';
import 'source-map-support/register';
// 配列のセットアップ
export const map: Handler = async (event) => {
console.log(event)
return [1,2,3,4,5]
}
// 偶数奇数判定
export const mapTask: Handler = async (event) => {
console.log(event)
if (event % 2 === 0) throw new Error(`${event} is not odd`)
return {
event,
times: event * 2
}
}
export const mapTask2: Handler = async (event) => {
return {
...event,
// 特に意味はないけど、+1した値を足してみる
add: event.times + 1
}
}
export const errorHandler: Handler = async (event) => {
console.log(event)
// oddは弾かれるので1を渡して調整してやる
return 1
}
export const end: Handler = async (event) => {
// 結果のログ出力
console.log(JSON.stringify(event))
return {
statusCode: 200,
body: JSON.stringify(event)
};
}
Functionレベルで分割するので、それぞれの見通しはかなりよくなりました。
Serverless Frameworkでスタックを作る
あとは実際に実行するスタックを定義すればOKです。
service:
name: sls-playground
plugins:
- serverless-webpack
- serverless-step-functions
provider:
name: aws
runtime: nodejs10.x
logRetentionInDays: 14
region: us-east-1
stage: development
functions:
hello:
handler: handler.hello
map:
handler: handler.map
mapTask:
handler: handler.mapTask
mapTask2:
handler: handler.mapTask2
end:
handler: handler.end
errorHandler:
handler: handler.errorHandler
stepFunctions:
stateMachines:
yourCatchMachine:
definition:
Comment: "A Catch example of the Amazon States Language using an AWS Lambda Function"
StartAt: HelloWorld
States:
# 配列をセットアップ
HelloWorld:
Type: Task
Resource:
Fn::GetAtt: [map, Arn]
Next: mapped_task
# Mapで1つずつ処理する
mapped_task:
Type: Map
Iterator:
StartAt: MapTask
States:
# 偶数奇数判定
MapTask:
Type: Task
Resource:
Fn::GetAtt: [mapTask, Arn]
Catch:
- ErrorEquals: ["States.ALL"]
Next: MapError
Next: MapTask2
# +1する処理
MapTask2:
Type: Task
Resource:
Fn::GetAtt: [mapTask2, Arn]
End: true
# 1を渡して強制的に奇数にする
MapError:
Type: Task
Resource:
Fn::GetAtt: [errorHandler, Arn]
Next: MapTask
Next: end_task
# 結果をログ出力
end_task:
Type: Task
Resource:
Fn::GetAtt: [end, Arn]
End: true
これを実行すると、このような結果がend_taskのLambdaログに残ります。
[
{
"event": 1,
"times": 2,
"add": 3
},
{
"event": 1,
"times": 2,
"add": 3
},
{
"event": 3,
"times": 6,
"add": 7
},
{
"event": 1,
"times": 2,
"add": 3
},
{
"event": 5,
"times": 10,
"add": 11
}
]
偶数の値(2と4)が1に丸められて処理されていることがわかります。
注意点
Map Taskがエラーを投げる-> Map Errorで値を修正 -> Map Taskを呼び出しという形になっています。つまり無限ループの可能性がここには潜んでいます。
今回は簡略化するためにこの形にしていますが、実際にはリトライ回数をみて強制的に落とすような仕組みがあった方がよさそうです。