NO IMAGE

Lambda SnapStart 完全活用 Java cold start 99%削減 Terraform

NO IMAGE
目次

1. この記事について

fig01: Lambda cold start フェーズ × SnapStart 効果範囲

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 を投入できる」ことをゴールとしている。

【シリーズ】Lambda 応用シリーズ (全3巻)

  • 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軸で整理する。「概念解説止まり」でなく「本番投入まで完走できる」ことを一貫したゴールとして構成している。

差別化6軸: 本記事が埋める空白

  • 軸1 — 仕組み図解: Firecracker MicroVM が Java Init Phase を snapshot 化し restore する動作原理を fig02 / fig03 で可視化。JVM 初期化完了状態の MicroVM イメージが S3 に保存される実装詳細まで踏み込む
  • 軸2 — LifecycleHook 実装コード: BeforeSnapshot / AfterRestore フックを Java Resource interface + 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
§3SnapStart の仕組みFirecracker MicroVM snapshot / restore フロー / cold start タイムライン
§4有効化 + Terraform 実装snap_start ブロック / publish=true / alias 運用
§5LifecycleHook 実装BeforeSnapshot / AfterRestore Java 完全実装
§6cold start 計測と検証Logs Insights クエリ + before-after 比較表
§7制約・落とし穴 + Provisioned Concurrency 使い分けVPC / Container image 非対応 / PC 比較マトリクス
§8まとめ + Vol3 予告 + 落とし穴 10 選チートシート + シリーズナビ

1-5. 想定環境

本記事のサンプルコードおよび Terraform HCL は以下の環境で動作確認している。

ツールバージョン一覧

ツール / ライブラリバージョン用途
Java (Amazon Corretto)21Lambda ランタイム・ローカル開発ともに統一
Maven3.9.xビルドツール (Gradle 8.x でも同様)
Terraform1.9.xインフラ管理 (aws provider ~> 5.0)
AWS CLIv2.xaws lambda invoke / aws logs start-query で利用
aws-lambda-java-core1.2.3Lambda Handler・LifecycleHook インターフェース
aws-lambda-java-runtime-interface-client2.4.2CRaC SnapStart SDK (AfterRestore 対応)
Lambda Insights 拡張機能v1.0.229.0cold 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 で詳述する。

§3 SnapStart の仕組みに進む


2. 前提・環境・準備

2-1. 前提環境

本記事では以下の前提環境を想定している。

AWS 環境前提

  • AWS アカウント (Administrator または Lambda / CloudWatch / IAM の操作権限を持つロール)
  • Lambda 対応リージョン (SnapStart は全リージョン対応。本記事サンプルは ap-northeast-1 で動作確認)
  • S3 バケット (SnapStart の snapshot 保存先は AWS マネージド。ユーザーによるバケット作成は不要)

ローカル開発環境確認

ツール確認コマンド期待される出力例
Java 21 (corretto)java -versionopenjdk version "21.x.x" ... Corretto
Maven 3.9+mvn -versionApache Maven 3.9.x
Terraform 1.9.xterraform versionTerraform v1.9.x
AWS CLI v2aws --versionaws-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 LambdaSnapStart GA (2022-11〜)実行環境
Amazon Corretto Java21Lambda ランタイム (SnapStart 対応)
aws-lambda-java-core1.2.3RequestHandler / Context / Resource interface
aws-lambda-java-runtime-interface-client2.4.2CRaC LifecycleHook SDK (AfterRestore 対応)
Terraform aws provider~> 5.0aws_lambda_function.snap_start リソース管理
CloudWatch Logs Insightscold 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.xmlaws-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 以降で突然出てくる専門用語の予備知識として活用されたい。

用語説明
SnapStartJava Lambda の cold start 削減機能。初期化済み実行環境を snapshot し restore することで Init Duration を短縮
Firecracker MicroVMAWS が開発した軽量仮想マシン。Lambda の実行環境として使用。SnapStart は JVM 初期化済み状態を snapshot として S3 に保存
Init PhaseLambda cold start の第1フェーズ。SnapStart ではこのフェーズが snapshot の restore に置換される
BeforeSnapshotsnapshot 取得直前に呼ばれる LifecycleHook。DB 接続切断・一時ファイル削除などリソース解放処理を記述
AfterRestoresnapshot からの restore 後に呼ばれる LifecycleHook。DB 接続再確立・キャッシュ初期化処理を記述
apply_onSnapStart の適用タイミング設定。PublishedVersions が唯一の有効な値 (None は無効化)
publish = trueLambda 関数バージョンの発行。SnapStart は発行済みバージョンに対してのみ有効
CRaCCoordinated Restore at Checkpoint。OpenJDK の checkpoint / restore 機能。AWS SnapStart はこれを Lambda に統合
Provisioned Concurrency (PC)事前に実行環境をウォームアップする機能。SnapStart と異なりコスト発生。§7 で SnapStart との使い分けを解説
InitDurationCloudWatch 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 82024-04 GA
python3.12 / python3.132024-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 フェーズは実行環境の準備から始まる。

  1. コード/イメージのダウンロード — Lambda が実行環境コンテナにコードパッケージを配置する(~100ms)
  2. Init フェーズ — ランタイムを起動しハンドラーを初期化する。Java では「JVM 起動」と「フレームワーク初期化」の 2 ステップが含まれる
  3. 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 startSnapStart 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 解剖

