「Lambda、たまに最初の1発がすごく遅い」——本番で必ずぶつかるのがコールドスタートです。決済の確定API、ユーザーが待っているフォーム送信、SLAのある社内API——P99レイテンシが効く場所でこれが出ると、体感品質を直撃します。
この記事は、AWS Lambda のコールドスタートを本番品質で抑えるための実装ガイドです。「無料で効く基本」から、SnapStart・プロビジョンド同時実行という2枚のカードの使い分け、VPC の落とし穴までを、意思決定ツリーで整理します。Lambda 本番運用の全体像(実行モデル・冪等性・可観測性・セキュリティ・コスト)は姉妹記事 AWS Lambda 本番運用ガイド に、本稿は**「速くする」一点**に集中します。
この記事のルール:仕様・パラメータ名・既定値は AWS 公式ドキュメント(2026年6月時点) に基づきます。SnapStart の対応ランタイム・料金体系・スケール上限は改定されるため、本番投入前に必ず公式(末尾「参考」)で最新値を確認してください。
0. メンタルモデル:コールドスタート=「実行環境を新しく作る時間」
まず正体を1枚に固定します。Lambda はリクエストごとに実行環境を割り当てます。手元に再利用できる環境が無いとき、新しく1つ作る——この準備時間がコールドスタートです。
- コールドスタート=コードのダウンロード+環境構築+INIT(ハンドラ外の初期化)。公式は「典型的には全呼び出しの1%未満、所要は100ms未満〜1秒超」とする。
- 問題は平均ではなく裾(P99/P100)。99%が速くても、待たされる1%が「決済確定」なら事業インパクトは大きい。
- 3つの打ち手がある:①無料の最適化(接続再利用・パッケージ削減・Arm64)②SnapStart(初期化済みスナップショットを復元)③プロビジョンド同時実行(環境を事前に温めておく)。
- 2025年8月から INIT 時間も課金対象。コールドスタートを減らすことは、レイテンシだけでなくコストも改善する。
この章の順番——まず無料、次にSnapStart、最後にプロビジョンド——が、そのまま意思決定の優先順位です(第5章で図にします)。
1. まず測る:コールドスタートを可視化する
最適化の前に計測です(推測で温めるのは最も高くつく)。コールドスタートは CloudWatch Logs の REPORT 行に Init Duration として現れます。
REPORT RequestId: ... Duration: 12.34 ms Billed Duration: 13 ms
Memory Size: 1024 MB Max Memory Used: 95 MB Init Duration: 320.45 ms
Init Duration 行がある呼び出し=コールドスタート、無い呼び出し=ウォームスタートです。Logs Insights で割合とP99を出します。
-- コールドスタート率と Init Duration の分布(CloudWatch Logs Insights)
filter @type = "REPORT"
| stats count(*) as invocations,
sum(strcontains(@message, "Init Duration")) as cold_starts,
avg(@initDuration) as avg_init_ms,
pct(@initDuration, 99) as p99_init_ms
X-Ray アクティブトレーシングを有効にすると、Initialization サブセグメントとしてコールドスタートが可視化され、どの初期化(SDK・DB接続・設定取得)が重いかまで追えます。まずここで「本当に効く場所」を特定してから打ち手を選びます。
2. 無料で効く最適化:接続再利用・パッケージ削減・Arm64
SnapStart やプロビジョンドの前に、追加課金なしで効く基本を尽くします。多くのワークロードはここで十分です。
2.1 重い初期化はハンドラ外で1度だけ(接続再利用)
凍結された実行環境はメモリ状態ごと再利用されます。公式も明言する通り、ハンドラ外で宣言したオブジェクトはウォーム呼び出し間で生き続ける。DB接続・SDKクライアント・設定取得はハンドラ外で1度だけ行い、再利用します。これはコールドスタートのINITを軽くし、かつウォーム呼び出しの実行時間も削ります。
// 良い例:接続・クライアントはハンドラ外(INIT)で1度だけ。ウォーム呼び出しで再利用される。
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient({})); // ← ここで1度だけ
let configPromise: Promise<AppConfig> | undefined; // 重い設定は遅延初期化
export const handler = async (event: MyEvent) => {
configPromise ??= loadConfig(ddb); // 初回だけ実行、以降は同じPromiseを再利用
const config = await configPromise;
return process(event, config);
};
# 悪い例:ハンドラ内で毎回クライアント生成 → 毎回接続コスト+実行時間増(やってはいけない)
def lambda_handler(event, context):
ddb = boto3.client("dynamodb") # ← 毎回生成。ハンドラ外に出すべき
...
2.2 パッケージを小さく、依存を絞る
コードのダウンロードはコールドスタートの一部です。パッケージが小さいほど速い。
- AWS SDK v3 は必要なサブクライアントだけ import(
@aws-sdk/client-dynamodb等)。SDK 全体を持ち込まない。 - esbuild 等でツリーシェイク・バンドル。
node_modulesの丸ごと同梱を避ける。 - Go / Rust はレイヤー非推奨(公式)。実行ファイルに同梱した方がコールドスタートに有利。
2.3 Arm64(Graviton2)にする
互換性があるなら Arm64 はほぼ常に得です。実行料金は x86 比20%安く、多くのワークロードで性能も向上します。ビルドターゲットを変えるだけのことが多い。レイテンシそのものよりコストに効きますが、コールドスタート対策とコスト最適化は同じ方向を向きます。
2.4 メモリを上げて「速く終える」
CPU はメモリに比例配分され、1,769MB で1 vCPU 相当。初期化や処理がCPUバウンドなら、メモリを上げるとコールドスタートも実行時間も短くなることがあります。推測せず計測で決める(AWS Lambda Power Tuning が公式サンプル)。
3. SnapStart:初期化済みのスナップショットを復元する
無料の最適化で足りない——とくにJVMのような初期化が重いランタイムで裾が許容できない場合の第一手が SnapStart です。
3.1 仕組みと対応ランタイム
SnapStart は、関数バージョン公開時に1度だけ初期化を実行し、初期化済みの実行環境の(メモリ+ディスクの)スナップショットを Firecracker MicroVM で取得・暗号化・キャッシュします。以降は初期化をやり直す代わりに、**キャッシュされたスナップショットから復元(Restore)**してコールドスタートを短縮します。ライフサイクルに Restore フェーズが加わるイメージです。
| ランタイム | SnapStart対応 | 料金(公式) |
|---|---|---|
| Java 11 以降 | ✅ | 追加料金なし |
| Python 3.12 以降 | ✅ | キャッシュ料金(最低3時間)+復元料金 |
| .NET 8 以降 | ✅ | キャッシュ料金(最低3時間)+復元料金 |
| Node.js / Ruby / OS-only / コンテナイメージ | ❌ | — |
ポイント:Java は無料、**Python/.NET は「スナップショットのキャッシュ維持」+「復元のたびの料金」**が発生します。また SnapStart は公開バージョン/エイリアスでのみ有効($LATEST 不可)、プロビジョンド同時実行とは併用不可、/tmp 512MB超やEFS等とも併用不可です。
3.2 最大の罠:一意性(スナップショットは使い回される)
SnapStart で必ず踏むのがこれです。スナップショットは初期化時の状態を丸ごと固めて使い回すため、初期化中に生成した「一意なはずの値」が全環境で同じになります。乱数シード・UUID・トークン・キャッシュしたタイムスタンプが該当します。
対策はランタイムフック。復元後(afterRestore)に「一意・揮発的な値」を作り直します。
// Java(CRaC: org.crac)。afterRestore で乱数源を作り直す。
import org.crac.Context;
import org.crac.Core;
import org.crac.Resource;
public class Handler implements Resource {
private SecureRandom random = new SecureRandom();
public Handler() { Core.getGlobalContext().register(this); }
@Override public void beforeCheckpoint(Context<? extends Resource> c) { /* 接続を畳む等 */ }
@Override public void afterRestore(Context<? extends Resource> c) {
// スナップショット復元後に一意性・エントロピーを再取得(全環境で同じ値になるのを防ぐ)
this.random = new SecureRandom();
}
}
# Python(snapshot-restore-py。Python managed runtime に同梱)
from snapshot_restore_py import register_after_restore
@register_after_restore
def reseed():
# 復元後にランダム性・一時データを作り直す(DB接続の張り直しもここで)
global random_seed
random_seed = generate_fresh_seed()
公式が挙げる3つの注意:①一意性——初期化で作った一意コンテンツは復元後に作り直す。②ネットワーク接続——復元後は接続状態が保証されないので検証・再確立する(AWS SDKの接続は通常自動回復)。③一時データ——一時認証情報やキャッシュしたタイムスタンプはハンドラ内で更新してから使う。
3.3 いつ SnapStart が効くか
SnapStart はスケールして頻繁に呼ばれる関数で最も効きます(公開時の初期化コストを多数の復元で償却するため)。逆にほとんど呼ばれない関数は、Python/.NET ではキャッシュ料金(最低3時間〜継続)だけ乗って割に合わないこともあります。Java は無料なので、初期化が重いJVM関数のコールドスタート対策としては第一候補です。
4. プロビジョンド同時実行:環境を事前に温めておく
「二桁ミリ秒の応答が必須で、SnapStart では足りない/対応ランタイムでない」場合の確実な一手が**プロビジョンド同時実行(Provisioned Concurrency)**です。
4.1 仕組みと使いどころ
プロビジョンド同時実行は、指定した数の実行環境を事前に初期化して待機させます。コールドスタートが構造的に発生しないので、最も低く・最も予測可能なレイテンシが出ます。ただし追加課金があり、$LATEST には設定できず(公開バージョン/エイリアスのみ)、予約済み同時実行を超えて設定できません。Lambda は1関数あたり最大6,000環境/分のペースで用意します(即座には立ち上がらない)。
4.2 トラフィックに追従させる:Application Auto Scaling
プロビジョンド分を固定値で持つと、暇な時間に払いすぎ・ピークに足りない、が起きます。Application Auto Scaling のターゲット追跡で、利用率に応じて自動増減させます。
# Terraform: プロビジョンド同時実行をターゲット追跡で自動スケール
# 利用率 ProvisionedConcurrencyUtilization を 70% に保つように増減する
resource "aws_appautoscaling_target" "lambda" {
service_namespace = "lambda"
resource_id = "function:orders-api:live" # エイリアス live
scalable_dimension = "lambda:function:ProvisionedConcurrency"
min_capacity = 2
max_capacity = 50
}
resource "aws_appautoscaling_policy" "lambda_target_tracking" {
name = "lambda-pc-target-tracking"
policy_type = "TargetTrackingScaling"
service_namespace = aws_appautoscaling_target.lambda.service_namespace
resource_id = aws_appautoscaling_target.lambda.resource_id
scalable_dimension = aws_appautoscaling_target.lambda.scalable_dimension
target_tracking_scaling_policy_configuration {
target_value = 0.70 # 10%〜90%の範囲で設定可。70%利用を狙う
predefined_metric_specification {
predefined_metric_type = "LambdaProvisionedConcurrencyUtilization"
}
}
}
予測できるピーク(毎営業日9時の一斉ログイン等)にはスケジュールスケーリングを併用し、ピーク前に温めておきます。プロビジョンド分を超えたリクエストは通常のオンデマンド(コールドスタートあり)にこぼれるので、min_capacity はピークの底を、max_capacity は予約済み同時実行以内で設計します。
5. 意思決定ツリー:何を、どの順で使うか
ここまでを1つの順序に落とします。安く効くものから試すのが原則です。
コールドスタートのP99が許容できない?
├─ No → 何もしない(コールドスタートは1%未満。過剰最適化は技術的負債)
└─ Yes
├─ ① まず無料の最適化(全ランタイム共通・必須)
│ 接続をハンドラ外で再利用 / パッケージ削減 / Arm64 / メモリを計測で最適化
│ → これで足りれば終了(多くはここで足りる)
│
├─ ② 対応ランタイム(Java / Python 3.12+ / .NET 8+)か?
│ Java → SnapStart(無料)。初期化が重いJVMの第一候補
│ Python/.NET → 規模があるなら SnapStart(復元課金 < コールドスタート損失なら採用)
│ → 一意性・接続はランタイムフックで必ず作り直す
│
└─ ③ 二桁ミリ秒のSLAが必須 / SnapStart非対応(Node.js,Ruby) / SnapStartでも不足?
→ プロビジョンド同時実行 + Application Auto Scaling(ターゲット追跡)
→ 予測ピークはスケジュールスケーリングを併用
3つの打ち手を比較表で固定します。
| 手段 | レイテンシ改善 | 追加課金 | 対応 | 主な注意 |
|---|---|---|---|---|
| 無料の最適化 | 中(INIT短縮) | なし | 全ランタイム | まずこれ。多くはここで足りる |
| SnapStart | 大 | Javaは無料 / Python・.NETは復元+キャッシュ | Java11+/Py3.12+/.NET8+ | 一意性・接続の作り直し。$LATEST不可。プロビジョンドと併用不可 |
| プロビジョンド同時実行 | 最大(事前初期化) | あり(常時) | 全ランタイム | 予約済み以内。Auto Scalingで払いすぎ防止 |
併用不可に注意:SnapStart とプロビジョンド同時実行は同じ関数バージョンで併用できません。「JavaでSnapStart無料が効くか」をまず見て、足りなければプロビジョンドへ、という排他選択になります。
6. VPC のコールドスタート:Hyperplane ENI の現在地
「Lambda を VPC に入れるとコールドスタートが激遅になる」という古い知識は、現在はほぼ当てはまりません。Lambda が VPC 用に作る ENI は Hyperplane ENI で、同じサブネット+セキュリティグループの組み合わせの関数間で共有・再利用され、各 ENI は最大65,000接続をさばきます。
重要なのは、ENI の作成は「関数の作成・更新時」に行われる点です。新規VPCアタッチ時は関数が一時的に Pending 状態になり数分かかることがありますが、これは呼び出し経路から切り離されているため、定常運用のコールドスタートには乗りません。注意点は2つ:
- 14日間アイドルで ENI が回収され、関数が
Inactiveになる(次回呼び出しで再作成)。常時トラフィックがあれば問題にならない。 - 外向き通信には NAT が必要。プライベートサブネット配置だけではインターネットに出られない。AWSサービスへは VPC エンドポイントで NAT を介さず到達でき、レイテンシ・コスト・セキュリティの全てで有利。
つまり VPC は今や「コールドスタートを理由に避けるもの」ではなく、設計(サブネット/SGの共有、エンドポイント)を正しくやれば普通に使えるものです。
7. まとめ:コールドスタート対策チートシート
- まず測る:
Init Duration(REPORT行)とX-RayのInitializationでP99と重い初期化を特定。推測で温めない。 - 無料の最適化を尽くす:接続をハンドラ外で再利用 / パッケージ削減(SDKサブクライアント+ツリーシェイク)/ Arm64 / メモリを計測で最適化。多くはここで足りる。
- SnapStart:Java/Python3.12+/.NET8+。Javaは無料で初期化が重いJVMの第一候補。一意性・接続はafterRestoreで必ず作り直す。
$LATEST不可、プロビジョンドと併用不可。 - プロビジョンド同時実行:二桁ミリ秒SLA必須 or SnapStart非対応のとき。Application Auto Scalingのターゲット追跡(
ProvisionedConcurrencyUtilizationを70%)で払いすぎを防ぎ、予測ピークはスケジュールスケーリング。 - VPC:Hyperplane ENIで今は速い。サブネット/SG共有・VPCエンドポイント・NATを正しく設計すれば避ける理由はない。
- コスト視点:2025年8月からINITも課金対象。コールドスタート削減はレイテンシとコストを同時に改善する。
コールドスタート対策は「とりあえずプロビジョンドで温める」ではなく、計測 → 無料の最適化 → 対応ランタイムならSnapStart → SLA必須だけプロビジョンドという順で、払う必要のないコストを払わずに裾を潰す設計です。私は決済プラットフォームのレイテンシ要件と費用を両立させるため、この優先順位で「効く場所にだけ」コストをかけました。
「自社のLambdaのレイテンシ(P99)を、過剰コストをかけずに本番SLAに収めたい」——計測・打ち手の選定・IaCでの自動スケールまで、一人 × 生成AI(Claude Code)の速さで伴走します。 Lambda運用の全体設計は AWS Lambda 本番運用ガイド も併せてどうぞ。
参考(公式ドキュメント)
- Lambda execution environment lifecycle — INITフェーズ、コールドスタート(1%未満・100ms〜1s超)、ハンドラ外オブジェクトの再利用
- AWS Lambda standardizes billing for the INIT phase — 2025年8月1日からの INIT 課金統一
- Improving startup performance with Lambda SnapStart — 対応ランタイム(Java11+/Python3.12+/.NET8+)、Firecrackerスナップショット、料金、制限
- Handling uniqueness with SnapStart — 一意性・乱数・エントロピーの注意
- SnapStart runtime hooks (Java/CRaC) / Python —
beforeCheckpoint/afterRestore、@register_after_restore - Provisioned concurrency — 事前初期化、Application Auto Scaling(スケジュール/ターゲット追跡、
ProvisionedConcurrencyUtilization) - Lambda concurrency — 予約済み/プロビジョンド、SnapStartとの併用不可
- Configuring a Lambda function to access resources in a VPC — Hyperplane ENI、共有・再利用、Inactive、NAT/VPCエンドポイント
- AWS Lambda Pricing — 実行時間/プロビジョンド/Arm の料金