概要
Lambda 関数の応答が、ときどきだけ数秒遅くなることがあります。ログを見ると、遅い呼び出しにだけ Init Duration が付いている。これは コールドスタート、つまり新しい実行環境を用意して、ハンドラーの外にある初期化コード(ライブラリの読み込み、設定の取得、SDK クライアントの作成など)を実行していた時間です。
初期化コードを軽くするのが本筋ですが、コードを直す手間をかけずに待ち時間をなくしたいときに使えるのが プロビジョニングされた同時実行 です。指定した数の実行環境をあらかじめ初期化しておき、リクエストが来たらすぐハンドラーを実行します。名前の似た 予約された同時実行 は、同時実行数の確保と上限を決めるもので、初期化の時間は縮まりません。
この記事では、コールドスタートの正体と見つけ方、プロビジョニングされた同時実行と予約された同時実行の違いを整理します。実践では、初期化に 2 秒かかる関数の Init Duration を確かめ、バージョンとエイリアスを作ってプロビジョニングされた同時実行を設定し、呼び出し方によって効く・効かないが分かれることまで確かめます。

クラウドおさる猿山でも、朝いちばんの荷運びは、若猿を起こして顔を洗わせるところから始まるので遅いのでござる。前の晩から起きて待っている当番を置いておけば、すぐ走れる。今日はその「起きて待つ当番」を Lambda で用意するでござるよ。
この記事のメリット
- コールドスタートが Lambda の実行環境の初期化(Init フェーズ)であることを説明できるようになる
- CloudWatch Logs の
REPORT行のInit Durationから、コールドスタートを見つけられるようになる - プロビジョニングされた同時実行と予約された同時実行の違い(目的・料金・設定先)を整理できる
- プロビジョニングされた同時実行をエイリアスに設定し、呼び出し側がそのエイリアスを使っていないと効かないことが分かる
執筆者とキャラクター紹介
執筆者:土肥(とひ)
株式会社ジェニュインの代表であり元エンジニア。これまでオンプレミスと AWS 環境のインフラ業務を担当。AWS 認定資格の学習で得た知識を、ハンズオンとともに解説するのが得意。直近では新人の教育も行っており、これから技術を学ぶ方が納得できる解説を心がけています。基礎編・Cloud Practitioner の記事では、解説役としても登場します。


クラウドおさる



はじめましてでござる。弊猿はクラウドおさる。IT インフラが異常に発達した猿山から、雲に乗って地上の AWS を学びに降りてきたのでござる。



基礎編や Cloud Practitioner の記事では、とひさんに素朴な疑問をぶつける役でござるな。Security - Specialty などの記事では、要所でワンポイントや注意点をひとこと添えるでござる。



猿山にもデータセンターや監視当番があって、地上で起きることはだいたい猿山にも同じ仕組みがあるのでござる。例え話がつい猿山に寄るのは、弊猿の癖でござる。
技術解説
コールドスタートとは
Lambda は、関数を呼び出すための実行環境を必要に応じて作ります。新しい実行環境を作るときは Init フェーズ として、拡張機能の起動、ランタイムの起動、そして関数の静的コード(ハンドラーの外に書いたコード)の実行を行い、それからハンドラーを実行します。この初期化のぶんだけ、その呼び出しの応答が遅くなります。これがコールドスタートです。
一度作った実行環境はしばらく再利用されるので、2 回目以降の呼び出しは初期化を飛ばしてすぐハンドラーを実行します。同時に来るリクエストが増えて実行環境が足りなくなると、新しい実行環境が作られ、そこで再びコールドスタートが起きます。
| 見るところ | 分かること |
|---|---|
CloudWatch Logs の REPORT 行の Init Duration | その呼び出しで初期化が走ったこと(コールドスタート)と、かかった時間 |
環境変数 AWS_LAMBDA_INITIALIZATION_TYPE | 実行環境が on-demand か provisioned-concurrency か |
CloudWatch Lambda Insights の init_duration | 初期化にかかった時間をメトリクスで追う |