fig02: Firecracker MicroVM snapshot 取得 → restore フロー

SnapStart の中核は Firecracker の MicroVM スナップショット機能である。Firecracker は AWS が開発した軽量ハイパーバイザーで、Lambda の実行基盤として使われている。MicroVM のメモリ・CPU レジスタ・ファイルシステム差分をひとまとめに「凍結」し、あとで任意のタイミングで「解凍」して再開できる機能が SnapStart の根幹にある。

バージョン発行時の処理(1 回のみ):

  1. Lambda がマネージド Java ランタイムの Firecracker MicroVM 上で JVM を起動する
  2. ランタイムがエントリポイントクラスをロードし、フレームワークの ApplicationContext を初期化する(Bean 登録・DI 解決・Proxy 生成など)
  3. BeforeSnapshot ライフサイクルフック(SnapStartInitializer.beforeSnapshot())が呼び出される—スナップショットに含めたくないリソース(DB 接続・スレッドプール・オープンソケット等)をここで解放する
  4. Firecracker がその瞬間の MicroVM 状態(ヒープ・スタック・メタスペース・CPU レジスタ・FS 差分)をスナップショットとして暗号化し、AWS が管理する内部ストレージに保存する
  5. 生成されたスナップショットは Lambda バージョンに紐づく—コードを変更して再バージョン発行するまで同じスナップショットが使い回される。追加課金は発生しない

invoke 時(cold start ごと)の処理:

  1. Lambda が新しい実行環境スロットを割り当てる—従来の「コードダウンロード → JVM 起動」ステップは実行しない
  2. Firecracker が保存済みスナップショットからヒープ・スタック・FS 差分を新しい MicroVM に投影し、環境を復元する(メモリコピーのため数十 ms で完了)
  3. AfterRestore ライフサイクルフック(SnapStartInitializer.afterRestore())が呼び出される—restore 後に必要なリソースをここで再初期化する(DB 接続再確立・乱数シード再生成・TTL リセット等)
  4. 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 回のみ):

  1. Lambda が JVM 起動 + フレームワーク初期化を実行(Init フェーズ)
  2. BeforeSnapshot フック実行—DB 接続・ソケット・スレッドプールを解放してスナップショット対象外にする
  3. Firecracker MicroVM のメモリ状態(ヒープ・スタック・CPU レジスタ・FS 差分)をスナップショットとして暗号化保存

invoke 時(cold start ごと):

  1. スナップショットからメモリ状態を restore(JVM 起動をスキップ、数十 ms で完了)
  2. AfterRestore フック実行—DB 接続再確立・乱数シード再生成・外部リソース初期化
  3. 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 DurationSnapStart Init Durationrestore 速度の目安
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 Lambdamanaged ランタイムのみ対応。カスタムコンテナは非対応
$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 pagingrestore 後の物理ページ割り当てで追加 ms が発生する場合がある高メモリ設定で物理ページ確保を早める

VPC ENI 遅延が問題になる場合は、SnapStart と Provisioned Concurrency の併用が有効な選択肢となる。Provisioned Concurrency は実行環境を事前に起動して待機させるため、VPC ENI も事前に確保される。SnapStart 単体では解決できない VPC 遅延もこの組み合わせで解消できる。

SnapStart 導入前後の移行チェックリスト:

既存の Lambda 関数に SnapStart を追加導入する際は、以下の点を順番に確認する。

  1. ランタイムが java11 / java17 / java21 であることを確認
  2. publish = true の設定を追加(または既存設定を確認)
  3. snap_start { apply_on = "PublishedVersions" } ブロックを追加
  4. BeforeSnapshot フック実装—スナップショット前に解放すべきリソースを特定
  5. AfterRestore フック実装—restore 後に再初期化すべきリソースを特定
  6. 乱数シードの再生成AfterRestore に追加(必須)
  7. ステートフルなシングルトン(接続プール等)が restore 後も正常動作するか検証
  8. terraform apply 後に新バージョンが発行されスナップショットが取得されたことを確認
  9. CloudWatch Metrics の InitDuration が大幅に下がっていることを検証

