FOR PRACTITIONERS実務向け

Jコンテナとサーバーレス基盤段3基盤層仕組み

Lambda が動く仕組み|実行環境・コールドスタート・同時実行を、1 回の呼び出しの流れで理解する

Lambda が動く仕組み|実行環境・コールドスタート・同時実行を、1 回の呼び出しの流れで理解する

Lambda で関数を動かしてみたら、たまに応答が遅い、同時にたくさん来ると一部が失敗する、データベースの接続数が増えすぎた、といった現象に出会うことがあります。どれも関数のコードだけを見ていても原因は分かりません。裏で実行環境が作られ、使い回され、並列に増えているからです。この記事は、1 回の呼び出しの流れを追いながら、遅さと失敗を仕組みのどこで切り分けるかを整理します。Lambda の基本は Lambda とは何か にあります。

この記事の結論

  • 呼び出しには実行環境(コードを動かす隔離された箱)が要ります。空きが無ければ新しく作り、その準備が Init フェーズ、つまりコールドスタートの正体です。
  • 処理を終えた環境は凍結されて一定時間残り、次の呼び出しで再利用されます(ウォームスタート)。ただし永続はしません。
  • 同時実行数は「毎秒のリクエスト数 × 平均処理時間(秒)」で決まります。同時に処理する数だけ環境が並びます。
  • 同時実行はリージョンごとのアカウント上限を全関数で分け合います。予約済みは取り分の保証と上限、プロビジョニング済みは先に温めておく機能で、別物です。
  • 遅さは Init(初期化コードとパッケージの大きさ)、失敗はスロットリング(上限)と下流への接続数に分けて見ます。

Lambda は呼び出しのたびに何をしているのか

Lambda は、サーバーを管理せずにコードを実行する、イベント駆動のサービスです。その「サーバーを管理しない」の裏側では、呼び出しごとに実行環境が用意されています。実行環境は、関数のコード、ランタイム(言語の実行基盤)、設定値を載せた隔離された箱で、1 つの環境が同時に処理するのは 1 回の呼び出しだけです。

環境のライフサイクルは大きく 3 つのフェーズです。

  • Init: 環境を新しく作り、コードを取得し、ランタイムを起動し、ハンドラの外にある初期化コードを実行する。環境変数もこの段階で読み込まれ、関数の中から使えるようになる
  • Invoke: ハンドラ関数が呼ばれ、イベントを処理して結果を返す
  • Shutdown: 使われなくなった環境が片付けられる

呼び出しが来たとき、Lambda はまず「待機中の空き環境があるか」を見ます。あればそのまま Invoke へ進み、無ければ Init から始めます。処理を終えた環境はすぐに消されず、凍結された状態で次の呼び出しを待ちます。

ONE INVOCATION1 回の呼び出し

呼び出しは空き環境があれば Invoke だけ、無ければ Init から始まる

  1. リクエスト

    イベントが届く

  2. 空き環境は?

    待機中の環境を探す

  3. Init

    コード取得・起動・初期化(無いときだけ)

  4. Invoke

    ハンドラが処理する

  5. 凍結

    次の呼び出しで再利用

出典: AWS Lambda 公式ドキュメント(実行環境のライフサイクル)

設定値をコードに埋め込まず環境変数で渡す方法は、Init で環境変数が読まれる流れと合わせて理解できます。コードを変えずに接続先を差し替えられるのは、環境を作るときに設定が注入されるからです。

CHECK 1ここまでの確認

ハンドラ関数の外に書いた初期化コードが実行されるのは、いつ?

答えと解説

答え: 2. 新しい実行環境を作る Init のときだけで、使い回された環境では再実行されない

ハンドラの外は Init フェーズで動き、環境が再利用されると飛ばされます。「コールドスタートとは何か」の章を読み直してください。

コールドスタートとは何か、ウォームスタートで何が使い回されるか

空き環境が無く、Init から始まる呼び出しをコールドスタートと呼びます。待機中の環境にそのまま Invoke する場合はウォームスタートです。利用者から見ると、コールドスタートは Init の分だけ応答が遅れる呼び出しです。

