👾

Lambda SnapStart でベンチマークをとって効果検証をしてみた

に公開

2024年11月22日に AWS Lambda の SnapStart が Python でも使えるようになっていたのですが、実際どれくらい効果があるのか気になって、ベンチマークツールを作って検証してみました。

https://aws.amazon.com/jp/blogs/aws/aws-lambda-snapstart-for-python-and-net-functions-is-now-generally-available/

結論から言うと、コールドスタート時間が 85% も改善しました!これはかなりインパクトがありますね。

Lambda SnapStart って何?

まず簡単に SnapStart について説明すると、Lambda のコールドスタート問題を解決するための機能です。

通常、Lambda 関数が起動するときは:

  1. 実行環境の準備
  2. ランタイムの起動
  3. コードのロード
  4. 初期化処理の実行

という手順を踏むんですが、特に Python で NumPy や Pandas みたいな重いライブラリを使うと、この初期化に数秒かかることがあります。

SnapStart は、この初期化済みの状態をスナップショットとして保存しておいて、次回起動時にそこから復元することで高速化する仕組みです。Firecracker microVM のスナップショット機能を使っているそうです。

なぜベンチマークを作ったのか

AWS のドキュメントには「大幅に改善します」と書いてあるけど、実際のところどれくらい速くなるの?という疑問がありました。

特に気になったのは:

  • Python でも本当に効果があるのか?
  • NumPy/Pandas みたいな重いライブラリでどれくらい改善するか?
  • 追加コストに見合う効果があるか?

ということで、実際に測定できるツールを Claude Code を使って作ってみました。
以下のコードはプロンプトを3回くらい入力しただけでできてしまったので驚きですね!

ベンチマークツールの構成

作ったベンチマークツールは、同じ Lambda 関数を SnapStart あり/なしの 2 パターンでデプロイして、パフォーマンスを比較する仕組みです。

Lambda 関数の中身

テスト用の Lambda 関数はこんな感じで、あえて重い処理を入れています:

# グローバルスコープでの初期化(SnapStartで最適化される部分)
import numpy as np
import pandas as pd
import boto3

# 重いライブラリのインポートとセットアップ
df = pd.DataFrame(np.random.randn(10000, 100))
df_summary = df.describe()

# AWS SDKクライアントの初期化
s3_client = boto3.client('s3')
dynamodb_client = boto3.client('dynamodb')
ssm_client = boto3.client('ssm')

# 疑似的な機械学習モデルの初期化
class DummyModel:
    def __init__(self):
        self.weights = np.random.randn(1000, 1000)
        self.bias = np.random.randn(1000)
        # 行列演算で時間を消費
        for _ in range(5):
            self.weights = np.dot(self.weights, self.weights.T) / 1000

model = DummyModel()

def lambda_handler(event, context):
    # ハンドラー内の処理は軽め
    input_data = np.random.randn(100)
    prediction = model.predict(input_data)
    return {"statusCode": 200, "body": json.dumps({...})}

ポイントは、初期化処理(グローバルスコープ)に重い処理を集中させていることです。これが SnapStart で最適化される部分になります。

デプロイ構成

CloudFormation で以下の 2 つの Lambda 関数を作成します:

  1. SnapStart なし: 通常の Lambda 関数
  2. SnapStart あり: SnapStart: ApplyOn: PublishedVersions を設定
# SnapStartありのLambda関数
LambdaFunctionWithSnapStart:
  Type: AWS::Lambda::Function
  Properties:
    FunctionName: !Sub '${AWS::StackName}-with-snapstart'
    Runtime: python3.12
    SnapStart:
      ApplyOn: PublishedVersions  # ← これがポイント!
    # ... その他の設定

PublishedVersions は簡単に言うと、「公開されたバージョンにのみ SnapStart を適用する」 という設定値です。

Lambda の 2 つの状態

  1. $LATEST(最新版)
    - 開発中の作業バージョン
    - コード更新のたびに変わる
    - SnapStart 使えない ❌
  2. Published Version(公開バージョン)
    - 固定されたバージョン(1, 2, 3...)
    - 一度公開すると変更不可
    - SnapStart 使える ✅

なぜ公開バージョンのみなのか?

$LATEST の問題:
・コードが頻繁に変更される
・スナップショット作ってもすぐ無効に
・無駄なコストが発生

PublishedVersions の利点:
・コードが固定で変わらない
・スナップショットが確実に有効
・本番環境で安定して使える

つまり、SnapStart は本番環境向けの機能として設計されており、開発中の頻繁に変更されるコードではなく、固定された安定版でのみ動作するようになっているようです。

実際にベンチマークを実行してみた