4. 有効化 + Terraform 実装

SnapStart を本番で活用するには、関数の snap_start 有効化・バージョン発行・alias 切替の 3 ステップを Terraform で自動化するのが最善だ。
CI/CD パイプラインと組み合わせることで、コード変更のたびに新バージョンが自動発行され SnapStart の最適化が適用される。
コンソール・AWS CLI の手順も理解しておくと、トラブルシュートや迅速な動作確認に役立つ。

4-1. snap_start ブロック概要

fig03: snap_start Terraform リソース依存関係

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_functionaws_lambda_aliasaws_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 実装 必須チェックリスト

項目設定値備考
runtimejava21 / java17 / java11corretto managed runtime のみ対応。python/nodejs/Container image は非対応
publishtrue必須。false のまま snap_start を設定しても SnapStart は適用されない
snap_start.apply_on"PublishedVersions"唯一の有効値。無効化する場合は "None"
source_code_hashfilebase64sha256("function.zip")設定しないとコード変更なしでも毎回バージョンが発行され、不要な再最適化が発生する
memory_size128MB 以上 (1024MB 推奨)メモリが多いほど snapshot restore が高速になる
aws_lambda_aliasfunction_version = .version 参照alias 経由で invoke することで SnapStart 適用済みバージョンを確実に呼ぶ
aws_lambda_permission.qualifieralias name を指定qualifier 省略で $LATEST に向いてしまい SnapStart が無効になる
lifecycle.create_before_destroytrueバージョン更新時のゼロダウンタイム切替に必要
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 を有効化する手順は次のとおりだ。

  1. Lambda コンソール → 対象の関数名をクリック
  2. 「設定」タブ「一般設定」 → 右上の 「編集」 をクリック
  3. 「SnapStart」セクションで 「SnapStart を有効にする」 を選択
  4. 「保存」 をクリック (関数の更新が完了するまで数秒待機)
  5. 関数ページに戻り、「アクション」「新しいバージョンを発行」 を選択
  6. 説明を任意で入力 (例: SnapStart enabled) し 「発行」 をクリック
  7. 発行されたバージョンの 「設定」タブ → 「SnapStart」欄で 最適化ステータス を確認する
  8. 「有効 (OptimizationStatus: On)」に変わったら 「エイリアス」タブ から live alias を新バージョンへ更新

⚠️ コンソールで「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 が必要か

fig04: LifecycleHook 実行タイミング

SnapStart が snapshot を取得する際、JVM のヒープ・スタック・静的変数が丸ごと freeze される。
問題になるのは以下のリソース群だ。

リソースsnapshot 凍結時の問題LifecycleHook での対処
DB 接続 (JDBC / HikariCP)接続が EOF / タイムアウト状態で resume → クエリ時に即 ConnectionErrorBeforeSnapshot で全接続を close
AWS SDK クライアント (v2)内部 HTTP connection pool が切断済みAfterRestore で再初期化
IAM 一時資格情報TTL が 1 時間程度のため snapshot 取得後に期限切れリスクAfterRestore で資格情報プロバイダを refresh
java.util.Random シード全 restore インスタンスが同一シードを持ち乱数衝突AfterRestore で new SecureRandom() で再シード
SecureRandomLinux /dev/random に依存し restore 後エントロピー不足で block する場合ありAfterRestore で SecureRandom.getInstance() 再生成
キャッシュ (Guava / Caffeine)TTL ベースのキャッシュが古い時刻基準で動作BeforeSnapshot で invalidateAll() / AfterRestore で再ウォーム
外部 API HTTP コネクションKeep-Alive 接続がサーバ側で切断済みAfterRestore でクライアント再生成または接続リセット

LifecycleHook を実装しない場合、単体テスト・CI はすべて通過しても本番 SnapStart 環境で
SQLException: Connection is closedSdkClientException が再現するという典型的な落とし穴に陥る。

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 完全実装

QG-3: LifecycleHook 実装チェックリスト

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 v2listTables(limit=1)なしテーブル数が多くても limit=1 で高速
S3 v2headBucket()なしバケット名は環境変数から取得
Bedrock RuntimelistFoundationModels()なしinvokeModel() は課金発生するため warm-up に使用しない
SQS v2getQueueAttributes()なし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 クエリ

fig05: cold start 計測パイプライン (Lambda Insights → 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"
QG-4: cold start 計測結果比較表 — SnapStart before/after 実測値サンプル (Java 21 / Spring Boot 3.2 / 1024 MB)

メトリクスSnapStart 無効 (Init Duration)SnapStart 有効 (Restore Duration)削減率
p504,200 ms42 ms99.0%
p956,800 ms85 ms98.7%
p999,500 ms180 ms98.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 時点)

