APIGateway初心者あるある
「LambdaとAPIGatewayでAPI作ったで!
ちょい処理重めやけどLambdaは最大15分まで動くし、15分もあったら余裕やわ」
ポチっ(実行)
30秒後
{
"message": "Endpoint request timed out"
# 訳: いつまでリクエスト待たせんねんワレ
}
「15分間は耐えるんちゃうんかい」
(ナレーション)
彼はどこで間違えてしまったのでしょうか。
そうです、Lambdaは確かに設定を変えれば15分まではタイムアウト時間を延ばせますが、APIGatewayは最大30秒しか耐えられないのです。
つまりこのタイムアウトはLambdaによるものではなくAPIGatewayによるものなのです。その証拠に、Lambdaのログを見るとタイムアウト後も動作していることが確認できます。
解決策:APIを二つに分けて回避
やり方はいくつかあるみたいですが、今回は「Lambda発火API」と「Lambda状態取得API」の二つのAPIを用意することで解決します。
例えば単純にデータを取得するGETメソッドAPIであれば、もともとの設計は、
【これまでのAPI】
でしたが、タイムアウトを回避するため、
【今回作るAPI】
のようにします。
1~4と5~8でそれぞれ別のエンドポイントを叩いています。
もともとのAPIとの違いは、3でデータを返していないということです。
3では整理券なるものを渡し、後でデータが用意できたところを自分で取りにいくというイメージです。
いつデータが完成するかわからないので何度か定期的に訪れる必要があるでしょう。
実装
前提
- APIで実行したいLambdaは用意されている
Step FunctionsでLambdaを動かすステートマシンを作成
ここまで、「Lambda発火」や「Lambda動かしてや」などと言ってきましたが、正しくは「ステートマシンを動かす」になります。
どういうことかはステートマシンを作成するとわかります。
AWSマネコンから「Step Functions」を開いて、ステートマシンを作成します。
Step Functionsはあらゆるデータのやりとりを一つのフローとして保存することができるサービスです。詳しくはハンズオンとか見てください。
以下のように定義します。
現在はステートマシンの設計は「Workflow Studio」というGUI操作でフローを組み立てられる機能があるのでこの画像だけで十分かもしれません。
一応、コードにするとこうなってます。
{
"Comment": "",
"StartAt": "Lambda Invoke",
"States": {
"Lambda Invoke": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"OutputPath": "$.Payload",
"Parameters": {
"Payload.$": "$",
"FunctionName": "arn:aws:lambda:ap-northeast-1:xxx:function:xxx:$LATEST"
},
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException",
"Lambda.TooManyRequestsException"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
}
],
"End": true
}
}
}
このように、ただLambdaを実行するだけのステートマシンを用意します。なぜわざわざStep Functionsをかまさないといけないかは後ほどわかります。
API GatewayでStep Functionsと連携するAPIを作成
「Lambda発火API」と「Lambda状態取得API」の二つのAPIを用意すると述べましたが、この二つのAPIをここからは
「 StartExecution API 」と「 DescribeExecution API 」と呼びます。
1. APIGatewayからStep Functionsを呼び出せるようにロールを作成
以下の公式ドキュメントのステップ1を参照してください。
2. StartExecution API(Lambda発火API)
これは上記APIフローの2「Lambda動かしてや」にあたります。
APIGatewayにはStartExecutionというアクションが用意されており、このAPIを叩くことでロールで指定したStep Functionsを実行することが可能です。
APIGatewayを開いて、executionというリソースを作成し、そこ POSTメソッド を作成します。
今回は、私がもともと作成していたAPIにリソースを追加する形で作成したので、画像(左部)にはPOSTが3つありますが一番上のPOSTは無視してください。
以下のようにメソッドを設定します。
3. DescribeExecution API(Lambda状態取得API)
これは上記APIフローの6「おいまだ出来てへんのか?」にあたります。
APIGatewayに用意されているDescribeExecutionは、実行タスクの状態を取得するAPIです。
DescribeExecutionは、Lambdaの状態を直接見ることはできませんが、Step Functionsの状態なら見ることができます。
これがLambdaをわざわざStep Functionsにかませた理由になります。
/executionの中にstatusというリソースを作成して同じく POSTメソッド を作成します。
以下のように設定します。「アクション」の部分だけ先ほどと違います。
これで完成です。
使い方
まずLambdaを発火させるためにStartExecution APIを叩きます。
/execution のPOSTを投げると、
{
"executionArn": "arn:aws:states:ap-northeast-1:xxx:execution:xxx:xxx",
"startDate": x.xxx
}
のようなデータが返ってきます。
このexecutionArn
が実行タスクになります。
次にLambdaの状態を取得するDescribeExecution APIを叩きます。
/execution/status のPOSTを投げるのですが、ここでリクエストボディに先ほどのexecutionArn
を含める必要があります。
{
"executionArn": "arn:aws:states:ap-northeast-1:xxx:execution:xxx:xxx"
}
これで
{
"executionArn": "arn:aws:states:ap-northeast-1:xxx:execution:xxx:xxx"
...(省略)...
"output": "{\"isBase64Encoded\":false,\"statusCode\":200,\"headers\":{},\"body\":{\"errorCode\":0,\"message\":\"成功\"}}",
"status": "RUNNING",
...(省略)...
}
のようなレスポンスが帰ってきたら成功です。
status
を見れば状態がわかるようになっています。
また、output
のbody
の中に本来のAPIで求めていたレスポンスが含まれています。
APIのリクエストにパラメーターを含めたい
パラメーターを渡す方法は簡単です。
まずStartExecution APIのマッピングテンプレートを編集します。
APIGatewayの画面から、StartExecution APIの統合リクエストの画面下部の
マッピングテンプレート > マッピングテンプレートの追加 を選択します。
application/jsonタイプで以下のように記述します。
{
"stateMachineArn": "arn:aws:states:ap-northeast-1:xxx:stateMachine:xxx",
"input": "$util.escapeJavaScript($input.json('$')).replaceAll('\\\\\"', '\"')"
}
ここで、stateMachineArnには、最初に作成したStep FunctionsのステートマシンのARNを入れます。Step Functionsのマネコンからすぐに確認することができます。
保存したら、あとはStartExecution APIを叩くときにいつも通りパラメーターをリクエストボディに含ませるだけです。
Lambdaのパラメーターの受け取り方
パラメーターはeventのbodyにあります。したがって、
event_body = json.loads(event['body'])
で受け取れます。
まとめ
WebAPIというのはすぐに実行結果を返すというのが大切というか前提になっているから、APIGatewayのタイムアウト制限は最大30秒になっているんでしょうか。
とにかく、Lambdaの処理に時間がかかる可能性があるなら非同期でLambdaを動かして、別のAPIで結果を取得する必要があります。これでとりあえず解決です。