目次
31 章

Lambda 深掘り — コールドスタート · SnapStart · パッケージング · 可観測性

17章 Lambda 基礎の上に production 運用の観点を加えます。コールドスタートと SnapStart · Provisioned Concurrency、Layers とコンテナイメージのパッケージング(FastAPI 1サイクル)、Lambda Powertools ベースの可観測性、Step Functions との結合、そして Lambda vs Fargate のコストのトレードオフを整理します。

第17章 Lambda 基礎 で関数を作りトリガーで呼び出す基本を押さえ、第18章 API Gateway + Lambda で HTTP の前段を付けました。この章はその上で、Lambda を production で運用するときにぶつかる深いテーマ を扱います — コールドスタート、パッケージング戦略、可観測性、そしていつ Lambda が ECS Fargate より優れるかです。

5部の最後のサービス章です。ここで扱うコールドスタートとコストのトレードオフは、本書のコンテナ優先路線(第15章 ECS と Fargate)とサーバーレス路線の境界を明確にしてくれます。6部 キャップストーン のフルスタックデプロイで、どの部分を Fargate に、どの部分を Lambda に置くかの判断根拠になります。

コールドスタート — Lambda の最初の呼び出しコスト #

Lambda は呼び出しがないとインスタンスを落とします。新しい呼び出しが来ると実行環境を新たに立ち上げますが、この過程が コールドスタート です。

コールドスタートの区間は次のとおりです。

  1. 実行環境(マイクロ VM)の生成
  2. ランタイムの初期化
  3. 関数コードのロード + 初期化コード(ハンドラの外)の実行

このあと同じインスタンスに来る呼び出しは ウォームスタート で速いです。コールドスタートを減らす方法は次のとおりです。

  • 初期化コードを減らす — ハンドラの外で重いライブラリを import したり大きな接続を結んだりすると、コールドスタートのたびにそのコストがかかります。本当に必要なものだけ置きます。(ただし、一度結んだ DB コネクションをハンドラの外に置いてウォームスタートで再利用するのは推奨されます。)
  • パッケージサイズを減らす — デプロイパッケージが小さいほどロードが速いです。
  • 言語選択 — Python / Node は初期化が軽く、JVM 系はコールドスタートが長いです(このとき下の SnapStart を検討)。
  • メモリを増やす — Lambda はメモリに比例して CPU が付きます。メモリを上げると初期化も速くなることがあり、ときにはメモリを上げる方が安く速いです。適正値は推測せず、Lambda Power Tuning で測定して選びます。

SnapStart — 初期化スナップショットの復元 #

SnapStart は、関数の初期化をあらかじめ一度実行して メモリ・ディスクのスナップショットを取っておき、呼び出し時にそのスナップショットを復元して初期化区間をスキップする機能です。追加コストなしでコールドスタートを大きく減らします。

  • 対応ランタイム: Java 11 以上、Python 3.12 以上、.NET 8 以上。(Node.js · Ruby などは未対応。)
  • コンテナイメージには適用されません。 zip パッケージング + 対応ランタイムの組み合わせでのみ動作します。つまり下の §「コンテナイメージ」パッケージングを選ぶと SnapStart は使えないので、コールドスタートが重要ならパッケージング選択から影響を受けます。
  • 初期化がスナップショット時点で一度だけ実行されるため、乱数シード・時刻・使い捨てトークンを初期化段階で固めてはいけません。 そうした値はランタイムフック(register_before_snapshot / register_after_restore)で復元時点に作り直します。
Python SnapStart ランタイムフック — 復元時にコネクションを再生成
from snapshot_restore_py import register_after_restore

@register_after_restore
def reinit_db():
    global conn
    conn = create_db_connection()   # スナップショットではなく復元直後に新規接続

SnapStart と Provisioned Concurrency はどちらもコールドスタートを減らしますが、SnapStart は 追加課金がなく(復元は無料)、PC は立ち上げておいた分だけ課金されます。そのため対応ランタイムなら SnapStart を先に検討し、それでも足りない超低遅延の経路に PC を載せます。

Provisioned Concurrency #