Init Duration が付いている行だけが、若猿を起こしたところから始めた呼び出しでござる。遅い呼び出しにだけ付いていれば、犯人は初期化でござるな。
プロビジョニングされた同時実行と予約された同時実行
| プロビジョニングされた同時実行 | 予約された同時実行 | |
|---|---|---|
| 目的 | 実行環境をあらかじめ初期化しておき、コールドスタートの待ちをなくす | 関数が使える同時実行数を確保し、同時にその数を上限にする |
| 応答時間 | 初期化済みの環境で処理するので縮まる | 変わらない(初期化は従来どおり起きる) |
| 設定先 | バージョンかエイリアス($LATEST には設定できない) | 関数 |
| 料金 | 追加料金がかかる(リクエストを処理していなくても、設定している間は課金) | 追加料金なし |
| 超えたとき | 設定数を超えたぶんは通常のオンデマンドの実行環境で処理する | 上限を超えたぶんはスロットリングされる |
プロビジョニングされた同時実行は、関数のバージョンかエイリアスに設定します。呼び出す側(API Gateway の統合やイベントソースなど)が、設定したバージョンかエイリアスを指していないと使われません。関数名だけで呼ぶと $LATEST が動き、オンデマンドの実行環境で処理されます。
どちらの同時実行も、アカウントの同時実行数から割り当てます。予約されていない同時実行を 100 残す必要があるので、アカウントの同時実行の上限が小さいと設定できない場合があります。



プロビジョニングされた同時実行は、呼ばれなくても待たせているぶんのバナナが出ていくのでござる。試したら設定を消す。予約された同時実行を増やしても速くはならぬ、という取り違えにも気をつけるでござるぞ。


設定数を超えたリクエストがオンデマンドで処理された回数は、CloudWatch のメトリクス ProvisionedConcurrencySpilloverInvocations で確認できます。設定数が足りているかは ProvisionedConcurrencyUtilization(使用中の数 ÷ 設定数)で見ます。
実践
初期化に 2 秒かかる関数 soa-coldstart-fn を使います。まず $LATEST をテストして Init Duration を確かめ、次にバージョン 1 とエイリアス live を作って、live にプロビジョニングされた同時実行を 1 つ設定します。最後に、live を指定した呼び出しと、関数名だけの呼び出しで結果を比べます。
前提条件
- リージョン:
ap-northeast-1(東京) - 以下のリソースが作成済みであること
まだ用意していない場合は、「作り方の参考」の記事の手順で作れます。リソース名は、この記事の名前に読み替えてください。
| リソース種別 | リソース名 | 内容 | 作り方の参考 |
|---|---|---|---|
| Lambda 関数 | soa-coldstart-fn | Python 3.13・メモリ 128 MB・タイムアウト 15 秒。コードは下記 | AWS Lambda で関数を作成しイベント駆動で実行する |
soa-coldstart-fn の lambda_function.py は次の内容です。ハンドラーの外で 2 秒待つことで、重い初期化を再現しています。
import os
import time
# ハンドラーの外のコードは、実行環境の初期化(Init)のときに 1 回だけ動く。
# 重いライブラリの読み込みや設定の取得の代わりに 2 秒眠る
time.sleep(2)
INIT_TYPE = os.environ.get("AWS_LAMBDA_INITIALIZATION_TYPE")
def lambda_handler(event, context):
return {"initType": INIT_TYPE, "version": context.function_version}作成するリソース一覧
| リソース種別 | リソース名 | 用途 |
|---|---|---|
| Lambda のバージョン | 1 | プロビジョニングされた同時実行を設定する対象 |
| Lambda のエイリアス | live | バージョン 1 を指す。呼び出し側はこの名前で呼ぶ |
| プロビジョニングされた同時実行の設定 | live に 1 | 初期化済みの実行環境を 1 つ用意する |
手順 1: コールドスタートを確かめる
AWS マネジメントコンソールにログインし、リージョンが ap-northeast-1(東京)であることを確認します。上部の検索バーに Lambda と入力し、「Lambda」を選択して、関数 soa-coldstart-fn を開きます。
「テスト」タブで、イベント名に ping と入力し(イベント JSON はデフォルトのまま)、「テスト」をクリックします。実行結果の「詳細」を開くと、レスポンスに "initType": "on-demand" と "version": "$LATEST" が返り、「ログ出力」の REPORT 行に Init Duration: 2xxx ms が付いています。ハンドラーの実行(Duration)はわずかで、時間のほとんどが初期化に使われたことが分かります。


続けてもう一度「テスト」をクリックします。今度は同じ実行環境が再利用されるので、REPORT 行に Init Duration が付かず、すぐに応答が返ります。
手順 2: バージョンとエイリアスを作る
「バージョン」タブで「新しいバージョンを発行」をクリックし、ダイアログの「バージョンの説明 – オプション」に cold start test と入力して「発行」をクリックします(SnapStart は None のまま)。バージョン 1 が作られます。
続けて「エイリアス」タブで「エイリアスを作成」をクリックし、以下を設定して「保存」をクリックします。
- 名前:
live - バージョン:
1


