Lambda コールドスタートの正体と 3 つの最適化戦略の使い分け

Lambda のコールドスタートが発生するメカニズムを Firecracker MicroVM のライフサイクルから解説し、SnapStart / Provisioned Concurrency / 関数設計の 3 軸で 3 つの最適化手法をコストと制約の面から比較します。

コールドスタートはなぜ起きるのか

Lambda のコールドスタートを正しく最適化するには、まず発生メカニズムを理解する必要があります。Lambda は Firecracker MicroVM 上で関数を実行します。新しいリクエストが到着したとき、再利用可能な実行環境が存在しなければ、AWS は MicroVM の起動、ランタイムの初期化、関数コードのダウンロードと展開、ハンドラ外のグローバルスコープの実行という一連のプロセスを経てからリクエストを処理します。この初期化プロセス全体がコールドスタートです。重要なのは、コールドスタートの大部分は AWS 側の制御下にある MicroVM 起動とランタイム初期化であり、開発者が直接制御できるのはパッケージサイズとグローバルスコープの初期化処理だけだという点です。Python や Node.js のような軽量ランタイムでは AWS 側の初期化が 100〜200ms 程度で完了するのに対し、Java や .NET ではランタイム自体の起動に 500ms〜数秒を要します。この差がランタイム選択の重要な判断材料になります。

ランタイム別のコールドスタート特性

ランタイムごとのコールドスタート特性は、アーキテクチャ設計の初期段階で考慮すべき要素です。以下に挙げるミリ秒の数値はいずれも本記事の目安で、AWS が公表している値ではありません。メモリ設定・依存ライブラリの量・初期化コードの内容で上下します。Node.js と Python は最も軽量で、128MB メモリ設定でもコールドスタートが 200〜400ms 程度に収まります。Go はコンパイル済みバイナリとして動作するため、ランタイム初期化のオーバーヘッドがほぼゼロで、コールドスタートは 100〜200ms と最速クラスです。一方、Java は JVM の起動と JIT コンパイルの初期化に時間がかかり、Spring Boot のような DI フレームワークを使用すると 3〜10 秒のコールドスタートが発生することも珍しくありません。.NET も CLR の起動に 500ms〜1 秒程度を要します。ただし、Java と .NET はウォームスタート時のスループットが高く、長時間実行されるバッチ処理や計算集約型のワークロードでは有利です。つまり、コールドスタートの頻度とウォームスタート時の性能のどちらを重視するかで最適なランタイムは変わります。API のバックエンドのようにレイテンシが重要なユースケースでは Node.js や Python、バッチ処理では Java という使い分けが合理的です。

表: ランタイム別のコールドスタートの目安 (本記事の目安。メモリ設定と実装で変わる概算)
ランタイムコールドスタートの目安性質と向き先
Go100〜200 ミリ秒コンパイル済みバイナリとして動くため、ランタイム初期化のオーバーヘッドがほぼない
Node.js / Python200〜400 ミリ秒 (メモリ 128MB でも)最も軽量。レイテンシが効く API のバックエンドに向く
.NETCLR の起動に 500 ミリ秒〜1 秒ウォームスタート時のスループットは高い
JavaJVM 起動と JIT の初期化ぶん。Spring Boot のような DI フレームワークでは 3〜10 秒になることもSnapStart で起動性能を最大 10 倍まで改善でき、秒未満に収まる例もある。長時間のバッチや計算集約型では有利

SnapStart - Java コールドスタート問題の根本解決

2022 年の re:Invent で発表された SnapStart は、Java ランタイムのコールドスタート問題に対する AWS の回答です。SnapStart はバージョン発行時に初期化済みの実行環境のスナップショットを取得し、暗号化してキャッシュします。コールドスタート時は、実行環境をゼロから初期化する代わりにこのスナップショットから復元します。これにより、JVM の起動と Spring Boot の DI コンテナ初期化をスキップでき、AWS の表現では起動性能が最大 10 倍まで改善します。秒未満に収まる例もありますが、短縮幅はアプリケーションの規模と初期化処理の重さ次第で、数秒の起動が 1 秒台になる程度に留まるケースもあります。SnapStart を有効にするには、関数の設定で SnapStart の ApplyOn を PublishedVersions に設定するだけです。ただし、SnapStart にはいくつかの制約があります。スナップショットからの復元時に、ランダム値の再生成やネットワーク接続の再確立が必要になるため、初期化コードで一意性に依存する処理 (UUID 生成、暗号鍵の初期化など) を行っている場合は afterRestore フックで再初期化する必要があります。また、Provisioned Concurrency との併用はできません。対応するのはマネージドランタイムのうち Java 11 以降・Python 3.12 以降・.NET 8 以降で (Python と .NET へは 2024 年 11 月に拡張されました)、コンテナイメージベースの関数では使用できない点にも注意してください。料金は言語で分かれます。Java のマネージドランタイムは追加コストなしですが、Python と .NET はスナップショットのキャッシュ (最低 3 時間分が課金対象) と復元に対して料金が発生します。

Provisioned Concurrency - コストと引き換えの確実性