コールドスタートをそもそも無くす必要がある遅延に敏感な経路(決済、ユーザー対面の API)には Provisioned Concurrency(PC) を使います。指定した数だけの実行環境を あらかじめ初期化してウォーム状態で 維持します。

エイリアス + Provisioned Concurrency + トラフィックに応じた自動調整
resource "aws_lambda_alias" "live" {
  name             = "live"
  function_name    = aws_lambda_function.api.function_name
  function_version = aws_lambda_function.api.version
}

resource "aws_lambda_provisioned_concurrency_config" "live" {
  function_name                     = aws_lambda_function.api.function_name
  qualifier                         = aws_lambda_alias.live.name
  provisioned_concurrent_executions = 5
}
  • 長所: コールドスタートが消えます。
  • 短所: あらかじめ立ち上げておいた分だけ 呼び出しがなくても課金 されます。常時トラフィックが一定水準以上であれば、このときがまさに Lambda よりも ECS Fargate の方が安い区間 です。PC 量を Application Auto Scaling で時間帯別にスケジューリングすれば、夜間のコストを減らせます。

判断基準: トラフィックが断続的なら Lambda(オンデマンド課金)が、常時一定量以上で流れるなら Fargate(常時課金だが単価が低い)が有利です。PC を多く付ける必要があるなら、Fargate 移行を検討する合図です。

パッケージング — Layers とコンテナイメージ #

Lambda コードをまとめる方法は3つあります。

方法上限SnapStart適する場面
インライン / zip アップロード圧縮 50MB、展開 250MB可能小さな関数
Layers共通依存を分離した zip可能複数の関数が同じライブラリを共有
コンテナイメージ最大 10GB不可大きな依存、既存の Docker ワークフロー再利用

Layers #

複数の関数が同じライブラリ(例: 共通ユーティリティ、SDK 拡張、Powertools)を使うなら Layer に分離します。関数パッケージは軽くなり、ライブラリは一度だけ管理します。ただし Layer も展開 250MB の上限に一緒に含まれます。

コンテナイメージ — FastAPI 1サイクル #

依存が大きい場合(機械学習ライブラリなど)や、すでに Docker でビルドしているチームなら コンテナイメージ がすっきりします。第15章 ECS · 第16章 ECR で使ったコンテナワークフローをそのまま Lambda に再利用できます。ただし上で見たとおり コンテナイメージは SnapStart を使えないので、コールドスタートに敏感な関数なら zip + 対応ランタイムを検討します。FastAPI アプリを Lambda コンテナに載せる1サイクルは次のとおりです。

Dockerfile — FastAPI on Lambda
FROM public.ecr.aws/lambda/python:3.13

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY app/ ${LAMBDA_TASK_ROOT}/app/
# Mangum で ASGI(FastAPI)を Lambda ハンドラにアダプティング
CMD ["app.main.handler"]
app/main.py — Mangum アダプタ
from fastapi import FastAPI
from mangum import Mangum

app = FastAPI()

@app.get("/health")
def health():
    return {"status": "ok"}

handler = Mangum(app)  # API Gateway イベントを ASGI に変換
ECR でビルド・プッシュ後に Lambda 生成
docker build -t myapp-lambda .
docker tag myapp-lambda:latest \
  123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp-lambda:latest
docker push 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp-lambda:latest

aws lambda create-function \
  --function-name myapp \
  --package-type Image \
  --code ImageUri=123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/myapp-lambda:latest \
  --role arn:aws:iam::123456789012:role/myapp-lambda-role

第18章 API Gateway + Lambda の HTTP API を前に付ければ FastAPI アプリがサーバーレスで回ります。同じ FastAPI アプリを 第22章 インフラ骨格 では ECS Fargate に載せたので、両者を比べると「サーバーレス vs 常時コンテナ」のトレードオフが明確になります。

可観測性 — Lambda Powertools #

Lambda は短く生きて分散しているのでデバッグが難しいです。Lambda Powertools は3つを標準化します。

  • 構造化ロギング — JSON ログにリクエスト ID · コールドスタートの有無などを自動で付け、第7章 CloudWatch Logs Insights でクエリしやすくします。
  • メトリクス — ビジネスメトリクスを EMF(Embedded Metric Format)で残し、CloudWatch メトリクスとして自動集計します。
  • トレーシング — X-Ray と連携して API Gateway → Lambda → DynamoDB という1リクエストの流れを追跡します(第26章 モニタリング — CloudWatch アラームと X-Ray)。