何が Init の長さを決めるか

Init にかかる時間は、おおまかに次の要素で変わります。

  • デプロイパッケージ(コードと依存ライブラリ)の大きさ。大きいほど取得に時間がかかる
  • ハンドラの外で実行する初期化コードの重さ。大きなライブラリの読み込みや、起動時の通信などが含まれる
  • ランタイムの種類

具体的な時間は言語や構成で変わるため、この記事では数字を挙げません。手元の関数で測るのが確実です。パッケージを軽くする手段として、共通の依存をレイヤーに切り出して複数の関数で再利用する方法があります。レイヤーはデプロイパッケージを小さく保つ機能で、Init に効く話とつながります。

ウォームスタートで使い回されるもの

環境が再利用されると、ハンドラの外で作ったものが次の呼び出しにも残ります。たとえば次のような使い方です。

import boto3

# ハンドラの外: Init のときに一度だけ実行される
client = boto3.client("dynamodb")

def handler(event, context):
    # ハンドラの中: 呼び出しのたびに実行される
    return client.get_item(...)

SDK のクライアントやデータベースの接続をハンドラの外で作っておけば、ウォームな環境では作り直さずに済みます。環境内の一時領域(/tmp)も、同じ環境が再利用される間は残ります。

ただし、ここに注意点があります。

  • 環境がいつまで残るかは保証されていません。残る前提で作ると、環境が無くなった呼び出しで失敗する
  • 同じ環境でも、前の呼び出しの状態が残っているので、呼び出し間で混ざってはいけない値(利用者ごとのデータなど)をグローバルに置かない
  • 別の呼び出しが同時に走るときは、別の環境が使われる。/tmp や変数は環境ごとに別物で、環境をまたいで共有されない

使い回しで気をつけたい 3 つ

  • 環境は永続しない。接続が切れていたら張り直せるようにしておく
  • 前の呼び出しの値が残る。利用者ごとのデータをグローバルに置かない
  • 環境ごとに /tmp も変数も別物。環境をまたいだ共有は当てにしない

「コールドスタートは必ず起きる」「起きない」といった言い方はできません。頻度は、リクエストの来方(まとまって来るか、間隔があくか)と同時実行数の増え方で変わります。

CHECK 2ここまでの確認

使い回される実行環境で、データベースへの接続オブジェクトはどう扱うのがよい?

答えと解説

答え: 2. ハンドラの外で作って再利用する。ただし環境がいつまでも残るとは限らないので、切れていたら張り直せるようにしておく

使い回しは効率化の手段ですが、環境は永続しません。再利用を前提にしつつ、無くなっても動く作りにします。

同時実行数はどう決まるのか

同時実行数とは、ある瞬間に処理中の呼び出しの数です。1 つの環境は同時に 1 回の呼び出ししか処理しないので、同時実行数はそのまま「並んでいる環境の数」になります。計算式は次のとおりです。

同時実行数 = 平均リクエスト数(毎秒) × 平均処理時間(秒)

たとえば毎秒 10 リクエスト、処理が 1 秒なら、同時実行数は 10 です。処理が 2 秒に延びれば、同じ毎秒 10 リクエストでも 20 になります。呼び出しの数が同じでも、処理が遅くなるだけで同時実行数は増える点が大事です。下流の遅延で処理時間が延び、それが上限への到達につながる、という連鎖は実務でよく起きます。

CONCURRENCY同時実行数の考え方

同時実行数は毎秒のリクエスト数 × 処理時間で決まる

  1. 毎秒のリクエスト数

    例: 100 件/秒

  2. 平均処理時間を掛ける

    例: 0.5 秒 → 同時に必要な環境は 50

  3. アカウント上限を全関数で分け合う

    超えた分はスロットリング

出典: AWS Lambda 公式ドキュメント(同時実行)。上限の既定値は公式で確認

上限は全関数で分け合う

同時実行にはリージョンごとのアカウント上限があり、アカウント内の全関数で分け合います。既定値は公式ドキュメントで確認してください(引き上げの申請もできます)。1 つの関数が取りすぎると、同じリージョンの別の関数が上限に当たります。上限を超えた新しいリクエストは、スロットリングされます。

