FastAPI on AWS Lambda : Lambda Web AdapterとMangumはどちらがコールドスタートが速いのか
AWS LambdaでFastAPIを動かす方法として、下記の2つがあります。
-
Lambda Web Adapter
- AWSが提供するLambda Extensionで、Webアプリケーションをコード変更なしでLambda上で実行できる。
-
Mangum
- Pythonのライブラリで、FastAPIなどのASGIアプリケーションをLambdaのイベント形式に対応させることで動作させる。
どちらも試したところ、手軽さというところでは大差なかったので、Lambdaで動かすうえで重要になってくるコールドスタートの違いを比較してみました。
計測方法
簡単なFastAPIアプリケーションを用意し、それぞれの方式でAWS Lambdaにデプロイして確認しました。
その他の条件は下記の通りです。
- Lambdaのメモリサイズ: 256MB
- デプロイ形式: Image
- API Gateway: HTTP API
それぞれのコードは下記になります。(タグ切っています)
- Lambda Web Adapter https://github.com/onozaty/fastapi-dynamodb-sample/tree/coldstart-webadapter-image
- Mangum https://github.com/onozaty/fastapi-dynamodb-sample/tree/coldstart-mangum-image
コールドスタートを発生させるにはLambdaのコンテナが破棄される必要があるので、デプロイ直後と、しばらく時間をあけてからリクエストを送って確認しています。
コールドスタートが発生した(Init Phaseが発生した)ことは、CloudWatch LogsのREPORT行にInit Durationが表示されることで確認しています。
REPORT RequestId: 8c8dbb26-139e-4438-802d-207014a978e7 Duration: 1150.79 ms Billed Duration: 2228 ms Memory Size: 256 MB Max Memory Used: 120 MB Init Duration: 1076.84 ms
計測結果
それぞれ11回計測しています。
Init Durationがコールドスタート時に発生する初期化処理の時間になります。Durationが関数(今回だと1リクエスト)自体の実行時間で、Billed Durationが請求対象の時間(Init Phaseを含む関数実行にかかった時間)になります。
Lamda Web Adapter
| # | デプロイ後初回 | Init Duration(ms) | Duration(ms) | Billed Duration(ms) |
|---|---|---|---|---|
| 1 | ○ | 8,018.37 | 21.08 | 8,040.00 |
| 2 | 2,327.90 | 1,270.40 | 3,599.00 | |
| 3 | 2,188.89 | 1,173.84 | 3,363.00 | |
| 4 | 1,828.71 | 1,080.68 | 2,910.00 | |
| 5 | 1,969.84 | 1,094.44 | 3,065.00 | |
| 6 | ○ | 2,754.96 | 1,153.36 | 3,909.00 |
| 7 | 1,987.27 | 1,115.45 | 3,103.00 | |
| 8 | 2,033.40 | 1,162.41 | 3,196.00 | |
| 9 | 2,125.03 | 1,084.20 | 3,210.00 | |
| 10 | 2,010.93 | 1,160.73 | 3,172.00 | |
| 11 | 2,022.96 | 1,190.60 | 3,214.00 |
デプロイ後初回はECRからのイメージの取得が発生&キャッシュが無い状態なので、Init Durationがより長くなっています。
初回はユーザが体感するコールドスタート時間としてはあまり参考にならないので、初回以外のInit Durationを見ると、 平均2,054.99ミリ秒 となりました。
Mangum
| # | デプロイ後初回 | Init Duration(ms) | Duration(ms) | Billed Duration(ms) |
|---|---|---|---|---|
| 1 | ○ | 5,820.75 | 1,246.05 | 7,067.00 |
| 2 | 1,091.35 | 1,143.80 | 2,236.00 | |
| 3 | 995.88 | 1,127.97 | 2,124.00 | |
| 4 | 1,187.51 | 1,201.02 | 2,389.00 | |
| 5 | 1,034.21 | 1,141.60 | 2,176.00 | |
| 6 | 1,056.19 | 1,172.21 | 2,229.00 | |
| 7 | 1,010.56 | 1,111.53 | 2,123.00 | |
| 8 | ○ | 1,852.35 | 1,211.97 | 3,065.00 |
| 9 | 1,140.37 | 1,169.02 | 2,310.00 | |
| 10 | 1,039.76 | 1,166.31 | 2,207.00 | |
| 11 | 1,274.34 | 1,174.36 | 2,449.00 |
初回を除いたInit Durationを見ると、 平均1,092.24ミリ秒 となりました。
まとめ
Lambda Web AdapterとMangumのコールドスタート時間を比較した結果、Init DurationはMangumの方がLambda Web Adapterより早いことがわかりました。
MangumはUvicornを使わず直接Lambdaのイベント形式に対応させますが、Lambda Web Adapterの場合はUvicornを起動することになるので、その分コールドスタート時間が長くなるのかもしれません。
補足
コールドスタート時以外の情報は今回載せてていませんが、Duration(リクエスト自体の処理時間)はコールドスタート時は約1,000ms、それ以降は約20~40msと大きくことなっていました。
コールドスタート時(=Lambdaインスタンス起動初回時)はPythonのコンパイルキャッシュが無いためではと想定しています。これは事前にコンパイルキャッシュを用意することで改善できると思うので、別途試してみたいと思います。
ライブラリのコンパイルキャッシュはUV_COMPILE_BYTECODE=1を設定していたので既にされていました。app配下のコードに対して追加でpython -m compileall -f -j 0 -qしてみましたが変化ありませんでした。
固定の文字列返すようなAPIを追加して試したら早かったので、Pydantic周りかDynamoDBクライアントの初期化あたりが影響しているのかもしれません。
(追記@2025-11-21) メモリが少ないことでCPUリソースも制限され、初期化に時間がかかっていたようです。
メモリを増やしていったところ、初回のDurationが大幅に改善しました。
メモリ毎の初回Duration結果
| # | Memory Size(MB) | Duration(ms) |
|---|---|---|
| 1 | 256 | 1121.89 |
| 2 | 256 | 1170.42 |
| 3 | 256 | 1143.62 |
| 4 | 256 | 1123.75 |
| 5 | 256 | 1125.77 |
| 6 | 512 | 553.89 |
| 7 | 512 | 536.51 |
| 8 | 512 | 534.81 |
| 9 | 512 | 544.82 |
| 10 | 512 | 530.64 |
| 11 | 1024 | 291.00 |
| 12 | 1024 | 280.33 |
| 13 | 1024 | 286.72 |
| 14 | 1024 | 284.70 |
| 15 | 1024 | 300.80 |
| 16 | 1536 | 218.52 |
| 17 | 1536 | 201.64 |
| 18 | 1536 | 208.79 |
| 19 | 1536 | 335.50 |
| 20 | 1536 | 224.00 |
| 21 | 2048 | 196.71 |
| 22 | 2048 | 190.46 |
| 23 | 2048 | 195.35 |
| 24 | 2048 | 203.50 |
| 25 | 2048 | 191.16 |
Discussion