Powertools — ロギング · メトリクス · トレーシングをまとめて
from aws_lambda_powertools import Logger, Tracer, Metrics
from aws_lambda_powertools.metrics import MetricUnit

logger = Logger()
tracer = Tracer()
metrics = Metrics()

@logger.inject_lambda_context     # リクエスト ID・コールドスタートを自動付与
@tracer.capture_lambda_handler    # X-Ray セグメント
@metrics.log_metrics              # EMF メトリクス flush
def handler(event, context):
    metrics.add_metric(name="OrderCreated", unit=MetricUnit.Count, value=1)
    logger.info("order created", extra={"order_id": event["id"]})
    return {"statusCode": 201}

Step Functions との結合 #

第21章 Step Functions で見たように、複数の段階を1つの Lambda の中に try/except で押し込まず、ステートマシン に分けます。Lambda 深掘りの観点での結合パターンは次のとおりです。

  • 各段階を小さな Lambda に置き、Step Functions が順序 · リトライ · 分岐を担います。
  • コールドスタートが問題になる段階だけ SnapStart または Provisioned Concurrency を付けます。
  • 長い待機(外部承認など)は Lambda を立ち上げておかず、Step Functions の待機状態(Wait / コールバックトークン)で処理してコストを抑えます。Lambda の最大実行時間は15分なので、それより長い流れは必ず Step Functions に分割します。

Lambda vs Fargate — 一行決定 #

シグナル選択
断続的 / イベントベースのトラフィックLambda(オンデマンド課金)
常時一定量以上のトラフィックFargate(単価が低い)
15分を超える作業Fargate(Lambda は最大15分)
コールドスタートに敏感 + 常時トラフィックFargate(または SnapStart / PC のコストを検討)
速いスパイクへの対応Lambda(即時拡張)
大きな依存(10GB イメージ)Lambda コンテナまたは Fargate(ただし Lambda コンテナは SnapStart 不可)

練習問題 #

  1. 自分のワークロードを1つ選び、Lambda と ECS Fargate のどちらが合うかを §「Lambda vs Fargate」表のシグナルで判断し、根拠を一段落で書いてみてください。Provisioned Concurrency を多く付ける必要がある状況なら、その結論がどう変わるかも書きます。
  2. コールドスタートに敏感な Python 関数があります。SnapStart とコンテナイメージのパッケージングを同時に使えるかを §「SnapStart」と §「パッケージング」表を根拠に答え、どちらを諦めるべきかとその理由を書いてみてください。
  3. SnapStart をオンにした関数が「初期化時に結んだ DB コネクションが復元後に切れている」という問題に遭遇します。§「SnapStart」のランタイムフックでどう解決するかをコードの流れで書いてみてください。

一行まとめ: Lambda コールドスタートは実行環境の生成・ランタイム・初期化コードのコストであり、初期化縮小・パッケージ縮小・メモリ増設で減らします。対応ランタイム(Java 11+/Python 3.12+/.NET 8+、ただしコンテナイメージ不可)なら追加課金のない SnapStart をまず使い、それでも足りない超低遅延の経路に課金される Provisioned Concurrency を載せます。PC を多く付ける必要があれば Fargate の方が安いシグナルです。パッケージングは zip / Layers / コンテナイメージ(最大 10GB、SnapStart 不可、FastAPI+Mangum で1サイクル)で、可観測性は Lambda Powertools で構造化ロギング・EMF メトリクス・X-Ray トレーシングを標準化します。15分を超える流れは Step Functions に分割し、断続トラフィックは Lambda・常時トラフィックは Fargate が一般的な基準です。

次の章 #

5部を終えます。次の 第32章 フルスタックアプリを AWS にデプロイする では、1 ~ 31章のすべてのサービスを1つに織り上げます。modern-react の Next.js アプリと modern-python の FastAPI アプリを ECS Fargate + RDS + S3 + CloudFront + Terraform で1つのアカウントにデプロイし、この章で整理した「どこを Fargate に、どこを Lambda に」の判断を実際のシステムに適用します。

X