同時実行数をあえて絞る

上限は「超えたら困る」ものですが、あえて小さく絞る使い方もあります。たとえば、キューで受けた処理を Lambda が取り出し、関数の同時実行数に上限をかけると、下流(モデルの API やデータベース)への呼び出し速度を平準化できます。急な投稿の集中でも、キューが受け止め、Lambda は決めた並列数でゆっくり処理します。失敗した分はデッドレターキューに送れば失われません。同時実行数は、守るための数値でもあります。

CHECK 3ここまでの確認

毎秒 100 リクエストが届き、1 回の処理に平均 0.5 秒かかる関数の、同時実行数は?

答えと解説

答え: 2. 約 50

同時実行数は「毎秒のリクエスト数 × 平均処理時間(秒)」なので 100 × 0.5 で 50 です。「同時実行数はどう決まるのか」の章の式を確かめてください。

予約済み同時実行とプロビジョニング済み同時実行は何が違うのか

名前が似ていますが、守っているものが違います。

観点 予約済み同時実行 プロビジョニング済み同時実行
何をするか その関数のために同時実行数の取り分を確保する 指定した数の実行環境を先に初期化しておく
上限の役割 取り分であり、同時にその関数の最大値でもある 取り分を確保する機能ではない
コールドスタート 減らさない(環境は温めない) 避けられる(先に Init が済んでいる)
追加料金 かからない かかる
向く目的 他の関数に食われないようにする。下流を守るため上限をかける 応答の遅れを許せない関数の起動を速くする

RESERVED VS PROVISIONED予約済みとプロビジョニング済み

取り分を守るか、先に温めるか

予約済み

  • 取り分の保証と上限
  • 追加料金なし
  • 環境は温めない

プロビジョニング済み

  • 先に初期化しておく
  • コールドスタートを避ける
  • 追加料金あり

何を守りたいか

出典: AWS Lambda 公式ドキュメント(同時実行)

選ぶときの軸は「何を守りたいか」です。他の関数に取り分を食われたくない、または下流を守るために上限を設けたいなら予約済み。起動の遅さを避けたいならプロビジョニング済み。目的が別なので、両方を使う構成もあります。

なお、プロビジョニング済み同時実行はバージョンかエイリアスに対して設定します。バージョンは公開後に変えられない固定の版で、エイリアスはどの版を指すかを切り替えるポインタです。そのため、エイリアスの向き先を新しいバージョンへ切り替えるときは、新しい版側にも温めた環境が必要になる点に目を配ってください。

CHECK 4ここまでの確認

コールドスタートを避けるために先に実行環境を初期化しておく機能は、どちら?

答えと解説

答え: 2. プロビジョニング済み同時実行(追加料金あり)

予約済みは取り分の保証と上限で、環境は温めません。先に温めるのはプロビジョニング済みです。「予約済みとプロビジョニング済みは何が違うのか」の章を読み直してください。

同期呼び出しと非同期呼び出しで、受け付けのされ方はどう違うのか

同時実行の上限に当たったとき、何が起きるかは呼び出し方式で違います。

  • 同期呼び出し: 呼び出し元が処理の完了を待つ。スロットリングされると、呼び出し元にエラーが返り、再試行するかは呼び出し元が決める
  • 非同期呼び出し: Lambda がイベントを内部のキューに入れ、受け付けた時点で成功(HTTP 202)を返す。関数の処理は後から、キューから取り出して行われる

非同期では、処理が失敗した場合や上限に当たった場合も、Lambda が再試行します。再試行を使い切ったイベントは、失敗時の送信先に設定した SQS キューなどに送れます。設定しなければイベントは失われるので、実運用では送信先を置きます。キューを使った再試行と冪等性の設計は、イベント駆動の設計で扱います。

遅い・止まるときは仕組みのどこを見るか

ここまでの仕組みを、症状から原因を切り分ける見方に直します。