Provisioned Concurrency は、指定した数の実行環境を事前にウォーム状態で維持する機能です。設定した並列数の範囲内ではコールドスタートが発生しませんが、アイドル状態でも課金が発生するため、コスト設計が重要になります。Provisioned Concurrency の料金は、プロビジョニングされた同時実行数 × 時間で計算されます。たとえば、1024MB メモリの関数を 100 同時実行で 1 ヶ月 (730 時間) 通してプロビジョニングすると、us-east-1 の 0.0000041667 USD/GB 秒 で計算して 262,800,000 GB 秒 = 月額約 1,095 USD のプロビジョニング料金になります (2026 年 8 月時点)。これに加えて、プロビジョニング済み環境での実行時間 0.0000097222 USD/GB 秒 とリクエスト課金もかかります。桁を間違えると月次予算を丸ごと食う規模なので、常時プロビジョニングを前提にせず、必要な時間帯だけ確保する設計から始めるのが安全です。コスト効率を高めるには、Application Auto Scaling と組み合わせて、トラフィックパターンに応じてプロビジョニング数を動的に調整します。たとえば、平日の営業時間帯は 100、夜間は 10、週末は 5 というスケジュールベースのスケーリングを設定できます。CloudWatch メトリクスの ProvisionedConcurrencySpilloverInvocations が 0 でない場合、プロビジョニング数が不足しているサインです。逆に ProvisionedConcurrencyUtilization が常に低い場合はプロビジョニング数を減らしてコストを削減できます。

関数設計による最適化 - 開発者が今すぐできること

SnapStart や Provisioned Concurrency を使わなくても、関数の設計を見直すだけでコールドスタートを大幅に短縮できます。最も効果が大きいのはパッケージサイズの削減です。Lambda はコールドスタート時に S3 からデプロイパッケージをダウンロードして展開するため、パッケージが大きいほど初期化に時間がかかります。Node.js であれば esbuild や webpack でバンドルし、tree-shaking で未使用コードを除去してください。AWS SDK v3 はモジュラー設計なので、@aws-sdk/client-s3 のように必要なクライアントだけをインポートすれば、SDK 全体をバンドルする場合よりパッケージが小さくなり、ダウンロードと展開に費やす時間を削れます。Python では Lambda Layers に共通ライブラリを分離し、関数本体のパッケージを軽量に保ちます。メモリ設定も重要な最適化ポイントです。Lambda はメモリに比例して CPU パワーが割り当てられるため、メモリを増やすとコールドスタートの初期化処理も高速化します。128MB から 512MB に増やすだけで初期化時間が半減するケースもあります。AWS Lambda Power Tuning ツールを使えば、コストとパフォーマンスの最適なメモリ設定を自動的に見つけられます。

3 つの最適化手法の使い分け

コールドスタート最適化の 3 つのアプローチは、ユースケースに応じて使い分けるべきです。API Gateway のバックエンドのように、P99 レイテンシが SLA に直結するユースケースでは、Provisioned Concurrency が最も確実です。コストは増加しますが、設定した並列数の範囲内であればコールドスタートは発生しません。ただし同時実行が設定値を超えた分は通常の呼び出しとして扱われ、そこではコールドスタートが起こります。Java や .NET を使用しており、コールドスタートが 1 秒を超えるケースでは、まず SnapStart を検討してください。Java のマネージドランタイムなら追加コストなしで起動時間を大きく縮められます (.NET 8 以降と Python 3.12 以降も対応していますが、こちらはスナップショットのキャッシュと復元が課金対象です)。コールドスタートが 500ms 以下で許容範囲内であれば、関数設計の最適化だけで十分です。パッケージサイズの削減、メモリ設定の調整、グローバルスコープでの接続プール初期化を組み合わせれば、追加コストなしで 200〜300ms のコールドスタートを実現できます。非同期処理 (SQS トリガー、EventBridge ルールなど) では、数百ミリ秒のコールドスタートはエンドユーザーに影響しないため、最適化の優先度を下げて構いません。

表: 3 つの最適化手法をコスト・制約・向き先で並べる
観点SnapStartProvisioned Concurrency関数設計の見直し
効果起動性能を最大 10 倍まで改善。秒未満に収まる例もある設定した並列数の範囲内では発生しない (超過分は通常のコールドスタート)追加コストなしで 200〜300 ミリ秒を目指せる
追加コストJava は なし。Python 3.12 以降・.NET 8 以降はスナップショットのキャッシュ (最低 3 時間) と復元に課金プロビジョニング数 × 時間で常時課金 (1024MB を 100 同時実行で 730 時間なら us-east-1 で月額約 1,095 USD・2026 年 8 月時点)なし
対象Java 11 以降・Python 3.12 以降・.NET 8 以降のマネージドランタイム (Python・.NET は 2024 年 11 月に追加)すべてのランタイムすべてのランタイム
主な制約Provisioned Concurrency と併用不可。コンテナイメージの関数では使えない。復元時に一意性が要る処理は afterRestore フックで再初期化するアイドル時も課金される。Application Auto Scaling でトラフィックに追従させる設計が必要AWS 側の MicroVM 起動とランタイム初期化には手が出せない
向いている場面Java・Python・.NET でコールドスタートが 1 秒を超えているP99 レイテンシが SLA に直結する API のバックエンド500 ミリ秒以下で許容できる場合、SQS や EventBridge 起点の非同期処理

参考資料 (AWS 公式)

本ページの一次情報は AWS 公式サイトおよび公式ドキュメントです。最新の仕様 / 料金は次の公式ページで確認できます。

本ページと公式ドキュメントの記述が食い違う場合は、公式ドキュメントを正としてください。