手順 3: プロビジョニングされた同時実行を設定する
関数 soa-coldstart-fn の「設定」タブで、左側の「同時実行と再帰の検出」を選択します。「プロビジョニングされた同時実行」のセクションで「設定を追加」をクリックし、「プロビジョニングされた同時実行の設定」の画面で以下を設定して「保存」をクリックします。
- 修飾子のタイプ: 「エイリアス」
- エイリアス:
live - プロビジョニングされた同時実行:
1
画面には料金表へのリンクが出るだけで、金額は表示されません。この設定は保存した時点から、呼び出しの有無にかかわらず課金が始まります。


設定一覧の「ステータス」が「成功」になるまで、1〜2 分待ちます(CLI の get-provisioned-concurrency-config では IN_PROGRESS から READY に変わります)。このあいだに Lambda が実行環境を用意し、ハンドラーの外のコード(2 秒の待ち)を先に済ませています。





「成功」は、当番の若猿が顔を洗って持ち場で待っている状態でござる。2 秒の支度は、呼ばれる前に済んでいるのでござるな。
手順 4: エイリアスを指定した呼び出しと、関数名だけの呼び出しを比べる
上部のツールバーの CloudShell アイコンをクリックして AWS CloudShell を開き、次のコマンドを実行します。1 つ目は関数名にエイリアス live を付けた呼び出し、2 つ目は関数名だけの呼び出しです。
aws lambda invoke --function-name soa-coldstart-fn:live live.json && cat live.json; echo
aws lambda invoke --function-name soa-coldstart-fn latest.json && cat latest.json; echo1 つ目は {"initType": "provisioned-concurrency", "version": "1"} が返り、すぐに応答します。2 つ目は {"initType": "on-demand", "version": "$LATEST"} が返ります。プロビジョニングされた同時実行は live に設定したものなので、関数名だけで呼ぶと使われません。


手順 5: ログで Init Duration を見比べる
関数の「モニタリング」タブ →「CloudWatch Logs を表示」をクリックし、ロググループ /aws/lambda/soa-coldstart-fn を開きます。ログストリーム名の [1] はバージョン 1、[$LATEST] は $LATEST の実行環境です。
ロググループの画面で「Logs Insights で表示」(または左側ナビゲーションの「Logs Insights」で、クエリの対象にロググループ /aws/lambda/soa-coldstart-fn を指定)を開き、次のクエリを実行します。
fields @timestamp, @logStream, @duration
| filter @type = "REPORT"
| parse @message "Init Duration: * ms" as initDuration
| sort @timestamp desc[$LATEST] のストリームで最初に呼び出した行には initDuration に約 2000 が入り、[1](live 経由)の呼び出しの行には initDuration が入りません。バージョン 1 の初期化は、プロビジョニングされた同時実行を設定したときに済んでいるからです。そのときの初期化は、[1] のストリームに INIT_REPORT という別の行(Init Duration 付き)で記録されています。


まとめ
| 困りごと・確かめたいこと | 見るところ・やること |
|---|---|
| 一部の呼び出しだけ遅い | REPORT 行の Init Duration(Lambda Insights なら init_duration) |
| コードを直さずに初期化の待ちをなくしたい | バージョンかエイリアスにプロビジョニングされた同時実行を設定する |
| 設定したのに速くならない | 呼び出し側がそのエイリアス / バージョンを指しているか。AWS_LAMBDA_INITIALIZATION_TYPE で確認 |
| 設定数が足りているか | ProvisionedConcurrencySpilloverInvocations と ProvisionedConcurrencyUtilization |
- コールドスタートは、新しい実行環境の初期化(Init フェーズ)にかかる時間。ハンドラーの外のコードもここで動く
- プロビジョニングされた同時実行は、指定数の実行環境をあらかじめ初期化しておく。
$LATESTには設定できない - 予約された同時実行は同時実行数の確保と上限で、応答時間は縮まらない。追加料金もかからない
- プロビジョニングされた同時実行は、使っていなくても設定している間は課金される
- 予約されていない同時実行を 100 残す必要があるので、アカウントの上限によっては設定できない



「応答が初期化で遅い、コードは直したくない」と来たら、待たせておく当番、つまりプロビジョニングされた同時実行でござる。予約された同時実行と名前が似ているので、目的の違いで見分けるのでござるな。
参照先
- 関数に対するプロビジョニングされた同時実行数の設定 – AWS Lambda
- 関数に対する予約済み同時実行数の設定 – AWS Lambda
- Lambda 実行環境のライフサイクルの概要 – AWS Lambda
- Lambda 関数のバージョンを管理する – AWS Lambda
- Lambda 関数のエイリアスの作成 – AWS Lambda
- Lambda 関数のメトリクスのタイプ – AWS Lambda
- Lambda Functions – Amazon CloudWatch
- parse – Amazon CloudWatch Logs