症状 疑う場所 確かめること
たまに応答が遅い Init(コールドスタート) 遅い呼び出しだけ初期化の時間が長いか。パッケージ・初期化コードを軽くできるか
常に遅い 処理本体・下流 ハンドラの中の処理時間、外部呼び出しの待ち
同時に来ると一部が失敗する スロットリング 同時実行数の推移が上限に近づいていないか。他の関数が取り分を使っていないか
データベースの接続が増えすぎる 環境ごとの接続 同時実行数 ×(環境あたりの接続数)が下流の許容を超えていないか
外のサービスに出られず止まる ネットワーク VPC に接続した関数は、外へ出るにも NAT ゲートウェイ経由が要る

データベースの接続は、仕組みから説明がつきます。環境ごとに接続を持つので、同時実行数が増えるほど接続も増えます。上限を絞る、キューで平準化する、といった手が効く理由です。

VPC に接続した関数が外のサービスを呼ぶ場合、関数の環境のネットワークインターフェースにはパブリック IP が付きません。外へ出るにはプライベートサブネットから NAT ゲートウェイへ向ける経路が必要です。内部の通信は通るのに外部の API だけタイムアウトするときは、ここを疑います。

Cloudory 執筆者(AWS 認定 全冠)

「遅い」と「失敗する」は、別の場所の問題です。遅さは Init と処理本体、失敗は上限と下流の許容量。まずこの 2 つに分けて見ると、直す場所が絞れます。

まとめ

  • 呼び出しは、空き環境があれば Invoke だけ、無ければ Init から始まる。後者がコールドスタート。
  • ハンドラの外で作ったものは環境が再利用される間だけ残る。永続は前提にしない。
  • 同時実行数は、毎秒のリクエスト数 × 平均処理時間。全関数でアカウント上限を分け合う。
  • 予約済みは取り分と上限、プロビジョニング済みは先に温める。目的で選ぶ。
  • 非同期は Lambda のキューで受け付けられ、失敗時の送信先を置いて取りこぼしを防ぐ。

参照ソース

SCOREこの記事の確認テスト

0/4問 正解

各章の「ここまでの確認」に答えると、ここに集まります。結果はこの端末のブラウザにだけ残り、サーバには送りません。

PRACTICEこの記事の論点を練習問題で確かめる

この記事が扱うサービスの問題だけを続けて解けます。練習問題は Google でログインした人が使えます(練習問題の一覧)。

HAND IT OVER人に渡すなら

一言で説明するなら

Lambda は「注文のたびに作業台を用意する厨房」です。空いた台があればすぐ使えますが、無ければ台の組み立てから始まります。同時に来る注文の数だけ台が並ぶので、台の総数に上限があります。

FAQよくある質問

コールドスタートはなくせますか?

プロビジョニング済み同時実行で、先に実行環境を初期化しておけます。ただし追加料金がかかります。初期化コードやパッケージを軽くして Init を短くする方法もあります。

予約済みとプロビジョニング済みはどちらを使うべきですか?

取り分を守る、または上限をかけたいなら予約済み、起動の遅さを避けたいならプロビジョニング済みです。目的が別なので、両方を組み合わせる場合もあります。

同時実行の上限に達するとどうなりますか?

新しいリクエストはスロットリング(受け付けの拒否)されます。呼び出し方式によって、再試行の扱いが違います。同期は呼び出し元にエラーが返り、非同期は Lambda 側が再試行します。

SAME TRACK同じ領域の記事

ECS のタスク定義を設計する|サイズ・ロール・設定の渡し方を「1つのタスクに何を持たせるか」で決める
ECS のタスク定義を設計する|サイズ・ロール・設定の渡し方を「1つのタスクに何を持たせるか」で決める 段2 構成設計
ECS のタスクが止まる・デプロイが進まないときの調べ方|停止理由・サービスイベント・サーキットブレーカーを順に見る運用
ECS のタスクが止まる・デプロイが進まないときの調べ方|停止理由・サービスイベント・サーキットブレーカーを順に見る運用 段2 構成運用
Amazon EventBridge とは何か|イベントバス・ルール・ターゲットと SNS・SQS との使い分け
Amazon EventBridge とは何か|イベントバス・ルール・ターゲットと SNS・SQS との使い分け 段2 構成とは