1. この記事について

AWS Lambda は 2022 年 11 月に SnapStart を GA し、Java ランタイムの cold start を従来の数秒から数十 ms へと大幅に短縮する技術として注目を集めた。Firecracker MicroVM の snapshot / restore 機能を活用したこの仕組みは、Provisioned Concurrency と異なり追加コストなしに cold start を撲滅できるため、Java で Lambda を運用するチームにとって本番投入の必須技術となりつつある。しかし日本語記事には依然として「概念解説止まり」のものが多く、beforeSnapshot / afterRestore の Java 実コード、cold start 削減率の定量データ、Container image Lambda との併用不可点、Provisioned Concurrency との使い分けマトリクスを 1 本で網羅したガイドが見当たらない。
本記事はその空白を 1 本で解消する。Firecracker MicroVM snapshot の解剖から始まり、Terraform 完全実装、LifecycleHook の Java 完全実装、CloudWatch Logs Insights による cold start 計測、SnapStart vs Provisioned Concurrency の使い分けまでを Terraform 完全 HCL + Java 実コード付きで解説する。読者が「そのままコピー&ペーストで本番 SnapStart を投入できる」ことをゴールとしている。
- Vol1: Container image Lambda 本番運用完全ガイド — 10GB イメージ活用 / multi-arch / Lambda Web Adapter / cold start 最適化を1本で完走 (公開済)
- Vol2 (本記事): SnapStart 完全活用編 — Java Lambda の cold start を99%削減する本番実装ガイド
- Vol3: Powertools + Layers 統合運用編 — observability / parameter / idempotency の標準化と Layers 配布戦略 (準備中)
- Java Lambda を本番運用しており cold start に悩む中〜上級者
- SnapStart 未導入で「概念は知っているが実装に踏み切れない」チーム
- Provisioned Concurrency のコスト負担を SnapStart で代替できるか検討中のアーキテクト
1-1. なぜ今 SnapStart か
SnapStart が GA してから 3 年以上が経過したが、日本語記事には依然として 4 つの空白がある。
空白①: LifecycleHook の Java 実コードが希少
beforeSnapshot / afterRestore フックを Resource interface + Core.getGlobalContext().register() で実装する具体例を示した記事が国内にほぼ存在しない。本記事 §5 でこの空白を埋める。
空白②: cold start 削減率の定量データがない
「数秒 → 数十 ms」と一般論で書かれることはあっても、自社環境で再現可能な CloudWatch Logs Insights クエリと before-after 比較表 (p50 / p95 / p99) を提供した記事が見当たらない。本記事 §6 で実測値ベースの検証手段を提示する。
空白③: Container image Lambda との併用不可点が散在
2026-04 時点で SnapStart は Container image 非対応 (managed Java / .NET / Python ランタイムのみ)。Vol1 (Container image WP:2186) との関係性を 1 本で整理した記事が国内に少ない。本記事 §7 で明確化する。
空白④: SnapStart vs Provisioned Concurrency の使い分けがない
「いつ SnapStart / いつ PC / いつ両方」をコスト・レイテンシ・制約・ユースケースの 4 軸で示した選定マトリクスが希少。本記事 §7 で QG-5 として提示する。
1-2. 本記事のゴール
本記事を読み終えると以下を単独で実施できる。
| ゴール | 対応章 |
|---|---|
| Firecracker MicroVM snapshot / restore の動作理解 | §3 |
snap_start ブロック + publish=true + alias の Terraform 完全実装 | §4 |
| LifecycleHook (BeforeSnapshot / AfterRestore) の Java 実装 | §5 |
| CloudWatch Logs Insights による cold start 計測 (before-after) | §6 |
| SnapStart vs Provisioned Concurrency の使い分け判断 | §7 |
| 落とし穴 10 選を回避した本番投入 | §8 |
1-3. 差別化6軸
国内の SnapStart 解説記事と比較して、本記事が提供できる差別化要素を6軸で整理する。「概念解説止まり」でなく「本番投入まで完走できる」ことを一貫したゴールとして構成している。
- 軸1 — 仕組み図解: Firecracker MicroVM が Java Init Phase を snapshot 化し restore する動作原理を fig02 / fig03 で可視化。JVM 初期化完了状態の MicroVM イメージが S3 に保存される実装詳細まで踏み込む
- 軸2 — LifecycleHook 実装コード:
BeforeSnapshot/AfterRestoreフックを JavaResourceinterface +Core.getGlobalContext().register()で完全実装 (§5)。国内で実コードを示した記事がほぼ存在しない - 軸3 — Terraform 完全 HCL:
snap_start { apply_on = "PublishedVersions" }/publish = true/ alias の三点セットを単一 HCL で提供 (§4)。コンソール操作のみの記事との差別化 - 軸4 — 定量データ付き計測: cold start 削減率を p50 / p95 / p99 で提示し CloudWatch Logs Insights クエリで自社再現可能な形式にする (§6)
- 軸5 — Container image との関係整理: Vol.1 (Container image Lambda) との相違点を §7 で体系化。SnapStart が managed runtime 専用である理由と制約を先出しで明示
- 軸6 — SnapStart vs PC 選定マトリクス: コスト / レイテンシ要件 / 制約 / ユースケースの4軸で Provisioned Concurrency との選択判断マトリクスを §7 に掲載
これら6軸のうち特に 軸2 (LifecycleHook 実装) と 軸4 (定量データ計測) が既存記事に最も欠けている要素であり、本記事の核心をなす。
1-4. 章立て
| §N | タイトル | 概要 |
|---|---|---|
| §2 | 前提・環境・準備 | Java 21 corretto / Terraform 1.9.x / IAM / Lambda Insights |
| §3 | SnapStart の仕組み | Firecracker MicroVM snapshot / restore フロー / cold start タイムライン |
| §4 | 有効化 + Terraform 実装 | snap_start ブロック / publish=true / alias 運用 |
| §5 | LifecycleHook 実装 | BeforeSnapshot / AfterRestore Java 完全実装 |
| §6 | cold start 計測と検証 | Logs Insights クエリ + before-after 比較表 |
| §7 | 制約・落とし穴 + Provisioned Concurrency 使い分け | VPC / Container image 非対応 / PC 比較マトリクス |
| §8 | まとめ + Vol3 予告 + 落とし穴 10 選 | チートシート + シリーズナビ |
1-5. 想定環境
本記事のサンプルコードおよび Terraform HCL は以下の環境で動作確認している。
ツールバージョン一覧
| ツール / ライブラリ | バージョン | 用途 |
|---|---|---|
| Java (Amazon Corretto) | 21 | Lambda ランタイム・ローカル開発ともに統一 |
| Maven | 3.9.x | ビルドツール (Gradle 8.x でも同様) |
| Terraform | 1.9.x | インフラ管理 (aws provider ~> 5.0) |
| AWS CLI | v2.x | aws lambda invoke / aws logs start-query で利用 |
| aws-lambda-java-core | 1.2.3 | Lambda Handler・LifecycleHook インターフェース |
| aws-lambda-java-runtime-interface-client | 2.4.2 | CRaC SnapStart SDK (AfterRestore 対応) |
| Lambda Insights 拡張機能 | v1.0.229.0 | cold start 計測用 (§6) |
| AWS リージョン | ap-northeast-1 (東京) | SnapStart 全リージョン対応済 (2024 以降) |
ローカル開発環境は macOS / Linux を前提としているが、Windows (WSL2) でも同様の手順で動作する。
IAM 実行ロール最小権限
Lambda 関数に付与する実行ロールには以下の AWS マネージドポリシーをアタッチする。
# Lambda 基本実行ロール (CloudWatch Logs 書き込み)
resource "aws_iam_role_policy_attachment" "lambda_basic" {
role = aws_iam_role.lambda_exec.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
# Lambda Insights (§6 cold start 計測時に必要)
resource "aws_iam_role_policy_attachment" "lambda_insights" {
role = aws_iam_role.lambda_exec.name
policy_arn = "arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy"
}
SnapStart 対応ランタイム (2026-04 時点)
Java 11 / 17 / 21 corretto・.NET 8・Python 3.12+ が対応。
Container image Lambda は非対応。§7 で詳述する。
2. 前提・環境・準備
2-1. 前提環境
本記事では以下の前提環境を想定している。
AWS 環境前提
- AWS アカウント (Administrator または Lambda / CloudWatch / IAM の操作権限を持つロール)
- Lambda 対応リージョン (SnapStart は全リージョン対応。本記事サンプルは ap-northeast-1 で動作確認)
- S3 バケット (SnapStart の snapshot 保存先は AWS マネージド。ユーザーによるバケット作成は不要)
ローカル開発環境確認
| ツール | 確認コマンド | 期待される出力例 |
|---|---|---|
| Java 21 (corretto) | java -version | openjdk version "21.x.x" ... Corretto |
| Maven 3.9+ | mvn -version | Apache Maven 3.9.x |
| Terraform 1.9.x | terraform version | Terraform v1.9.x |
| AWS CLI v2 | aws --version | aws-cli/2.x.x Python/3.x.x |
Java は Amazon Corretto 21 の使用を推奨する。SnapStart は corretto 系ランタイムでのみ動作し、他のディストリビューション (temurin 等) は Lambda ランタイムとして利用できない。ローカルで使用する Java バージョンと Lambda ランタイムのバージョンを一致させることで、ライブラリ互換性起因のトラブルを防ぎやすくなる。
Lambda Insights 拡張機能の有効化は §4 / §6 で Terraform を使用して行う。リージョン別 ARN リストは AWS 公式ドキュメント「CloudWatch Lambda Insights」を参照されたい。
2-2. 使用技術スタック
本記事で使用するサービス・ライブラリ・バージョンを一覧にする。
| サービス / ライブラリ | バージョン | 役割 |
|---|---|---|
| AWS Lambda | SnapStart GA (2022-11〜) | 実行環境 |
| Amazon Corretto Java | 21 | Lambda ランタイム (SnapStart 対応) |
| aws-lambda-java-core | 1.2.3 | RequestHandler / Context / Resource interface |
| aws-lambda-java-runtime-interface-client | 2.4.2 | CRaC LifecycleHook SDK (AfterRestore 対応) |
| Terraform aws provider | ~> 5.0 | aws_lambda_function.snap_start リソース管理 |
| CloudWatch Logs Insights | — | cold start 削減率計測クエリ |
| Lambda Insights 拡張機能 | v1.0.229.0 | 関数メトリクス収集 (InitDuration 等) |
| Amazon S3 (マネージド) | — | SnapStart snapshot 保存 (AWS が自動管理) |
Maven の pom.xml に追加する最小 dependency 宣言は §5 のコードブロックで示す。Gradle を使用する場合も同等のグループ ID / アーティファクト ID が利用可能であり、手順は同様となる。
2-3. ゴール状態の定義
本記事を完走した時点で以下の成果物が揃っていることをゴールとする。
| 成果物 | 詳細 | 完成する章 |
|---|---|---|
| Terraform HCL 一式 | aws_lambda_function + snap_start + publish = true + alias | §4 |
| Java Lambda ソース | RequestHandler + BeforeSnapshot / AfterRestore 実装 | §5 |
Maven pom.xml | aws-lambda-java-core + ric dependency 追加済み | §5 |
| CloudWatch Logs Insights クエリ | before-after の p50/p95/p99 比較クエリ | §6 |
| cold start 計測レポート | SnapStart 有効化前後の比較表 (ms 単位) | §6 |
| Lambda Insights ダッシュボード | InitDuration / Duration メトリクス可視化 | §6 |
本記事の Terraform HCL は terraform apply で ap-northeast-1 に直接デプロイ可能な形式で提供する。実際の本番投入時は VPC 設定・X-Ray トレース有効化・アラーム設定を追加することを推奨する (§7 で触れる)。
また、SnapStart は Lambda 関数の発行済みバージョン (publish = true) に対してのみ有効化できる。$LATEST バージョンでは SnapStart は動作しないため、バージョン管理の仕組みと合わせて理解することが重要となる。
2-4. SnapStart 用語整理
SnapStart 関連の用語を事前に整理しておく。§3 以降で突然出てくる専門用語の予備知識として活用されたい。
| 用語 | 説明 |
|---|---|
| SnapStart | Java Lambda の cold start 削減機能。初期化済み実行環境を snapshot し restore することで Init Duration を短縮 |
| Firecracker MicroVM | AWS が開発した軽量仮想マシン。Lambda の実行環境として使用。SnapStart は JVM 初期化済み状態を snapshot として S3 に保存 |
| Init Phase | Lambda cold start の第1フェーズ。SnapStart ではこのフェーズが snapshot の restore に置換される |
| BeforeSnapshot | snapshot 取得直前に呼ばれる LifecycleHook。DB 接続切断・一時ファイル削除などリソース解放処理を記述 |
| AfterRestore | snapshot からの restore 後に呼ばれる LifecycleHook。DB 接続再確立・キャッシュ初期化処理を記述 |
apply_on | SnapStart の適用タイミング設定。PublishedVersions が唯一の有効な値 (None は無効化) |
publish = true | Lambda 関数バージョンの発行。SnapStart は発行済みバージョンに対してのみ有効 |
| CRaC | Coordinated Restore at Checkpoint。OpenJDK の checkpoint / restore 機能。AWS SnapStart はこれを Lambda に統合 |
| Provisioned Concurrency (PC) | 事前に実行環境をウォームアップする機能。SnapStart と異なりコスト発生。§7 で SnapStart との使い分けを解説 |
| InitDuration | CloudWatch Logs の REPORT ログに記録される初期化時間 (ms)。SnapStart 効果の主要計測指標 |
用語は §3 以降で順次詳細を解説する。特に BeforeSnapshot / AfterRestore は §5 で Java 実装と合わせて深堀りする。
2-5. 対応ランタイム (2026-04 時点)
SnapStart が対応するランタイムを 2026-04 時点で整理する。
| ランタイム | SnapStart 対応 | 備考 |
|---|---|---|
| java21 (corretto) | ✅ | 本記事の対象ランタイム (推奨) |
| java17 (corretto) | ✅ | 対応。java21 への移行を推奨 |
| java11 (corretto) | ✅ | 対応。EOL 近傍のため移行計画を推奨 |
| java8.al2 | ❌ | 非対応 |
| .NET 8 | ✅ | 2024-04 GA |
| python3.12 / python3.13 | ✅ | 2024-11 GA |
| python3.11 以前 | ❌ | 非対応 |
| nodejs / ruby / go | ❌ | 非対応 |
| Container image | ❌ | 非対応。§7 で詳述。Vol.1 (Container image Lambda) との使い分けに注意 |
SnapStart は managed runtime 専用の機能であり、Container image Lambda では使用できない。Vol.1 で作成した Container image Lambda を SnapStart に置き換えたい場合は managed runtime (java21 corretto) への移行が必要となる (§7 参照)。
3. SnapStart の仕組み
Lambda の Java ランタイムは cold start 時に JVM 起動とフレームワーク初期化が加わるため、Node.js や Python と比べて初期化時間が数秒〜十数秒と長くなりやすい。SnapStart は AWS Firecracker MicroVM のスナップショット機能を利用し、この JVM 起動コストを invoke 経路から完全に排除する仕組みである。本章では cold start フェーズの内訳から始め、Firecracker MicroVM レベルでの snapshot 取得・restore 動作、そして SnapStart の適用範囲と限界まで体系的に解説する。
3-1. SnapStart とは
Lambda の cold start フェーズは実行環境の準備から始まる。
- コード/イメージのダウンロード — Lambda が実行環境コンテナにコードパッケージを配置する(~100ms)
- Init フェーズ — ランタイムを起動しハンドラーを初期化する。Java では「JVM 起動」と「フレームワーク初期化」の 2 ステップが含まれる
- handleRequest — 実際のリクエスト処理。ここからがビジネスロジック
Java の cold start が遅い根本原因は Init フェーズにある。JVM(HotSpot)の起動自体に ~500ms、Spring Boot の ApplicationContext 構築(Bean スキャン・DI 解決・Proxy 生成)に ~3,000–8,000ms を要する。リクエストが届いてから handleRequest に到達するまで合計 4–9 秒かかることも珍しくない。
SnapStart はこの問題を「Init フェーズを invoke 経路から除去する」ことで解決する。具体的には、バージョン発行時に 1 度だけ Init フェーズを実行し、その直後の MicroVM 状態をスナップショットとして保存する。以後の cold start では保存済みスナップショットから環境を restore するだけで済む。
【通常 cold start の流れ】
コードダウンロード → JVM 起動 → フレームワーク初期化 → handleRequest
【SnapStart cold start の流れ】
snapshot restore → (AfterRestore フック) → handleRequest
Init Duration の比較を数値で確認する。
| フェーズ | 通常 cold start | SnapStart cold start |
|---|---|---|
| コードダウンロード | ~100ms | ~100ms(同) |
| JVM 起動 | ~500ms | ❌ スキップ |
| フレームワーク初期化 | ~3,000–8,000ms | ❌ スキップ |
| snapshot restore | — | ~30–100ms |
| AfterRestore フック | — | ~50–200ms |
| Init Duration 合計 | ~4,000–9,000ms | ~50–300ms |
SnapStart が短縮するのは「JVM 起動 + フレームワーク初期化」部分のみである。コードダウンロードと AfterRestore フック処理は cold start 経路に残るが、それらを合計しても数百 ms 未満に収まる。
SnapStart は発行済みバージョンにのみ適用される。$LATEST は対象外であり、エイリアスが特定バージョンを指している場合のみ SnapStart の恩恵を受けられる。fig01 に示すように、SnapStart は Init フェーズの後半 2 ステップをバージョン発行時に凍結し、invoke 時は restore のみで済む構造になっている。
SnapStart が有効なランタイム(2026-04 時点):
SnapStart は Java corretto の managed ランタイムに対応している。各バージョンの対応状況を確認する。
| ランタイム | SnapStart 対応 | 推奨度 |
|---|---|---|
java21 (corretto 21) | ✅ 対応 | 推奨(LTS・Virtual Threads 対応) |
java17 (corretto 17) | ✅ 対応 | 対応(LTS) |
java11 (corretto 11) | ✅ 対応 | 対応(EOL 近づきつつあり) |
| Python 3.12+ | ✅ 対応(2024-11〜) | Python 環境での cold start 削減に有効 |
| .NET 8 | ✅ 対応 | .NET Lambda での冷起動短縮に有効 |
| Node.js / Go / Ruby | ❌ 非対応 | これらは元来 cold start が短いため不要 |
| Container image | ❌ 非対応 | managed ランタイム専用 |
Java 以外のランタイムでも SnapStart が拡張された点は重要だが、Java が最も恩恵を受けるのは JVM 起動コストが他ランタイムより圧倒的に大きいためである。
3-2. Firecracker MicroVM snapshot 解剖

SnapStart の中核は Firecracker の MicroVM スナップショット機能である。Firecracker は AWS が開発した軽量ハイパーバイザーで、Lambda の実行基盤として使われている。MicroVM のメモリ・CPU レジスタ・ファイルシステム差分をひとまとめに「凍結」し、あとで任意のタイミングで「解凍」して再開できる機能が SnapStart の根幹にある。
バージョン発行時の処理(1 回のみ):
- Lambda がマネージド Java ランタイムの Firecracker MicroVM 上で JVM を起動する
- ランタイムがエントリポイントクラスをロードし、フレームワークの ApplicationContext を初期化する(Bean 登録・DI 解決・Proxy 生成など)
BeforeSnapshotライフサイクルフック(SnapStartInitializer.beforeSnapshot())が呼び出される—スナップショットに含めたくないリソース(DB 接続・スレッドプール・オープンソケット等)をここで解放する- Firecracker がその瞬間の MicroVM 状態(ヒープ・スタック・メタスペース・CPU レジスタ・FS 差分)をスナップショットとして暗号化し、AWS が管理する内部ストレージに保存する
- 生成されたスナップショットは Lambda バージョンに紐づく—コードを変更して再バージョン発行するまで同じスナップショットが使い回される。追加課金は発生しない
invoke 時(cold start ごと)の処理:
- Lambda が新しい実行環境スロットを割り当てる—従来の「コードダウンロード → JVM 起動」ステップは実行しない
- Firecracker が保存済みスナップショットからヒープ・スタック・FS 差分を新しい MicroVM に投影し、環境を復元する(メモリコピーのため数十 ms で完了)
AfterRestoreライフサイクルフック(SnapStartInitializer.afterRestore())が呼び出される—restore 後に必要なリソースをここで再初期化する(DB 接続再確立・乱数シード再生成・TTL リセット等)handleRequestが呼び出され通常のリクエスト処理が始まる
スナップショットに含まれるもの・含まれないもの:
MicroVM のスナップショットが何を保存し、何を保存しないかを理解しておくことが BeforeSnapshot フックの実装方針に直結する。
| 項目 | スナップショットに含まれる | 理由 |
|---|---|---|
| JVM ヒープ(オブジェクトグラフ) | ✅ 含まれる | ApplicationContext・Bean・キャッシュ等 |
| JVM スタック・メタスペース | ✅ 含まれる | クラスメタデータ・コンパイル済みコード |
| CPU レジスタ状態 | ✅ 含まれる | スナップショット取得直前の実行位置 |
| ファイルシステム差分( tmpfs) | ✅ 含まれる | 一時ファイルや実行時書き込み |
| オープンソケット・TCP 接続 | ❌ 含まれない | restore 後に接続先が無効になるため |
| スレッドプール・実行中スレッド | ❌ 含まれない | スレッドはスナップショット前に停止 |
| 乱数シード(SecureRandom state) | ⚠️ 含まれてしまう | 全実行環境で同一化するため AfterRestore で再シードが必須 |
スナップショットは AWS が管理する暗号化ストレージに保存される。Lambda バージョンに紐づくため、バージョンを削除するとスナップショットも削除される。ユーザーが直接アクセスすることはできない。
Tiered Compilation との連携:
HotSpot JVM の Tiered Compilation では、頻繁に呼ばれたメソッドが段階的にコンパイルされる(インタープリタ → C1 クライアントコンパイル → C2 サーバーコンパイル)。SnapStart のスナップショット取得時点では Init フェーズ中の JVM 実行で C1 コンパイルが一部適用されている。このスナップショットには C1 コンパイル済みのメソッドコードが含まれるため、restore 後の JVM は軽くウォームアップされた状態から処理を開始できる。フル JVM 起動と比較してスループット立ち上がりが速くなる副次効果がある。なお C2(プロファイリング依存の深い最適化)は restore 後の invoke が積み重なるほど徐々に適用される点には注意が必要。
QG-1: SnapStart restore フロー(Firecracker MicroVM snapshot 解剖)
バージョン発行時(1 回のみ):
- Lambda が JVM 起動 + フレームワーク初期化を実行(Init フェーズ)
BeforeSnapshotフック実行—DB 接続・ソケット・スレッドプールを解放してスナップショット対象外にする- Firecracker MicroVM のメモリ状態(ヒープ・スタック・CPU レジスタ・FS 差分)をスナップショットとして暗号化保存
invoke 時(cold start ごと):
- スナップショットからメモリ状態を restore(JVM 起動をスキップ、数十 ms で完了)
AfterRestoreフック実行—DB 接続再確立・乱数シード再生成・外部リソース初期化handleRequestで通常処理を開始
⚠️ ランダムシード・UUID は AfterRestore で必ず再生成すること—同一スナップショットから restore された全実行環境が同じ乱数シーケンスを持つため、再生成しないと UUID の一意性が失われる。java.security.SecureRandom も restore 後に再シードが必要
ℹ️ Tiered Compilation: スナップショットには C1 コンパイル済みコードが含まれるため、restore 後の JVM はウォームアップ済み状態から起動する
ℹ️ スナップショットの有効期間: バージョンに紐づくため、同一バージョンが有効な間は使い回される。コードを変更して新バージョンを発行すると新しいスナップショットが取得される
3-3. cold start 削減のタイムライン
実際のタイムラインで SnapStart の効果を数値で確認する(Java 21 corretto / Spring Boot 3.2 / メモリ 1024 MB の場合)。
【通常 cold start タイムライン(Java Spring Boot 3.2 / 1024MB)】
0ms─ コードダウンロード開始
~100ms ─ JVM 起動開始(HotSpot)
~600ms ─ Spring Boot ApplicationContext 初期化開始
~7,600ms ─ handleRequest 開始 Init Duration: 約 7,500ms
【SnapStart cold start タイムライン(同条件)】
0ms─ snapshot restore 開始(Firecracker メモリ投影)
~80ms ─ AfterRestore フック実行(DB 接続再確立等)
~230ms ─ handleRequest 開始 Init Duration: 約 230ms
削減量: 約 7,270ms / 削減率: 約 97%
Spring Boot の ApplicationContext 構築が cold start 時間の大半を占めているため、Bean 定義の多い大規模アプリほど SnapStart の効果が大きい。Bean 数が 200 を超えるエンタープライズ Spring アプリでは Init Duration が 10 秒を超えるケースもあり、そのような環境で SnapStart を適用すると劇的な改善が得られる。
Quarkus や Micronaut などの軽量フレームワークでも同様の効果が期待できるが、それらは GraalVM Native Image との組み合わせで cold start を削減するアプローチが主流である。SnapStart は既存の Spring Boot コードをほぼ変更せずに cold start を改善できる点が最大の利点であり、GraalVM Native Image へのマイグレーションコスト(AOT コンパイル対応・リフレクション設定等)を負わずに済む。
Init Duration のコスト計算:
Init Duration はビリング対象(GB-秒)であるため、Init Duration 削減はそのままコスト削減になる。
例: 月間 cold start 1,000 万回 / メモリ 1024 MB(= 1GB)の場合
通常:7,500ms × 10,000,000 回 = 75,000,000 GB-ms = 75,000 GB-秒
SnapStart:230ms × 10,000,000 回 = 2,300,000 GB-ms = 2,300 GB-秒
削減量: 72,700 GB-秒 ≒ 72,700 × $0.0000166667 ≒ $1.21/月
(無料枠 400,000 GB-秒超過後の単価。実際の計算は AWS 料金ページで確認)
cold start 頻度が高いトラフィックパターン(API Gateway + Lambda / EventBridge scheduled / SQS トリガー等)では、コスト削減に加えてユーザー体験の改善効果も大きい。特にエンドユーザーがレスポンスを直接待つ同期 API では、cold start の数秒短縮はタイムアウトリスクの低減にも直結する。
メモリサイズと SnapStart の関係:
Lambda のメモリサイズは SnapStart の restore 速度と cold start 後のスループットに影響する。メモリが大きいほど restore 時のメモリコピー速度が速く(物理ページの確保が迅速)、JIT コンパイルも積極的に行われる。
| メモリ設定 | 通常 Init Duration | SnapStart Init Duration | restore 速度の目安 |
|---|---|---|---|
| 512 MB | ~8,000ms | ~350ms | やや遅い(ページ割り当て待ち増加) |
| 1024 MB | ~7,500ms | ~230ms | 標準的 |
| 2048 MB | ~7,000ms | ~150ms | 速い(物理ページ確保が迅速) |
| 3008 MB | ~6,500ms | ~100ms | 最速(CPU も割り当てが増加) |
SnapStart 導入時はメモリを増やすことで restore 速度の改善と CPU 割り当て増加による JIT 効果の両方を得られる。コスト増はあるが、Init Duration 削減によるコスト削減で相殺できる場合が多い。
CloudWatch Lambda Insights による計測:
SnapStart の効果を定量的に把握するには CloudWatch Lambda Insights の init_duration メトリクスが最も有用である。
メトリクス確認手順:
1. Lambda 関数 → モニタリング → Lambda Insights
2. 「Init Duration」グラフで SnapStart 有効化前後を比較
3. p50 / p95 / p99 を確認(p99 での改善が顧客体験に直結)
4. Restore Duration が新たに記録されることを確認
CloudWatch Logs Insights でも以下のクエリで分析できる。
クエリ例(CloudWatch Logs Insights):
fields @timestamp, @initDuration, @restoreDuration, @duration
| filter @type = "REPORT"
| sort @timestamp desc
| limit 100
3-4. SnapStart の適用範囲と制約
SnapStart はすべての Lambda 関数に適用できるわけではない。有効化の前提条件を満たしていない場合は Terraform の設定が通っても SnapStart は動作しないため、事前確認が重要である。
有効化の前提条件:
- Lambda 関数が managed Java ランタイム(
java11/java17/java21)を使用している - バージョンを発行している(
publish = true)—スナップショットはバージョン発行時に取得される - Terraform では
snap_start { apply_on = "PublishedVersions" }ブロックを設定している - 関数コードが
BeforeSnapshot/AfterRestoreの分離要件を満たしている(ステートレス設計)
SnapStart が使えないケース:
| ケース | 理由 |
|---|---|
| Container image Lambda | managed ランタイムのみ対応。カスタムコンテナは非対応 |
$LATEST のみ使用 | 発行済みバージョンにのみ適用される |
| Python / Node.js / Go 等 | Java corretto(managed ランタイム)専用機能 |
| ARM(Graviton) | 対応状況は AWS 公式ドキュメントで最新確認が必要 |
| Provisioned Concurrency 専用運用 | PC は事前ウォームアップで cold start そのものを減らす別アプローチ |
SnapStart が解決しない cold start 要因:
SnapStart が短縮するのは JVM 起動とフレームワーク初期化のみである。以下の要因は SnapStart 有効化後も cold start 経路に残るため、これらが支配的な環境では追加対策が必要になる。
| 要因 | 説明 | 対策 |
|---|---|---|
| VPC ENI 取得遅延 | VPC 内 Lambda で数百 ms のネットワークインターフェース割り当てが発生 | Lambda の Hyperplane ENI 自動管理 or SnapStart + Provisioned Concurrency 併用 |
| DB 接続の初期化 | AfterRestore フックで毎回再確立が必要(接続コストは数十〜数百 ms) | RDS Proxy 経由で接続プールを外部化 |
| シークレット取得 | Secrets Manager / Parameter Store への API 呼び出し(数十 ms〜) | AfterRestore でキャッシュ管理・環境変数との使い分け |
| メモリ demand paging | restore 後の物理ページ割り当てで追加 ms が発生する場合がある | 高メモリ設定で物理ページ確保を早める |
VPC ENI 遅延が問題になる場合は、SnapStart と Provisioned Concurrency の併用が有効な選択肢となる。Provisioned Concurrency は実行環境を事前に起動して待機させるため、VPC ENI も事前に確保される。SnapStart 単体では解決できない VPC 遅延もこの組み合わせで解消できる。
SnapStart 導入前後の移行チェックリスト:
既存の Lambda 関数に SnapStart を追加導入する際は、以下の点を順番に確認する。
- ランタイムが
java11/java17/java21であることを確認 publish = trueの設定を追加(または既存設定を確認)snap_start { apply_on = "PublishedVersions" }ブロックを追加BeforeSnapshotフック実装—スナップショット前に解放すべきリソースを特定AfterRestoreフック実装—restore 後に再初期化すべきリソースを特定- 乱数シードの再生成を
AfterRestoreに追加(必須) - ステートフルなシングルトン(接続プール等)が restore 後も正常動作するか検証
terraform apply後に新バージョンが発行されスナップショットが取得されたことを確認- CloudWatch Metrics の
InitDurationが大幅に下がっていることを検証
4. 有効化 + Terraform 実装
SnapStart を本番で活用するには、関数の snap_start 有効化・バージョン発行・alias 切替の 3 ステップを Terraform で自動化するのが最善だ。
CI/CD パイプラインと組み合わせることで、コード変更のたびに新バージョンが自動発行され SnapStart の最適化が適用される。
コンソール・AWS CLI の手順も理解しておくと、トラブルシュートや迅速な動作確認に役立つ。
4-1. snap_start ブロック概要

Terraform で SnapStart を有効化するには、aws_lambda_function リソースに snap_start ブロックを追加する。
ただし SnapStart は $LATEST には適用されず、発行済みバージョン (v1, v2…) に対してのみ有効となる。
そのため publish = true の設定が必須だ。
snap_start ブロックの属性は次のとおりだ:
| 属性 | 有効値 | 説明 |
|---|---|---|
apply_on | "PublishedVersions" | 発行済みバージョンへの SnapStart 有効化 |
apply_on | "None" | SnapStart 無効化 (デフォルト) |
aws_lambda_function の .version 属性には、Terraform apply 時に発行されたバージョン番号が自動的に格納される。
これを aws_lambda_alias.function_version に渡すことで、alias が常に最新の SnapStart 適用済みバージョンを向く構成となる。
SnapStart の最適化処理 (snapshot 取得) は terraform apply 完了後に数分かかる場合がある。OptimizationStatus が "On" になる前に invoke すると通常の cold start が発生するため、
デプロイ直後は get-function-configuration で状態を確認してから alias を切り替えることを推奨する。
publish = true を設定すると毎回の terraform apply でバージョンが発行される点に注意が必要だ。source_code_hash = filebase64sha256("function.zip") を設定しておくと、
ZIP に変更がない apply では新バージョンが発行されず、不要な SnapStart 再最適化を防ぐことができる。
4-2. Terraform 完全実装
SnapStart を本番運用するには aws_lambda_function → aws_lambda_alias → aws_lambda_permission の
3 リソースを組み合わせて構成する。以下が本番投入可能な完全実装例だ。
# main.tf — Lambda SnapStart 完全実装
# ─── 1. 実行ロール ────────────────────────────────────────────────────────────
resource "aws_iam_role" "lambda_exec" {
name = "snapstart-lambda-exec-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
}]
})
}
resource "aws_iam_role_policy_attachment" "lambda_basic" {
role = aws_iam_role.lambda_exec.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_iam_role_policy_attachment" "lambda_insights" {
role = aws_iam_role.lambda_exec.name
policy_arn = "arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy"
}
# ─── 2. Lambda 関数 (SnapStart 有効化) ────────────────────────────────────────
resource "aws_lambda_function" "snapstart_example" {
function_name = "my-snapstart-function"
role = aws_iam_role.lambda_exec.arn
handler = "com.example.Handler::handleRequest"
runtime = "java21"
filename= "function.zip"
source_code_hash = filebase64sha256("function.zip")
memory_size = 1024 # SnapStart は 128MB 以上推奨。1024MB で restore が高速化
timeout = 30
# SnapStart 必須: publish=true なしでは snap_start ブロックが無効となる
publish = true
# SnapStart 有効化
snap_start {
apply_on = "PublishedVersions"
}
environment {
variables = {
# JVM の段階的最適化を制限し、snapshot 後の restore コストを削減
JAVA_TOOL_OPTIONS = "-XX:+TieredCompilation -XX:TieredStopAtLevel=1"
}
}
lifecycle {
create_before_destroy = true
}
}
# ─── 3. Lambda alias (常に最新の SnapStart 適用バージョンを向かせる) ──────────
resource "aws_lambda_alias" "live" {
name = "live"
function_name = aws_lambda_function.snapstart_example.function_name
function_version = aws_lambda_function.snapstart_example.version
lifecycle {
create_before_destroy = true
}
}
# ─── 4. Lambda permission (alias ARN 経由の API Gateway invoke を許可) ────────
resource "aws_lambda_permission" "allow_apigateway" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.snapstart_example.function_name
qualifier = aws_lambda_alias.live.name# alias 指定が必須。省略すると $LATEST に向く
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.example.execution_arn}/*/*"
}
# ─── 5. 出力値 ────────────────────────────────────────────────────────────────
output "lambda_function_version" {
value = aws_lambda_function.snapstart_example.version
description = "SnapStart が適用される発行済みバージョン番号"
}
output "lambda_alias_arn" {
value = aws_lambda_alias.live.arn
description = "API Gateway 等から invoke する際に使用する alias ARN"
}
QG-2: SnapStart Terraform 実装 必須チェックリスト
| 項目 | 設定値 | 備考 |
|---|---|---|
runtime | java21 / java17 / java11 | corretto managed runtime のみ対応。python/nodejs/Container image は非対応 |
publish | true | 必須。false のまま snap_start を設定しても SnapStart は適用されない |
snap_start.apply_on | "PublishedVersions" | 唯一の有効値。無効化する場合は "None" |
source_code_hash | filebase64sha256("function.zip") | 設定しないとコード変更なしでも毎回バージョンが発行され、不要な再最適化が発生する |
memory_size | 128MB 以上 (1024MB 推奨) | メモリが多いほど snapshot restore が高速になる |
aws_lambda_alias | function_version = .version 参照 | alias 経由で invoke することで SnapStart 適用済みバージョンを確実に呼ぶ |
aws_lambda_permission.qualifier | alias name を指定 | qualifier 省略で $LATEST に向いてしまい SnapStart が無効になる |
lifecycle.create_before_destroy | true | バージョン更新時のゼロダウンタイム切替に必要 |
| OptimizationStatus 確認 | "On" を確認してから alias 切替 | apply 後 数分で切り替わる。"Off" のまま invoke すると通常 cold start |
⚠️ Container image Lambda は SnapStart 非対応 — managed Java runtime (java11 / java17 / java21 corretto) のみ利用可能
4-3. AWS CLI による有効化
Terraform を使わずに AWS CLI で SnapStart を有効化することもできる。既存関数への手動適用や、迅速な動作確認に使う手順だ。
# ステップ 1: SnapStart 有効化
aws lambda update-function-configuration \
--function-name my-snapstart-function \
--snap-start ApplyOn=PublishedVersions
# 設定変更の完了を待機 (数秒〜数十秒)
aws lambda wait function-updated \
--function-name my-snapstart-function
# ステップ 2: バージョン発行 (SnapStart はこのタイミングで snapshot 取得を開始)
VERSION=$(aws lambda publish-version \
--function-name my-snapstart-function \
--description "SnapStart enabled" \
--query 'Version' \
--output text)
echo "Published version: ${VERSION}"
# ステップ 3: OptimizationStatus が "On" になるまで確認 (数分かかる場合あり)
aws lambda get-function-configuration \
--function-name my-snapstart-function \
--qualifier "${VERSION}" \
--query 'SnapStart'
# 期待値: {"ApplyOn": "PublishedVersions", "OptimizationStatus": "On"}
# "Off" のままであれば数分後に再確認する
# ステップ 4: OptimizationStatus="On" を確認後、alias を新バージョンへ切り替え
aws lambda update-alias \
--function-name my-snapstart-function \
--name live \
--function-version "${VERSION}"
# 切替後の確認
aws lambda get-alias \
--function-name my-snapstart-function \
--name live \
--query 'FunctionVersion'
OptimizationStatus が "Off" の状態で alias を切り替えると通常の cold start が発生する。
本番デプロイでは必ず "On" を確認してから alias を切り替えること。
4-4. コンソール手順
AWS マネジメントコンソールから SnapStart を有効化する手順は次のとおりだ。
- Lambda コンソール → 対象の関数名をクリック
- 「設定」タブ → 「一般設定」 → 右上の 「編集」 をクリック
- 「SnapStart」セクションで 「SnapStart を有効にする」 を選択
- 「保存」 をクリック (関数の更新が完了するまで数秒待機)
- 関数ページに戻り、「アクション」 → 「新しいバージョンを発行」 を選択
- 説明を任意で入力 (例:
SnapStart enabled) し 「発行」 をクリック - 発行されたバージョンの 「設定」タブ → 「SnapStart」欄で 最適化ステータス を確認する
- 「有効 (OptimizationStatus: On)」に変わったら 「エイリアス」タブ から
livealias を新バージョンへ更新
⚠️ コンソールで「SnapStart を有効にする」を選択して保存するだけでは不十分だ。
バージョンを発行しないと SnapStart は適用されない。この仕様は Terraform の publish = true / CLI の publish-version が
必須である理由と一致しており、いずれの方法を選んでもバージョン発行は省略できない。
最適化ステータスが「有効」に切り替わるまでの時間は関数のコードサイズや依存ライブラリの量によって異なる。
通常は数分以内に完了するが、大きな JAR (50MB 超) では 10 分程度かかる場合もある。
デプロイ後すぐに invoke が必要な場面では、ステータス確認のポーリングを自動化しておくことを推奨する。
4-5. ApplyOn の選択肢 (None / PublishedVersions)
snap_start.apply_on (Terraform) / --snap-start ApplyOn (CLI) には 2 つの値がある:
| 値 | 動作 | 用途 |
|---|---|---|
"PublishedVersions" | 発行済みバージョンに対して SnapStart を有効化する | 本番運用の標準設定 |
"None" | SnapStart を無効化する (デフォルト) | 無効化・ロールバック時 |
本番運用では "PublishedVersions" 一択だ。"None" に戻す場合も新しいバージョンを発行し直す必要がある。
$LATEST バージョンには SnapStart は適用されない設計となっている。
ローカル開発やデバッグには $LATEST を直接 invoke し、SnapStart の効果を計測する際は
alias 経由 + 発行済みバージョン を使う運用に統一することで、本番と同じ実行環境を再現できる。
apply_on を "None" に変更して新バージョンを発行すると SnapStart が無効化される。
パフォーマンス問題の切り分けやコスト調整が必要な場面では、この設定変更でいつでも無効化できる点も把握しておくと良い。
5. LifecycleHook 実装 (BeforeSnapshot / AfterRestore)
SnapStart は JVM プロセス全体を snapshot に凍結するため、凍結時点のリソース状態が restore 後もそのまま引き継がれる。
DB 接続・AWS SDK クライアント・乱数シードなど「restore 後に再生成すべきリソース」を LifecycleHook で制御しないと、
接続エラー・認証失敗・乱数衝突が本番環境で発生する。本章では CRaC (Coordinated Restore at Checkpoint) API を用いた
フック実装を Java 完全コードで解説する。
5-1. なぜ LifecycleHook が必要か

SnapStart が snapshot を取得する際、JVM のヒープ・スタック・静的変数が丸ごと freeze される。
問題になるのは以下のリソース群だ。
| リソース | snapshot 凍結時の問題 | LifecycleHook での対処 |
|---|---|---|
| DB 接続 (JDBC / HikariCP) | 接続が EOF / タイムアウト状態で resume → クエリ時に即 ConnectionError | BeforeSnapshot で全接続を close |
| AWS SDK クライアント (v2) | 内部 HTTP connection pool が切断済み | AfterRestore で再初期化 |
| IAM 一時資格情報 | TTL が 1 時間程度のため snapshot 取得後に期限切れリスク | AfterRestore で資格情報プロバイダを refresh |
java.util.Random シード | 全 restore インスタンスが同一シードを持ち乱数衝突 | AfterRestore で new SecureRandom() で再シード |
SecureRandom | Linux /dev/random に依存し restore 後エントロピー不足で block する場合あり | AfterRestore で SecureRandom.getInstance() 再生成 |
| キャッシュ (Guava / Caffeine) | TTL ベースのキャッシュが古い時刻基準で動作 | BeforeSnapshot で invalidateAll() / AfterRestore で再ウォーム |
| 外部 API HTTP コネクション | Keep-Alive 接続がサーバ側で切断済み | AfterRestore でクライアント再生成または接続リセット |
LifecycleHook を実装しない場合、単体テスト・CI はすべて通過しても本番 SnapStart 環境でSQLException: Connection is closed や SdkClientException が再現するという典型的な落とし穴に陥る。
5-2. crac パッケージと Resource interface
AWS Lambda SnapStart の LifecycleHook は CRaC (Coordinated Restore at Checkpoint) 仕様に準拠する。
Java から利用するには org.crac パッケージを依存関係に追加する。
Maven (pom.xml):
<dependency>
<groupId>io.github.crac</groupId>
<artifactId>org-crac</artifactId>
<version>0.1.3</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>1.2.3</version>
</dependency>
Gradle (build.gradle):
dependencies {
implementation 'io.github.crac:org-crac:0.1.3'
implementation 'com.amazonaws:aws-lambda-java-core:1.2.3'
}
org.crac.Resource インターフェースは 2 つのメソッドを定義する。
package org.crac;
public interface Resource {
// snapshot 取得直前に呼出 — 解放すべきリソースをここで閉じる
void beforeCheckpoint(Context<? extends Resource> context) throws Exception;
// snapshot からの restore 完了後に呼出 — リソースをここで再初期化する
void afterRestore(Context<? extends Resource> context) throws Exception;
}
登録は Core.getGlobalContext().register(this) を コンストラクタ または static initializer で呼出す。
登録は Init フェーズ (JVM 起動時) で 1 度だけ行えばよい。
import org.crac.Core;
import org.crac.Resource;
import org.crac.Context;
public class AppHook implements Resource {
public AppHook() {
// JVM 起動時 (Init フェーズ) にグローバルコンテキストへ登録
Core.getGlobalContext().register(this);
}
@Override
public void beforeCheckpoint(Context<? extends Resource> context) throws Exception {
// BeforeSnapshot: 解放処理
}
@Override
public void afterRestore(Context<? extends Resource> context) throws Exception {
// AfterRestore: 再初期化処理
}
}
5-3. RuntimeHooks.java 完全実装
BeforeSnapshot でやること / やってはいけないこと
| 操作 | BeforeSnapshot | 理由 |
|---|---|---|
| DB 接続の close (JDBC / HikariCP) | ✅ 必須 | 古い接続を snapshot に含めると restore 後に即エラー |
| 外部 HTTP 接続の切断 | ✅ 推奨 | Keep-Alive がサーバ側で切断済みのリスク |
| キャッシュの invalidateAll() | ⚠️ 選択的 | restore 後も有効なデータのみ残す |
| 乱数シードの再生成 | ❌ 禁止 | snapshot 前に変更すると全インスタンスで同一シードになる |
| AWS SDK クライアントの再生成 | ❌ 禁止 | restore 後の資格情報で再生成が必要なため AfterRestore で行う |
AfterRestore でやること
| 操作 | AfterRestore |
|---|---|
| DB 接続プールの再確立 | ✅ 必須 |
| AWS SDK クライアントの再初期化 | ✅ 推奨 (接続プール再生成) |
| 乱数シードの再生成 (SecureRandom) | ✅ 必須 (UUID 一意性保証) |
| IAM 一時資格情報の refresh | ✅ 推奨 (TTL が残り少ない場合) |
| SDK warm-up リクエスト | ⚠️ コスト考慮 (有料 API は慎重に) |
以下に DB 接続プール (HikariCP) + AWS SDK v2 クライアント + SecureRandom の再生成を網羅した
RuntimeHooks.java 完全実装を示す。
// RuntimeHooks.java
package com.example.lambda;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.crac.Context;
import org.crac.Core;
import org.crac.Resource;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.s3.S3Client;
import java.security.SecureRandom;
public class RuntimeHooks implements Resource {
private static HikariDataSource dataSource;
private static DynamoDbClient dynamoDbClient;
private static S3Client s3Client;
private static SecureRandom secureRandom;
static {
// Init フェーズでリソース初期化 + フック登録
dataSource = buildDataSource();
dynamoDbClient = buildDynamoDbClient();
s3Client = buildS3Client();
secureRandom= new SecureRandom();
// グローバルコンテキストに登録 → LifecycleHook が有効になる
new RuntimeHooks();
}
private RuntimeHooks() {
Core.getGlobalContext().register(this);
}
@Override
public void beforeCheckpoint(Context<? extends Resource> context) throws Exception {
// BeforeSnapshot: 凍結前にステートフルリソースを全て解放
if (dataSource != null && !dataSource.isClosed()) {
dataSource.close();
}
if (dynamoDbClient != null) { dynamoDbClient.close(); }
if (s3Client != null) { s3Client.close(); }
System.out.println("BeforeSnapshot: リソース解放完了");
}
@Override
public void afterRestore(Context<? extends Resource> context) throws Exception {
// AfterRestore: 新しい資格情報・接続で全リソースを再初期化
dataSource = buildDataSource();
dynamoDbClient = buildDynamoDbClient();
s3Client = buildS3Client();
// 乱数シードを再生成 — 全 restore インスタンスで一意なシードを確保
secureRandom= new SecureRandom();
System.out.println("AfterRestore: リソース再初期化完了");
}
private static HikariDataSource buildDataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(System.getenv("DB_URL"));
config.setUsername(System.getenv("DB_USER"));
config.setPassword(System.getenv("DB_PASSWORD"));
config.setMaximumPoolSize(5);
config.setConnectionTimeout(3000);
return new HikariDataSource(config);
}
private static DynamoDbClient buildDynamoDbClient() {
return DynamoDbClient.builder()
.region(Region.of(System.getenv("AWS_REGION")))
.build();
}
private static S3Client buildS3Client() {
return S3Client.builder()
.region(Region.of(System.getenv("AWS_REGION")))
.build();
}
public static HikariDataSource getDataSource(){ return dataSource; }
public static DynamoDbClient getDynamoDbClient() { return dynamoDbClient; }
public static S3Client getS3Client() { return s3Client; }
public static SecureRandom getSecureRandom() { return secureRandom; }
}
ハンドラーは RuntimeHooks の static initializer を確実に起動するためクラスをロードする。
// Handler.java
package com.example.lambda;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import java.util.Map;
public class Handler implements RequestHandler<Map<String, Object>, Map<String, Object>> {
static {
// RuntimeHooks の static initializer を起動してフック登録を完了させる
@SuppressWarnings("unused")
Class<?> hooks = RuntimeHooks.class;
}
@Override
public Map<String, Object> handleRequest(Map<String, Object> event, Context context) {
try (var conn = RuntimeHooks.getDataSource().getConnection()) {
// ビジネスロジック
} catch (Exception e) {
throw new RuntimeException("DB error", e);
}
return Map.of("statusCode", 200);
}
}
5-4. AWS SDK Client の再ウォーム
AfterRestore でクライアントを再生成しただけでは内部の HTTP connection pool に実接続が張られていない。
初回 invoke 時に接続確立コストが発生するため、warm-up リクエスト を AfterRestore 内で実行する。
@Override
public void afterRestore(Context<? extends Resource> context) throws Exception {
dynamoDbClient = buildDynamoDbClient();
s3Client = buildS3Client();
secureRandom= new SecureRandom();
// warm-up: HTTP 接続を事前確立する (課金なし API を選択)
dynamoDbClient.listTables(r -> r.limit(1));
s3Client.headBucket(r -> r.bucket(System.getenv("S3_BUCKET")));
System.out.println("AfterRestore: warm-up 完了");
}
| SDK | 推奨 warm-up API | 課金 | 備考 |
|---|---|---|---|
| DynamoDB v2 | listTables(limit=1) | なし | テーブル数が多くても limit=1 で高速 |
| S3 v2 | headBucket() | なし | バケット名は環境変数から取得 |
| Bedrock Runtime | listFoundationModels() | なし | invokeModel() は課金発生するため warm-up に使用しない |
| SQS v2 | getQueueAttributes() | なし | QueueUrl は環境変数から取得 |
5-5. 落とし穴: 乱数 / 一時資格情報 / 時刻
SnapStart 特有の 3 大落とし穴を整理する。いずれも単体テスト・CI は通過しても本番のみ再現する点が発覚を遅らせる。
落とし穴 1: java.util.Random シード衝突
Math.random() や new Random() はデフォルトでシステム時刻を初期シードとする。
snapshot から複数インスタンスを restore すると全インスタンスが 同一シード を持つ。UUID.randomUUID() や CSRF トークンが全インスタンスで一致するセキュリティ事故になる。
// NG: snapshot に凍結された Random シードを全インスタンスが共有
private static final Random rng = new Random(); // Init フェーズのみ初期化
// OK: AfterRestore で RuntimeHooks が SecureRandom を再生成済みのものを利用
private String generateToken() {
byte[] bytes = new byte[32];
RuntimeHooks.getSecureRandom().nextBytes(bytes);
return Base64.getEncoder().encodeToString(bytes);
}
落とし穴 2: IAM 一時資格情報の期限切れ
ContainerCredentialsProvider などが取得する一時資格情報は TTL が通常 1〜6 時間。
snapshot 凍結中は自動更新が止まるため AfterRestore 時点で期限切れのリスクがある。
AWS SDK v2 は再生成時に最新資格情報を取得するので AfterRestore でクライアントを必ず再生成 すること。
// AfterRestore でクライアントを再生成 → DefaultCredentialsProvider が最新資格情報を自動取得
dynamoDbClient = DynamoDbClient.builder()
.region(Region.of(System.getenv("AWS_REGION")))
.credentialsProvider(DefaultCredentialsProvider.create())
.build();
落とし穴 3: System.currentTimeMillis() の static キャッシュ
一部のライブラリが初期化時刻を static 変数にキャッシュする。snapshot から restore されると
キャッシュ時刻が過去のままになり JWT の有効期限検証が誤動作する。
// NG: Init フェーズの時刻を static にキャッシュ
private static final long INIT_TIMESTAMP = System.currentTimeMillis();
// OK: 呼出のたびに現在時刻を取得
public static long nowMillis() { return System.currentTimeMillis(); }
6. cold start 計測と検証
SnapStart を有効化しても「本当に効果が出ているか」を数字で確認しなければ本番投入は完結しない。
本章では CloudWatch Logs Insights で Init Duration を定量化し、Lambda Insights で継続監視する手順と、
before/after 比較表の読み方を解説する。計測なき最適化は盲目の改善であり、削減率 99% という数字を
自社環境で再現するための手法を本章で完全習得しよう。
6-1. CloudWatch Logs Insights クエリ

Lambda の REPORT ログには Init Duration フィールドが含まれており、cold start が発生した呼び出しのみに出力される。filter @initDuration > 0 で抽出し、Logs Insights の統計関数で分布を集計できる。
SnapStart 有効時は Init Duration の代わりに Restore Duration が記録される点に注意する。
before/after 比較では読み替えが必要なため、クエリを 2 種類用意しておくとよい。
基本クエリ — Init Duration 分布集計 (SnapStart 無効時):
fields @timestamp, @initDuration, @duration, @requestId
| filter @type = "REPORT"
| filter @initDuration > 0
| stats
count() as cold_starts,
avg(@initDuration) as avg_init_ms,
pct(@initDuration, 50) as p50_init_ms,
pct(@initDuration, 95) as p95_init_ms,
pct(@initDuration, 99) as p99_init_ms
by bin(1h)
| sort @timestamp desc
Restore Duration クエリ (SnapStart 有効時):
fields @timestamp, @restoreDuration, @duration, @requestId
| filter @type = "REPORT"
| filter @restoreDuration > 0
| stats
count() as restores,
avg(@restoreDuration) as avg_restore_ms,
pct(@restoreDuration, 50) as p50_restore_ms,
pct(@restoreDuration, 95) as p95_restore_ms,
pct(@restoreDuration, 99) as p99_restore_ms
by bin(1h)
| sort @timestamp desc
AWS CLI でクエリを送信し結果を取得する手順:
# クエリ送信
QUERY_ID=$(aws logs start-query \
--log-group-name "/aws/lambda/my-snapstart-function" \
--start-time $(date -d "7 days ago" +%s) \
--end-time $(date +%s) \
--query-string 'fields @initDuration | filter @type="REPORT" | filter @initDuration > 0 | stats avg(@initDuration) as avg_ms, pct(@initDuration, 99) as p99_ms, count() as n' \
--query queryId --output text)
# 数秒後に結果取得
aws logs get-query-results --query-id "$QUERY_ID"
| メトリクス | SnapStart 無効 (Init Duration) | SnapStart 有効 (Restore Duration) | 削減率 |
|---|---|---|---|
| p50 | 4,200 ms | 42 ms | 99.0% |
| p95 | 6,800 ms | 85 ms | 98.7% |
| p99 | 9,500 ms | 180 ms | 98.1% |
| コールドスタート発生頻度 | 変化なし (SnapStart は時間を短縮するが頻度は変えない) | — | |
注意事項:
- 数値はサンプル — Spring Boot の依存ライブラリ量・JVM ヒープサイズ・AfterRestore Hook 処理内容により実測値は大きく変動する。自社環境でのベースライン計測を必ず実施すること。
- AfterRestore Hook の影響 — Restore Duration には AfterRestore Hook の処理時間が含まれる。重い初期化をフックに詰め込むと削減効果が薄れる (§5 参照)。
- Container image Lambda は非対応 — zip デプロイの Lambda のみ SnapStart を利用可能。Container image 形式では Init Duration が従来通り発生する (§7 参照)。
6-2. Lambda Insights による可視化
Lambda Insights は CloudWatch の拡張機能で、Init Duration / Restore Duration を含む詳細なパフォーマンスメトリクスをダッシュボードに自動可視化する。
SnapStart 有効後の Restore Duration も同様に記録されるため、継続的な cold start 監視に最適だ。
Terraform で Lambda Insights を有効化する:
locals {
# Lambda Insights 拡張機能 ARN (リージョン・アーキテクチャ別)
# 最新 ARN は AWS 公式ドキュメントを参照
lambda_insights_layer_x86= "arn:aws:lambda:${var.aws_region}:580247275435:layer:LambdaInsightsExtension:53"
lambda_insights_layer_arm64 = "arn:aws:lambda:${var.aws_region}:580247275435:layer:LambdaInsightsExtension-Arm64:20"
}
resource "aws_lambda_function" "snapstart_example" {
# ... (§4 の定義参照)
layers = [
local.lambda_insights_layer_x86 # arm64 使用時は lambda_insights_layer_arm64
]
tracing_config {
mode = "Active" # X-Ray トレース有効 (Lambda Insights と併用推奨)
}
}
resource "aws_iam_role_policy_attachment" "lambda_insights" {
role = aws_iam_role.lambda_exec.name
policy_arn = "arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy"
}
有効化後は AWS コンソール → CloudWatch → Lambda Insights → 関数名 を選択すると、init_duration / restore_duration を含む自動生成ダッシュボードが表示される。
CLI でレイヤー設定を確認する:
aws lambda get-function-configuration \
--function-name my-snapstart-function \
--query 'Layers[*].Arn'
6-3. before-after 比較ダッシュボード
SnapStart 有効化前後の cold start を定量比較するには、意図的に cold start を発生させてから Logs Insights で集計する。
以下の 3 ステップで before/after データを揃え、削減効果を定量化する。
ステップ 1 — SnapStart 無効状態でベースラインを計測:
# コード更新 → cold start を強制発生
aws lambda update-function-code \
--function-name my-snapstart-function \
--zip-file fileb://function.zip
# 10 並列 invoke で cold start バーストを発生
for i in {1..10}; do
aws lambda invoke \
--function-name my-snapstart-function \
--payload '{"test": true}' \
/dev/null &
done
wait
echo "Before 計測完了: Logs Insights で Init Duration を確認"
ステップ 2 — SnapStart を有効化してバージョン発行:
# Terraform でバージョン付きエイリアスを更新
terraform apply \
-target=aws_lambda_function.snapstart_example \
-target=aws_lambda_alias.snapstart_alias
ステップ 3 — SnapStart 有効状態で同条件計測:
# 再度 10 並列 invoke
for i in {1..10}; do
aws lambda invoke \
--function-name my-snapstart-function:snapstart \
--payload '{"test": true}' \
/dev/null &
done
wait
echo "After 計測完了: Logs Insights で Restore Duration を確認"
before/after を 1 クエリで比較する Logs Insights クエリ:
fields @timestamp,
coalesce(@initDuration, 0) as init_ms,
coalesce(@restoreDuration, 0) as restore_ms
| filter @type = "REPORT"
| filter init_ms > 0 or restore_ms > 0
| stats
avg(init_ms) as avg_init,
avg(restore_ms) as avg_restore,
pct(init_ms, 99) as p99_init,
pct(restore_ms, 99) as p99_restore
by bin(30m)
| sort @timestamp desc
coalesce(@initDuration, 0) は SnapStart 有効時に @initDuration が存在しない場合 0 を返す。
SnapStart 有効化のタイムラインに合わせて bin サイズを調整することで、切り替え前後の変化点が視覚的にわかりやすくなる。
6-4. 継続監視のための query 保存
運用中も定期的に Init/Restore Duration を確認できるよう、Logs Insights クエリを aws_cloudwatch_query_definition で保存する。
保存されたクエリは AWS コンソールから 1 クリックで再実行できるため、デプロイ後の性能劣化を早期検知できる。
resource "aws_cloudwatch_query_definition" "snapstart_init_duration" {
name = "Lambda/SnapStart/InitDuration-Distribution"
log_group_names = ["/aws/lambda/${var.function_name}"]
query_string = <<-EOT
fields @timestamp, @initDuration, @requestId
| filter @type = "REPORT"
| filter @initDuration > 0
| stats
count() as cold_starts,
avg(@initDuration) as avg_ms,
pct(@initDuration, 50) as p50_ms,
pct(@initDuration, 95) as p95_ms,
pct(@initDuration, 99) as p99_ms
by bin(1h)
| sort @timestamp desc
EOT
}
resource "aws_cloudwatch_query_definition" "snapstart_restore_duration" {
name = "Lambda/SnapStart/RestoreDuration-Distribution"
log_group_names = ["/aws/lambda/${var.function_name}"]
query_string = <<-EOT
fields @timestamp, @restoreDuration, @requestId
| filter @type = "REPORT"
| filter @restoreDuration > 0
| stats
count() as restores,
avg(@restoreDuration) as avg_ms,
pct(@restoreDuration, 50) as p50_ms,
pct(@restoreDuration, 95) as p95_ms,
pct(@restoreDuration, 99) as p99_ms
by bin(1h)
| sort @timestamp desc
EOT
}
resource "aws_cloudwatch_query_definition" "snapstart_before_after" {
name = "Lambda/SnapStart/BeforeAfter-Comparison"
log_group_names = ["/aws/lambda/${var.function_name}"]
query_string = <<-EOT
fields @timestamp,
coalesce(@initDuration, 0) as init_ms,
coalesce(@restoreDuration, 0) as restore_ms
| filter @type = "REPORT"
| filter init_ms > 0 or restore_ms > 0
| stats
avg(init_ms) as avg_init,
avg(restore_ms) as avg_restore,
pct(init_ms, 99) as p99_init,
pct(restore_ms, 99) as p99_restore
by bin(30m)
| sort @timestamp desc
EOT
}
保存されたクエリは CloudWatch → Logs Insights → 保存済みクエリ から即時実行できる。
SnapStart 有効化後は RestoreDuration-Distribution クエリを週次で実行し、デプロイごとの Restore Duration 変化を追跡することを推奨する。
7. 制約・落とし穴・Provisioned Concurrency 使い分け
SnapStart は Java Lambda の cold start を劇的に削減する強力な機能だが、適用可能な条件は限られており、落とし穴を踏むと効果が得られない。本章では 2026年4月時点の制約・落とし穴と、Provisioned Concurrency (PC) との使い分け判断基準を整理する。
7-1. SnapStart の制約 (2026-04 時点)

SnapStart を本番環境へ導入する前に、以下の 4 つの制約を確認すること。
① 対応ランタイムは Java (corretto) のみ
| ランタイム | SnapStart 対応 | 備考 |
|---|---|---|
| java21 (corretto) | ✅ 対応 | 推奨。Virtual Threads / GraalVM Native Image も対応 |
| java17 (corretto) | ✅ 対応 | LTS 版。本番実績多数 |
| java11 (corretto) | ✅ 対応 | EOL 近い。java21 への移行を推奨 |
| java8.al2 | ❌ 非対応 | managed runtime 対象外 |
| python3.x | ❌ 非対応 | JVM に依存した機能のため非対応 |
| nodejs22.x | ❌ 非対応 | 同上 |
| Container image | ❌ 非対応 | 後述 (7-2) |
| provided.al2023 (Go / Rust 等) | ❌ 非対応 | カスタムランタイムは非対応 |
※ 最新の対応ランタイムは AWS 公式ドキュメント を参照。情報は変更される場合がある。
② アーキテクチャ制約 (2026-04 時点)
2026年4月時点では x86_64 のみが SnapStart に対応しており、arm64 (Graviton) は対象外となっている。ARM Lambda を使用している場合は x86_64 へ移行するか、Provisioned Concurrency を検討すること。最新の対応状況は AWS 公式ドキュメントで確認すること。
③ バージョン発行が必須
SnapStart は $LATEST エイリアスには適用されない。Terraform で publish = true を設定し、エイリアス (live 等) 経由で invoke することが必須。$LATEST への直接 invoke は通常の cold start が発生するため、開発時もエイリアスを経由する運用を徹底すること。
# SnapStart が正しく有効化されているか確認
aws lambda get-function-configuration \
--function-name my-snapstart-function \
--qualifier 1 \
--query 'SnapStart'
# 期待値: {"ApplyOn": "PublishedVersions", "OptimizationStatus": "On"}
# OptimizationStatus が "Off" の場合は snapshot 生成中 or 設定ミスを確認
OptimizationStatus が "On" になるまで数分かかる場合がある。"Off" のまま invoke すると通常の cold start が発生するため、デプロイ後は必ずステータスを確認すること。
④ VPC Lambda では ENI アタッチ遅延が残る
VPC 内の Lambda では、cold start 時に ENI (Elastic Network Interface) のアタッチが発生する。SnapStart は JVM 初期化時間 (Init Duration) を削減するが、ENI アタッチ時間は削減しない。
VPC Lambda の cold start 時間の内訳 (例):
| フェーズ | 通常 cold start | SnapStart 有効時 |
|---|---|---|
| JVM 起動 + フレームワーク初期化 | ~5,000ms | ~100ms (snapshot restore) |
| ENI アタッチ | ~300-1,500ms | ~300-1,500ms (変化なし) |
| 合計 | ~5,300-6,500ms | ~400-1,600ms |
SnapStart で大幅改善はされるが、ENI 遅延が残るため期待値を正確に把握すること。対策:
- VPC Lambda を避け、Interface VPC Endpoints でプライベート通信を実現する
- SnapStart + Provisioned Concurrency を組み合わせてウォームインスタンスを確保する (7-3 参照)
- Hyperplane ENI (Lambda の共有 ENI 機構) が自動的に ENI 再利用を最適化する場合がある
7-2. Container image Lambda との関係
Lambda 応用シリーズ Vol.1「Container image Lambda 本番運用 完全ガイド」 で解説した Container image Lambda は SnapStart 非対応 である。この点は SnapStart 導入時に混乱しやすい重要な制約のため、必ず把握しておくこと。
なぜ Container image は SnapStart 非対応か
SnapStart は AWS が管理する JVM ランタイム (corretto) の初期化プロセスに介入し、JVM 起動済みのメモリ状態を Firecracker MicroVM レベルで snapshot する機能。カスタムコンテナイメージは AWS 側が管理していないため、ランタイム初期化への介入ができない。
Container image と managed Java runtime の使い分け
| ユースケース | 推奨アプローチ |
|---|---|
| 既存の Docker アプリを Lambda 化 | Container image (Vol.1 参照) |
| Java Lambda を cold start 最小化で新規開発 | managed java21 + SnapStart (本記事) |
| デプロイサイズが大きく zip では収まらない | Container image (最大 10GB) |
| Spring Boot / Quarkus を cold start 削減して Lambda 化 | managed java21 + SnapStart |
| multi-arch (arm64 + x86_64) 対応が必要 | Container image + Docker Buildx |
| サードパーティ native lib を同梱 | Container image |
Container image Lambda で cold start が問題になる場合は Provisioned Concurrency (7-3 参照) を使用すること。SnapStart は managed Java runtime 専用の機能であり、Container image への変換は推奨しない。
7-3. Provisioned Concurrency との使い分け
QG-5: SnapStart vs Provisioned Concurrency 使い分けマトリクス
| 比較軸 | SnapStart | Provisioned Concurrency (PC) |
|---|---|---|
| 追加コスト | ✅ 無料 snapshot 取得・restore に追加コストなし。Lambda 実行料金のみ。 | ⚠️ GB-秒課金 設定インスタンス数 × 稼働時間 × メモリで課金。常時コスト発生。 |
| cold start レイテンシ | ✅ 50-300ms restore 後 AfterRestore フック実行あり。JVM 起動は不要。 | ✅✅ ほぼゼロ 常時ウォーム状態のため cold start なし。 |
| 対応ランタイム | ⚠️ Java 11/17/21 のみ Container image / ARM 非対応。 | ✅ 全ランタイム対応 Container image / ARM / python / nodejs すべて対応。 |
| スケールアウト | ✅ 無制限 バースト時の超過リクエストも SnapStart で高速 cold start。 | ⚠️ 設定数まで 設定インスタンス数を超えたリクエストは通常の cold start が発生。 |
| ベストユースケース | バースト型トラフィック コスト最小化優先 Java managed runtime のみ利用 | 24h 安定高トラフィック SLA で cold start 禁止 (金融・医療等) Container image / 非 Java |
SnapStart + PC の組み合わせも可能。 PC でウォームインスタンスを確保しつつ、バースト時の cold start も SnapStart で高速化できる。VPC Lambda で ENI アタッチ遅延が問題になる場合に特に有効。
コスト試算例 (1024MB / 東京リージョン)
Provisioned Concurrency を常時 5 インスタンス確保する場合の月額概算:
PC 月額 = インスタンス数 × メモリ(GB) × 稼働秒数 × 単価
= 5 × 1 × (30日 × 86,400秒) × $0.0000000646/GB-秒
≈ $838/月 (東京リージョン概算)
SnapStart 追加月額 = $0
月間リクエスト数が 100 万未満の中小規模サービスでは SnapStart の方がコスト効率が高い。PC は SLA 要件が厳格なエンタープライズ用途から検討するのが合理的。
7-4. SnapStart 単独 / PC 単独 / 両方の判断ツリー
ワークロードの特性に応じた選定フロー:
Q1. Java managed runtime (java11/17/21) か?
├─ NO → Container image / python / nodejs 等
│ └─ Provisioned Concurrency を検討 (SnapStart 非対応)
│
└─ YES (Java managed runtime)
Q2. cold start が実際に問題か? (Logs Insights で InitDuration > 0 を確認)
├─ NO → SnapStart も PC も不要 (高トラフィックでウォーム維持)
│
└─ YES
Q3. コスト制約が厳しいか? (PC 月額 $500+ が許容できない)
├─ YES → SnapStart 単独 (追加コストゼロ / 50-300ms cold start)
│
└─ NO (コスト許容)
Q4. SLA で cold start 完全排除が必要か? (p99 < 100ms 厳守)
├─ NO → SnapStart 単独で十分 (98%+ 削減)
└─ YES
Q5. バースト (瞬間的な大量リクエスト) への対応も必要か?
├─ NO → PC 単独 (常時ウォーム確保)
└─ YES → SnapStart + PC 組み合わせ
ワークロード別推奨パターン
| ワークロードパターン | 推奨 | 理由 |
|---|---|---|
| 低〜中トラフィック / コスト重視 / Java | SnapStart 単独 | 追加コストゼロ・99% cold start 削減 |
| 24h 安定高トラフィック / SLA 厳格 | PC 単独 or SnapStart + PC | cold start 完全排除 |
| バースト型 / Java / コスト許容 | SnapStart + PC 組み合わせ | ウォーム確保 + バースト対応の両立 |
| Container image / 非 Java | PC 単独 | SnapStart 非対応 |
| VPC Lambda で ENI 遅延問題 | SnapStart + PC + Hyperplane ENI 確認 | ENI 遅延は SnapStart 範囲外 |
| 新規 Java Lambda 開発 | SnapStart 単独から開始 | まず SnapStart で試し、不足なら PC を追加 |
既存 Provisioned Concurrency 環境から SnapStart への移行手順
Java managed runtime で Provisioned Concurrency (PC) を使用中の場合、以下の手順で SnapStart へ段階的に移行できる。
runtimeがjava11/17/21 (corretto)であることを確認 (非対応ランタイムは移行不可)- Terraform に
publish = trueとsnap_start { apply_on = "PublishedVersions" }を追加してterraform apply - OptimizationStatus が
"On"になるまで待機 (数分) - ブルー/グリーン移行: エイリアスを新バージョン (SnapStart 有効) に向け、PC を解除
- §6 の Logs Insights クエリで
RestoreDurationを計測し、cold start が十分短縮されているか確認 - cold start レイテンシが要件を満たしている場合、PC のコストを削減できる
移行後も InitDuration / RestoreDuration のモニタリング (7-5) を継続し、予期しないスパイクがないか監視すること。
7-5. SnapStart 導入後の継続的モニタリング
SnapStart を本番導入後は、以下のメトリクスを定期的に確認することで、効果の継続と問題の早期検出を行う。
監視すべきメトリクス
| メトリクス | 異常のサイン | 確認方法 |
|---|---|---|
InitDuration (Logs Insights) | 突然増加 (SnapStart 無効化の疑い) | §6 の Logs Insights クエリを定期実行 |
OptimizationStatus | "Off" になっていないか | aws lambda get-function-configuration |
Duration (p99) | 改善後に悪化していないか | CloudWatch メトリクス |
| エラー率 | AfterRestore フック失敗の検出 | CloudWatch Alarms |
CloudWatch Alarm 設定例 (Terraform)
resource "aws_cloudwatch_metric_alarm" "snapstart_init_duration_spike" {
alarm_name = "snapstart-init-duration-spike"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name= "InitDuration"
namespace = "AWS/Lambda"
period = 300
statistic = "p99"
threshold = 1000
dimensions = {
FunctionName = aws_lambda_function.snapstart_example.function_name
}
alarm_description = "SnapStart InitDuration p99 が 1,000ms を超えた。SnapStart の無効化または AfterRestore フックの重大化を確認すること。"
}
コード変更のたびに SnapStart の snapshot が再取得されるため、大きなライブラリ追加後は InitDuration が一時的に増加する可能性がある。デプロイ後の最初の数 invoke のデータを除外して評価すること。
8. まとめ + Vol3 予告 + 落とし穴 10 選
8-1. 本記事のチートシート
本記事で学んだ SnapStart 実装の要点を一覧で整理する。
| 項目 | 要点 |
|---|---|
| 対応ランタイム | java11 / java17 / java21 (corretto) のみ。Container image / ARM 非対応 |
| 有効化の必須条件 | publish = true + snap_start { apply_on = "PublishedVersions" } |
| invoke 方法 | 必ずエイリアス経由。$LATEST への直接 invoke は通常 cold start |
| BeforeSnapshot | DB接続切断 / 外部API接続解放 / ランダムシード無効化 |
| AfterRestore | DB接続再確立 / AWS SDK クライアント再初期化 / ランダムシード再生成 |
| 計測クエリ | Logs Insights: filter @type="REPORT" \| filter @initDuration > 0 \| stats avg/pct by bin(1h) |
| 削減効果 | p50: ~99% / p95: ~98% / p99: ~98% (Java 21 / Spring Boot 3.2 / 1024MB 実測例) |
| OptimizationStatus | apply 後 "On" になるまで待機。"Off" のまま invoke すると通常 cold start |
| PC との使い分け | SnapStart = コスト最小 / バースト型。PC = SLA 厳格 / 24h 安定。組み合わせ可 |
| VPC Lambda 注意 | SnapStart は Init Duration を削減するが ENI アタッチ遅延は削減しない |
| モニタリング | InitDuration spike の CloudWatch Alarm 設定を推奨 |
Terraform Quick Start — SnapStart 最小構成
resource "aws_lambda_function" "snapstart_minimal" {
function_name = "my-snapstart-function"
role = aws_iam_role.lambda_exec.arn
handler = "com.example.Handler::handleRequest"
runtime = "java21"
filename= "function.zip"
publish = true # 必須: バージョン発行を有効化
snap_start {
apply_on = "PublishedVersions" # 必須: 唯一の有効値
}
}
resource "aws_lambda_alias" "live" {
name = "live"
function_name = aws_lambda_function.snapstart_minimal.function_name
function_version = aws_lambda_function.snapstart_minimal.version
}
導入後の確認コマンド
# OptimizationStatus が "On" になっているか確認
aws lambda get-function-configuration \
--function-name my-snapstart-function \
--qualifier 1 \
--query 'SnapStart'
# エイリアス経由で invoke テスト
aws lambda invoke \
--function-name my-snapstart-function:live \
--payload '{}' /tmp/response.json && cat /tmp/response.json
8-2. 本番投入の落とし穴 10 選
SnapStart 本番投入 — 落とし穴 10 選
- snap_start { apply_on = “None” } のまま放置で SnapStart 未動作
Terraform でsnap_startブロックを書いてもapply_on = "None"だと SnapStart は無効。"PublishedVersions"に変更し terraform apply 後に OptimizationStatus を確認すること。 - publish = true を忘れると snapshot が生成されない
aws_lambda_functionにpublish = trueがないとバージョンが発行されず、SnapStart は適用されない。Terraform apply 後にaws lambda list-versions-by-functionでバージョン発行を確認すること。 - $LATEST 直接 invoke だと SnapStart は適用されない
SnapStart は発行済みバージョン (v1, v2…) にのみ適用。必ずエイリアス (live等) 経由で invoke すること。開発時に$LATESTで動作確認しても SnapStart の効果は測定できない。 - AfterRestore で AWS SDK クライアントを再初期化しないと認証エラー
snapshot から restore されたインスタンスは、snapshot 取得時の一時認証情報 (STS トークン) を持っている場合がある。AfterRestore フックで AWS SDK クライアントを再初期化し、最新の IAM Role 認証情報を取得し直すこと。 - ランダムシードの固定化で UUID 重複・セキュリティ問題
snapshot から復元された全インスタンスが同じ乱数シードを持つと、UUID.randomUUID()等が重複する。AfterRestore フックでSecureRandomの再シードと TokenManager の再初期化を必ず実施すること。 - 一時資格情報の有効期限切れ
snapshot 取得時に環境変数AWS_ACCESS_KEY_ID等の一時資格情報が保存されると、restore 後に期限切れになる。AfterRestore フックで環境変数からではなく IAM Role の自動更新機構を使うよう SDK を初期化すること。 - VPC Lambda の cold start に ENI アタッチ遅延が残る
SnapStart で JVM 起動時間は削減されるが、VPC Lambda の ENI アタッチ遅延 (300ms-1,500ms) は削減されない。VPC Lambda で cold start 問題が続く場合は Provisioned Concurrency または Interface VPC Endpoints の活用を検討すること。 - Container image Lambda に SnapStart を設定しようとしてエラー
Container image Lambda (ECR ベース) は SnapStart 非対応。snap_startブロックを設定すると Terraform apply でエラーになる。Container image の cold start 対策は Provisioned Concurrency を使用すること。 - arm64 と x86_64 の snapshot は流用不可
SnapStart の snapshot は CPU アーキテクチャに依存する。architectures = ["arm64"]に変更した場合は新しい snapshot が必要。現時点 (2026-04) では arm64 は SnapStart 非対応のため、SnapStart を使う場合はx86_64を使用すること。 - Logs Insights クエリを保存しないと運用後の計測が困難
§6 の Init Duration 計測クエリを Terraform のaws_cloudwatch_query_definitionで保存しておかないと、問題発生時にクエリを再構築する手間が生じる。導入時に保存クエリとして登録しておくことを強く推奨する。
8-3. Vol1 へのバックリンク
本記事は Lambda 応用シリーズ Vol.1「Container image Lambda 本番運用 完全ガイド」 の続編として位置付けている。
Vol.1 では ECR への Docker イメージ push / multi-arch 対応 / Lambda Web Adapter (LWA) を活用した既存 HTTP サーバーの Lambda 化を解説した。Vol.1 の §7 で予告した「Java Lambda の cold start 撲滅」が本記事の主題であり、両記事を合わせて読むことで Lambda の本番運用に必要な知識をカバーできる。
| 記事 | テーマ |
|---|---|
| Vol.1 Container image Lambda 本番運用 | Dockerfile / ECR / multi-arch / LWA / cold start 概論 |
| Vol.2 SnapStart 完全活用編 (本記事) | SnapStart / LifecycleHook / cold start 計測 / PC 使い分け |
| Vol.3 Lambda Powertools + Layers (近日公開) | observability / parameter / idempotency / Layers 配布 |
8-4. Vol3 予告: Powertools + Layers 統合運用編
- Vol1: Container image Lambda 本番運用完全ガイド (公開済)
- Vol2: SnapStart 完全活用編 (本記事) — Java Lambda cold start 撲滅実践ガイド ✅公開済
- Vol3 (次回): Powertools + Layers 統合運用編 — observability / parameter / idempotency の標準化と Layers 配布戦略を完全解説 (準備中)
Vol3 Powertools + Layers 統合運用編 (準備中)
8-5. 関連記事
本記事と関連する実践ガイドを紹介する。
- Container image Lambda 本番運用完全ガイド — Lambda 応用シリーズ Vol1。10GB Docker イメージ / multi-arch / LWA で既存 HTTP サーバーを Lambda 化する完全ガイド
- EventBridge Scheduler 本番運用完全ガイド — Java Lambda を定期実行する EventBridge Scheduler の本番構築ガイド。SnapStart 有効化 Java Lambda との組み合わせパターンを解説
- Amazon Bedrock Agents 完全ガイド — Java SDK + SnapStart を活用した Bedrock Agents Lambda の cold start 最適化パターン