fig06: SnapStart vs Provisioned Concurrency 比較マップ

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 startSnapStart 有効時
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 使い分けマトリクス

比較軸SnapStartProvisioned 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 組み合わせ

ワークロード別推奨パターン

ワークロードパターン推奨理由
低〜中トラフィック / コスト重視 / JavaSnapStart 単独追加コストゼロ・99% cold start 削減
24h 安定高トラフィック / SLA 厳格PC 単独 or SnapStart + PCcold start 完全排除
バースト型 / Java / コスト許容SnapStart + PC 組み合わせウォーム確保 + バースト対応の両立
Container image / 非 JavaPC 単独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 へ段階的に移行できる。

  1. runtimejava11/17/21 (corretto) であることを確認 (非対応ランタイムは移行不可)
  2. Terraform に publish = truesnap_start { apply_on = "PublishedVersions" } を追加して terraform apply
  3. OptimizationStatus が "On" になるまで待機 (数分)
  4. ブルー/グリーン移行: エイリアスを新バージョン (SnapStart 有効) に向け、PC を解除
  5. §6 の Logs Insights クエリで RestoreDuration を計測し、cold start が十分短縮されているか確認
  6. 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
BeforeSnapshotDB接続切断 / 外部API接続解放 / ランダムシード無効化
AfterRestoreDB接続再確立 / 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 実測例)
OptimizationStatusapply 後 "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 選

  1. snap_start { apply_on = “None” } のまま放置で SnapStart 未動作
    Terraform で snap_start ブロックを書いても apply_on = "None" だと SnapStart は無効。"PublishedVersions" に変更し terraform apply 後に OptimizationStatus を確認すること。
  2. publish = true を忘れると snapshot が生成されない
    aws_lambda_functionpublish = true がないとバージョンが発行されず、SnapStart は適用されない。Terraform apply 後に aws lambda list-versions-by-function でバージョン発行を確認すること。
  3. $LATEST 直接 invoke だと SnapStart は適用されない
    SnapStart は発行済みバージョン (v1, v2…) にのみ適用。必ずエイリアス (live 等) 経由で invoke すること。開発時に $LATEST で動作確認しても SnapStart の効果は測定できない。
  4. AfterRestore で AWS SDK クライアントを再初期化しないと認証エラー
    snapshot から restore されたインスタンスは、snapshot 取得時の一時認証情報 (STS トークン) を持っている場合がある。AfterRestore フックで AWS SDK クライアントを再初期化し、最新の IAM Role 認証情報を取得し直すこと。
  5. ランダムシードの固定化で UUID 重複・セキュリティ問題
    snapshot から復元された全インスタンスが同じ乱数シードを持つと、UUID.randomUUID() 等が重複する。AfterRestore フックで SecureRandom の再シードと TokenManager の再初期化を必ず実施すること。
  6. 一時資格情報の有効期限切れ
    snapshot 取得時に環境変数 AWS_ACCESS_KEY_ID 等の一時資格情報が保存されると、restore 後に期限切れになる。AfterRestore フックで環境変数からではなく IAM Role の自動更新機構を使うよう SDK を初期化すること。
  7. VPC Lambda の cold start に ENI アタッチ遅延が残る
    SnapStart で JVM 起動時間は削減されるが、VPC Lambda の ENI アタッチ遅延 (300ms-1,500ms) は削減されない。VPC Lambda で cold start 問題が続く場合は Provisioned Concurrency または Interface VPC Endpoints の活用を検討すること。
  8. Container image Lambda に SnapStart を設定しようとしてエラー
    Container image Lambda (ECR ベース) は SnapStart 非対応。snap_start ブロックを設定すると Terraform apply でエラーになる。Container image の cold start 対策は Provisioned Concurrency を使用すること。
  9. arm64 と x86_64 の snapshot は流用不可
    SnapStart の snapshot は CPU アーキテクチャに依存する。architectures = ["arm64"] に変更した場合は新しい snapshot が必要。現時点 (2026-04) では arm64 は SnapStart 非対応のため、SnapStart を使う場合は x86_64 を使用すること。
  10. 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 統合運用編

【シリーズ】Lambda 応用シリーズ — 次巻予告

  • Vol1: Container image Lambda 本番運用完全ガイド (公開済)
  • Vol2: SnapStart 完全活用編 (本記事) — Java Lambda cold start 撲滅実践ガイド ✅公開済
  • Vol3 (次回): Powertools + Layers 統合運用編 — observability / parameter / idempotency の標準化と Layers 配布戦略を完全解説 (準備中)

Vol3 Powertools + Layers 統合運用編 (準備中)

8-5. 関連記事

本記事と関連する実践ガイドを紹介する。