API Gateway と Lambda を使用して外部サービスの API を呼び出すアプリケーションを構築しました。このアプリケーションでは、API Gateway を通じて HTTP リクエストを受け取り、Lambda 関数がそのリクエストを適切に処理して外部サービス API を呼び出す仕組みを実装しました。
このプロジェクトの主要な要件として以下がありました。
- 秒間最大50リクエスト
- 処理時間2秒以内
- 処理時間:リクエストを受け付けてから、外部サービスに転送するまでの時間
上記の要件を満たすシステムを構築するために、Lambda・API Gateway それぞれ検討したことや詰まったポイントをまとめます。
※今回はマネージドのサービス(APIGateway/Lambda) を用いたアプリケーションを構築しており、要件がプログラム側(Lambda 関数内部)よりもAWSサービスの設定に依存しているものだったため、AWS サービスの設定において対応したことについて記載します。
前提
- Lambda Runtime:Node.js 20
- 構成:API Gateway -> Lambda
※今回構築したシステムはリアルタイムでの処理を求められるシステムのため、本記事では非同期的な処理やキューイングについては扱いません。
Lambdaのレスポンスを早めるために検討したこと
Lambda を用いて構築する場合、各実行環境の初回起動に時間がかかることが懸念として上がります。実際に検証として作成した Lambda 関数では起動時間が平均 450ms ほどかかっていました。
上記の懸念を解消して、要件を満たすために以下の内容を検討・対応しました。
① Provisioned Concurrencyの導入の検討
Provisioned Concurrencyとは
Lambdaはデフォルトでは、呼び出されてから
実行環境を立ち上げ -> 関数の実行 -> 実行環境を終了
という流れで実行されます。その場合、一般的なサーバーにあるアプリケーションと異なり呼び出しから起動までの時間がかかり、処理までに遅延が発生します。(コールドスタート)一方で、すでに実行環境が起動されている状態で呼び出された場合、既存の実行環境を再利用して関数を実行します。その場合は実行環境の立ち上げ時間が不要のため、比較的素早く処理が完了します。(ウォームスタート)
Provisioned Concurrencyは、Lambda 関数の実行環境をあらかじめ指定した台数起動しておくことで、コールドスタートを防ぎ、それに伴う遅延を回避する仕組みです。
設定はLambda関数設定より行うことができます。
導入検討
実際に、サンプルアプリで実行時間の検証を行いました。
- 導入効果
- 事前に起動した実行環境が用意されているため、初期起動時間の分レスポンスまでの時間が短縮されることが確認できました。
- 一方で、当然ですが関数自体の処理時間は変わりませんでした。
- コスト
- 常に数台のコンテナを起動しておくため、起動時間とメモリ・台数に対してコストがかかり、コストアップが見込まれます。(公式ドキュメント)
想定コスト(常時起動1台あたり):約18 USD/月(メモリ128 MBの場合)
コールドスタートになるリクエストをなくして最大で毎秒50件のリクエストを全て2秒以内に処理するためには、常に数台のサーバーを起動しておく必要があると考えました。
上記より、短縮が見込まれる実行時間(初期起動時間)とコストのバランスから導入を見送ることとしました。
② メモリの増強
Lambda 関数に割り当てるメモリ量は、実行速度に直接影響します。一方で、大きいメモリを割り当てることでコストもその分増加することが考えられます。
そこで実行時間を計測してコストのバランスを検討しました。
前提:
- 月間最大リクエスト数= 11,880,000回
- Lambdaのコストは公式ドキュメントを参照
パフォーマンスとコスト
メモリ | 平均レスポンス時間 | コスト(実行時間に対する課金) |
---|---|---|
128MB | 1.4秒 | 約35 USD/月 |
512MB | 0.3秒 | 約30 USD/月 |
※ コスト= 実行時間 (ms) × 想定最大リクエスト数 × 1ms当たりの単価で算出
※ リクエスト数に対する課金は、メモリによって変動しないため省略(約2.37 USD/月)
上記の結果から、同じリクエスト数を受け付ける場合
- パフォーマンスの改善(実行時間の短縮)
- 実行時間の短縮によるコストの削減
が期待できることが分かったため、512MB のメモリを採用することとしました。
メモリを改善することで、コールドスタートであってもレスポンス時間の要件はクリアできました!
リクエスト数制限の落とし穴
今回の要件のもう一つに、「秒間 50リクエスト以内に抑える」がありました。
50リクエスト以内に抑えるために、API Gateway で制限を行いました。
API Gatewayによるリクエスト制御
API Gateway では、メソッドごとにリクエストのレートとバーストの設定を行いました。 ※これらの制限にかかった場合は、Lambda にリクエストが送られずAPI Gatewayから 429 Too Many Requests のレスポンスが返ります。
- レート制限: 秒間に受け付けるリクエスト数
- 設定値:50
- バースト制限: 受け付ける同時実行数
- 設定値:100(秒間 50 リクエストを受け付け & 1 リクエストの最大実行時間が2秒以内のため)
これらの設定により、秒間50リクエストの受付とそれ以上の過剰なリクエストを遮断することで、 Lambda 関数内で呼び出すAPIに過剰な負荷がかからない状態とすることができます。
問題点
API Gateway のリクエスト数制限にはかかっていないにも関わらず Lambda にリクエストが届かず、 秒間 50 リクエストに到達しないということが発生しました。
- AWSデフォルトの制限: AWSのデフォルトで一つのアカウントあたり 1000 の並行実行数が割り当てられています。
Lambdaはアカウントに対し、1つのAWSリージョン内のすべての関数全体での合計数 1000 を上限とした同時実行をデフォルトで提供しています。
(引用:AWS公式ドキュメント)
- 問題点: 本プロジェクトで使用していたアカウントの同時実行数制限(初期値)が10に設定されていたため、1リクエストの実行時間が長い場合に Lambda の同時実行数制限によって関数が起動されていませんでした。
実際に、アカウントレベルの制限を「Service Quotas」から確認すると、
- AWSのデフォルトのクォータ値: 1000
- 適用されたアカウントレベルのクォータ値:10
と設定されていました。
デフォルトの制限値と適用されているアカウントレベルの制限値が異なるということを知らなかったため、少しはまってしまいました。
これに対しては、AWS への Lambda 関数同時実行数の制限引き上げリクエストを行い対応しました。
制限引き上げリクエストは、管理コンソール > 「Service Quotas」から簡単に申請でき、許可されれば適用されます。
まとめ
今回、プロジェクトを進めるにあたり、要件を満たすために試行錯誤した内容の一部をまとめました。
以前から Lambda を使ったことはありましたが、メモリを増やすとコストが上がるという大まかな理解しかしていませんでした。今回のプロジェクトで、実行時間とコストのバランスを実際に計測して検討した結果、適切なメモリを設定することが重要であることを認識しました。
また、今回は導入を見送った Provisioned Concurrency についても、固定の台数を常に起動しておく以外にも、使用率やスケジュールで起動しておく台数を変動させることも可能なようです。リクエストの傾向がわかっていて、コールドスタートを回避したい場合はこちらの導入を再度検討してもよいかもしれません。
これらの知見を今後のプロジェクトに活かしていきたいと思います。
新卒3年目のエンジニア。趣味は筋トレです。