セットアップ

まず環境をデプロイします:

# リポジトリをクローン
git clone https://github.com/Mo3g4u/lambda-snapstart-benchmark
cd lambda-snapstart-benchmark

# デプロイスクリプトを実行
./deploy.sh

デプロイスクリプトが自動で:

  • S3 バケットを作成
  • 依存関係(NumPy、Pandas)をレイヤーとしてパッケージング
  • CloudFormation スタックをデプロイ
    してくれます。便利!

ベンチマークテストの実行

デプロイが完了したら、ベンチマークを実行:

python benchmark_test.py \
  --function-no-snapstart lambda-snapstart-benchmark-no-snapstart \
  --function-with-snapstart lambda-snapstart-benchmark-with-snapstart:live \
  --cold-tests 3 \
  --warm-tests 5 \
  --concurrency 5

このスクリプトは 3 種類のテストを実行します:

  1. コールドスタートテスト: 新しい実行環境での起動時間を測定
  2. ウォームスタートテスト: 既存の実行環境での実行時間を測定
  3. 同時実行テスト: 複数の同時リクエストでのパフォーマンスを測定

驚きの測定結果!

実際に測定してみた結果がこちら:

============================================================
テスト結果サマリー
============================================================

NO SNAPSTART:
  コールドスタート:
    平均: 2457.91ms
    中央値: 2384.19ms
    最小: 2358.27ms
    最大: 2631.26ms

WITH SNAPSTART:
  コールドスタート:
    平均: 353.93ms
    中央値: 56.93ms
    最小: 44.93ms
    最大: 959.94ms

============================================================
SnapStartによるコールドスタート改善率: 85.6%
  SnapStartなし平均: 2457.91ms
  SnapStartあり平均: 353.93ms
  短縮時間: 2103.97ms
============================================================

結果の解釈

コールドスタート時間が 2.5 秒から 0.35 秒に短縮!

これはすごい改善ですね。特に注目したいのは:

  1. 初回起動(最大値 959ms)

    • SnapStart ありでも初回は少し時間がかかる
    • これはスナップショットの初回ロードのため
  2. 2 回目以降(最小値 45ms)

    • キャッシュが効いて爆速に!
    • ほぼウォームスタートと同じレベル
  3. ウォームスタート(40-60ms)

    • SnapStart の有無で差はない(予想通り)

同時実行時のパフォーマンス

同時に 5 つのリクエストを送った場合:

  • SnapStart なし: 平均 124.70ms
  • SnapStart あり: 平均 60.91ms
  • 改善率: 51.2%

大量のトラフィックが来たときのスケーリングでも効果的ですね。

実際に使ってみた感想

良かった点

  1. 効果が明確: 85% の改善は期待以上でした
  2. 設定が簡単: CloudFormation に数行追加するだけ
  3. 既存コードの変更不要: 基本的にはそのまま使える

注意点

  1. コストが追加でかかる(Python の場合)

    • キャッシュ料金: $0.00002/GB-時間
    • リストア料金: $0.0000083/リクエスト
    • ただし、レスポンス改善を考えると十分ペイする
  2. ユニーク性の考慮が必要

    • UUID や乱数生成はハンドラー内で行う必要がある
    • スナップショットが複数の実行環境で共有されるため
  3. 初回起動は少し遅い

    • スナップショットの初回ロードに時間がかかる
    • でも 2 回目以降は爆速

どんな場合に使うべき?

実際に使ってみて、以下のケースで特に効果的だと感じました:

おすすめケース ✅

  • API のエンドポイント: レスポンスタイムが重要
  • 不規則なトラフィック: コールドスタートが頻発する
  • 重いライブラリを使用: NumPy、Pandas、機械学習ライブラリなど
  • ユーザー体験重視: 2 秒の待ち時間を許容できない

あまり向かないケース ❌

  • 常にウォーム: 頻繁にアクセスがある場合は不要
  • バッチ処理: レスポンスタイムが重要でない
  • 軽量な関数: そもそもコールドスタートが速い

まとめ

Lambda SnapStart、想像以上に効果がありました!特に Python で重いライブラリを使っている場合は、検討する価値が十分にあります。

今回作ったベンチマークツールは GitHub で公開しているので、ぜひ自分の環境でも試してみてください:
https://github.com/Mo3g4u/lambda-snapstart-benchmark

設定も簡単だし、効果も明確なので、コールドスタートに悩んでいる方はぜひ試してみることをおすすめします。

ちなみに、Java だと追加料金なしで使えるらしいので、Java ユーザーは迷わず有効化していいと思います!

参考リンク

レスキューナウテックブログ

Discussion