@shinyaz

AWS Lambda Rust公式サポートを検証 — Pythonとコールドスタート・実行速度を実測比較

目次

はじめに

2025年11月14日、AWSはLambda の Rust サポートを一般提供(GA)に昇格させた。これまで「実験的」だった Rust ランタイムが正式にサポート対象となり、AWS サポートと Lambda SLA の適用範囲に入った。

Rust は高パフォーマンスとメモリ安全性を両立する言語として注目されているが、Lambda での実際の効果はどの程度か。本記事では cargo-lambda を使って Rust 関数をデプロイし、同等の Python 関数とコールドスタート・実行速度・メモリ使用量を定量比較する。

「公式サポート」の実態

最初に理解しておくべきは、今回の GA は rust という専用マネージドランタイムが追加されたわけではない という点だ。Rust Lambda は引き続き provided.al2023(カスタムランタイム)上で動作し、ランタイム設定は Runtime: provided.al2023 / Handler: bootstrap となる。

変わったのは以下の点だ。

  • aws-lambda-rust-runtime クレートが GA — AWS が公式にメンテナンスし、SLA の対象になった
  • AWS サポートへの問い合わせが可能 — Rust Lambda の問題について AWS サポートケースを起票できる
  • 全リージョン対応 — GovCloud・中国リージョン含む全リージョンで利用可能

つまり「Python や Node.js と同列のマネージドランタイム」ではなく、「カスタムランタイム + 公式クレート」という構成が正式サポートされた形になる。

検証環境

項目
リージョンap-northeast-1(東京)
アーキテクチャarm64(Graviton)
メモリ128 MB
Rust バージョン1.94.0
cargo-lambda1.9.1
Python ランタイムpython3.13

ワークロードは CPU 負荷(フィボナッチ計算 n=40)とメモリ負荷(10万要素のベクタ割り当て)の複合パターンを使用した。

前提条件:

  • Rust ツールチェーン(rustup でインストール)
  • cargo-lambdapip3 install cargo-lambda でインストール可能)
  • AWS CLI セットアップ済み(lambda:*iam:* の操作権限)

プロジェクト作成からデプロイまで

Rust 関数の作成

cargo lambda new rust-lambda-bench --http でプロジェクトを生成し、Cargo.tomlserde / serde_json を追加する。main.rs はテンプレートそのままで変更不要だ(ハンドラを service_fn でラップして run に渡すだけのエントリポイント)。

ハンドラの核心部分はシンプルだ。クエリパラメータで n を受け取り、フィボナッチ計算とベクタ割り当ての処理時間を計測してJSONで返す。

src/http_handler.rs(ハンドラ部分)
pub(crate) async fn function_handler(event: Request) -> Result<Response<Body>, Error> {
    let n: u32 = event
        .query_string_parameters_ref()
        .and_then(|params| params.first("n"))
        .and_then(|v| v.parse().ok())
        .unwrap_or(40);
 
    let total_start = Instant::now();
 
    // CPU負荷: フィボナッチ計算
    let compute_start = Instant::now();
    let fib_result = fibonacci(n);
    let compute_ms = compute_start.elapsed().as_secs_f64() * 1000.0;
 
    // メモリ負荷: 10万要素のベクタ割り当て
    let alloc_start = Instant::now();
    let items: Vec<u64> = (0..100_000).map(|i| i * i).collect();
    let alloc_ms = alloc_start.elapsed().as_secs_f64() * 1000.0;
 
    let total_ms = total_start.elapsed().as_secs_f64() * 1000.0;
 
    // BenchResult構造体にまとめてJSON返却
    let result = BenchResult {
        runtime: "rust".to_string(),
        fib_result, fib_n: n, compute_ms,
        alloc_items: items.len(), alloc_ms, total_ms,
    };
    let body = serde_json::to_string(&result)?;
    // ... Response構築 ...
}
完全なRust関数コード(Cargo.toml + 全ソース)
Cargo.toml
[package]
name = "rust-lambda-bench"
version = "0.1.0"
edition = "2021"
 
[dependencies]
lambda_http = "1.0.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["macros"] }
src/http_handler.rs
use lambda_http::{Body, Error, Request, RequestExt, Response};
use serde::Serialize;
use std::time::Instant;
 
#[derive(Serialize)]
struct BenchResult {
    runtime: String,
    fib_result: u64,
    fib_n: u32,
    compute_ms: f64,
    alloc_items: usize,
    alloc_ms: f64,
    total_ms: f64,
}
 
fn fibonacci(n: u32) -> u64 {
    if n <= 1 {
        return n as u64;
    }
    let (mut a, mut b) = (0u64, 1u64);
    for _ in 2..=n {
        let tmp = a + b;
        a = b;
        b = tmp;
    }
    b
}
 
pub(crate) async fn function_handler(event: Request) -> Result<Response<Body>, Error> {
    let n: u32 = event
        .query_string_parameters_ref()
        .and_then(|params| params.first("n"))
        .and_then(|v| v.parse().ok())
        .unwrap_or(40);
 
    let total_start = Instant::now();
 
    let compute_start = Instant::now();
    let fib_result = fibonacci(n);
    let compute_ms = compute_start.elapsed().as_secs_f64() * 1000.0;
 
    let alloc_start = Instant::now();
    let items: Vec<u64> = (0..100_000).map(|i| i * i).collect();
    let alloc_ms = alloc_start.elapsed().as_secs_f64() * 1000.0;
 
    let total_ms = total_start.elapsed().as_secs_f64() * 1000.0;
 
    let result = BenchResult {
        runtime: "rust".to_string(),
        fib_result, fib_n: n, compute_ms,
        alloc_items: items.len(), alloc_ms, total_ms,
    };
 
    let body = serde_json::to_string(&result)?;
    let resp = Response::builder()
        .status(200)
        .header("content-type", "application/json")
        .body(body.into())
        .map_err(Box::new)?;
    Ok(resp)
}

Python 比較関数

Rust と同じワークロード(フィボナッチ + 10万要素割り当て)を Python で実装する。

Python比較関数コード
lambda_function.py
import json
import time
 
def fibonacci(n):
    if n <= 1:
        return n
    a, b = 0, 1
    for _ in range(2, n + 1):
        a, b = b, a + b
    return b
 
def lambda_handler(event, context):
    params = event.get("queryStringParameters") or {}
    n = int(params.get("n", 40))
 
    total_start = time.perf_counter()
 
    compute_start = time.perf_counter()
    fib_result = fibonacci(n)
    compute_ms = (time.perf_counter() - compute_start) * 1000
 
    alloc_start = time.perf_counter()
    items = [i * i for i in range(100_000)]
    alloc_ms = (time.perf_counter() - alloc_start) * 1000
 
    total_ms = (time.perf_counter() - total_start) * 1000
 
    return {
        "statusCode": 200,
        "headers": {"content-type": "application/json"},
        "body": json.dumps({
            "runtime": "python", "fib_result": fib_result, "fib_n": n,
            "compute_ms": round(compute_ms, 4),
            "alloc_items": len(items), "alloc_ms": round(alloc_ms, 4),
            "total_ms": round(total_ms, 4),
        }),
    }

ビルドとデプロイ

デプロイには AWSLambdaBasicExecutionRole をアタッチした IAM ロールが必要だ。Rust 関数は cargo-lambda の builddeploy で完結し、ARM64 向けクロスコンパイルも自動で処理される。Python 関数は zip で固めて AWS CLI でデプロイする。

デプロイ手順
ターミナル(デプロイ)
# IAM ロール作成(未作成の場合)
aws iam create-role --role-name lambda-rust-bench-role \
  --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"},"Action":"sts:AssumeRole"}]}'
aws iam attach-role-policy --role-name lambda-rust-bench-role \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
 
# Rust: ビルド → デプロイ(Function URL 有効化)
cargo lambda build --release --arm64
cargo lambda deploy rust-lambda-bench \
  --iam-role arn:aws:iam::ACCOUNT_ID:role/lambda-rust-bench-role \
  --region ap-northeast-1 \
  --memory 128 \
  --enable-function-url
 
# Python: zip → デプロイ
zip -j function.zip lambda_function.py
aws lambda create-function \
  --function-name python-lambda-bench \
  --runtime python3.13 \
  --handler lambda_function.lambda_handler \
  --role arn:aws:iam::ACCOUNT_ID:role/lambda-rust-bench-role \
  --zip-file fileb://function.zip \
  --memory-size 128 --architectures arm64 \
  --region ap-northeast-1

Rust の初回ビルドは約23秒。デプロイまで含めても1分以内で完了した。cargo-lambda が Zig をクロスコンパイラとして使い、ホスト環境に依存せず ARM64 バイナリを生成してくれる点が非常に便利だ。

ベンチマーク結果

コールドスタート

Lambda は実行環境の設定を変更するとコンテナが再作成される。環境変数を更新して強制的にコールドスタートを発生させ、更新完了後の初回呼び出しで計測した。--log-type Tail を指定すると、レスポンスに REPORT 行(Duration、Billed Duration、Init Duration、Max Memory Used)が含まれる。

ベンチマーク実行手順
ターミナル(計測)
# コールドスタート強制
aws lambda update-function-configuration \
  --function-name rust-lambda-bench \
  --environment "Variables={BENCH_RUN=$(date +%s)}" \
  --region ap-northeast-1
aws lambda wait function-updated \
  --function-name rust-lambda-bench --region ap-northeast-1
 
# 呼び出し + REPORT 取得
aws lambda invoke \
  --function-name rust-lambda-bench \
  --cli-binary-format raw-in-base64-out \
  --payload '{"requestContext":{"http":{"method":"GET"}},"queryStringParameters":{"n":"40"},"rawPath":"/","headers":{}}' \
  --region ap-northeast-1 --log-type Tail \
  --query 'LogResult' --output text \
  /tmp/result.json | base64 -d | grep REPORT
 
# レスポンスボディ確認
cat /tmp/result.json

Python 側も同じ手順で --function-name python-lambda-bench に変えて実行する。ウォームスタートはコールドスタート後に同じ invoke コマンドを5回連続実行して計測した。

指標RustPython
Init Duration28.96 ms99.76 msRust が3.4倍高速
Duration(初回実行)1.77 ms167.47 msRust が95倍高速
Billed Duration31 ms268 msRust が8.6倍効率的
Max Memory Used16 MB41 MBRust が2.6倍効率的

Rust のコールドスタート(Init Duration)は約29ms。Python の約100ms と比較して3倍以上高速だ。さらに注目すべきは初回実行時の Duration で、Python の167ms に対して Rust は1.77ms と95倍の差がついた。

ウォームスタート

コールドスタート後の連続5回呼び出しで計測。

指標RustPython
Duration 平均1.22 ms108.21 msRust が89倍高速
Duration 最小1.13 ms90.58 msRust が80倍高速
Duration 最大1.39 ms129.14 msRust が93倍高速
Billed Duration 平均2 ms109 msRust が55倍効率的
Max Memory Used16 MB41 MBRust が2.6倍効率的

ウォームスタートでも約90倍の差が維持された。Rust は5回とも Duration が1.1〜1.4ms の範囲に収まり、分散が極めて小さい。一方 Python は90〜129ms とばらつきが大きかった。

アプリケーションレベルの処理時間

Lambda の Duration はランタイムオーバーヘッドを含む値だが、アプリケーションコード内で計測した処理時間も確認した。

処理RustPython
フィボナッチ(n=40)0.00008 ms0.008 ms
10万要素割り当て0.00003 ms70.0 ms
合計0.0003 ms70.0 ms

メモリ割り当て処理で圧倒的な差が出た。Python のリスト内包表記による10万要素の生成に70ms かかる一方、Rust の Vec は0.03μs で完了している。ゼロコスト抽象化とスタック/ヒープの最適化が効いている。

パッケージサイズ

項目RustPython
デプロイパッケージ1.2 MB641 B
ローカルバイナリ2.4 MB

Rust はバイナリに全ての依存を含むため1.2MB になるが、Lambda のコールドスタートに影響するほどのサイズではない。Python は標準ライブラリのみで構成されるため極小だが、実際のプロジェクトでは requirements.txt の依存が加わり差は縮まる。

コスト試算

128MB / ARM64 の東京リージョン料金($0.0000133334/GB-秒)で月100万回呼び出しを想定した場合。無料利用枠(月40万GB-秒)とリクエスト料金($0.20/100万件、両者共通)は除外している。なお月100万回・128MB 程度であれば両者とも無料枠内に収まるため、ここではスケール時の単価差を示すことが目的だ。

項目RustPython
Billed Duration 平均2 ms109 ms
月間コンピュート料金$0.0033$0.1817
約55倍

実行時間の差がそのままコストに反映される。高頻度で呼ばれる関数ほど Rust の経済的メリットが大きい。

導入時の注意点

コンパイル時間 — Rust の初回ビルドは依存クレートのコンパイルに時間がかかる(今回は23秒)。CI/CD ではキャッシュの活用が必須だ。

学習コスト — 所有権・ライフタイムなど Rust 固有の概念は、チームへの導入障壁になりうる。Lambda 関数は比較的小さいコードベースのため、Rust 入門としては適切なスコープだろう。

デバッグ体験cargo lambda watch でローカル実行できるが、Python の print デバッグに比べるとフィードバックループは長い。

ランタイムの位置づけprovided.al2023 上で動作するため、AWS コンソール上では「カスタムランタイム」として表示される。Python や Node.js のようなインラインエディタは使えない。

まとめ

  • コールドスタート29ms は本番投入可能な水準 — API Gateway 背後の同期 API でもユーザー体感に影響しないレベルだ
  • 90倍の実行速度差はコスト直結 — 高頻度関数で Rust を採用するだけで Lambda コストを大幅に削減できる
  • cargo-lambda で DX は十分実用的 — ビルド・デプロイ・ローカルテストが1コマンドで完結し、Rust の複雑さを適切に隠蔽している
  • 「マネージドランタイム」ではない点を理解する — GA はクレートとサポート体制の正式化であり、provided.al2023 + bootstrap バイナリという構成は変わらない

クリーンアップ

検証後は Lambda 関数と IAM ロールを削除する。

ターミナル
aws lambda delete-function-url-config --function-name rust-lambda-bench --region ap-northeast-1
aws lambda delete-function --function-name rust-lambda-bench --region ap-northeast-1
aws lambda delete-function --function-name python-lambda-bench --region ap-northeast-1
aws iam detach-role-policy --role-name lambda-rust-bench-role \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
aws iam delete-role --role-name lambda-rust-bench-role

共有する

田原 慎也

田原 慎也

ソリューションアーキテクト @ AWS

AWS ソリューションアーキテクトとして金融業界のお客様を中心に技術支援を行っています。クラウドアーキテクチャや AI/ML に関する学びをこのサイトで発信しています。このサイトの内容は個人の見解であり、所属企業の公式な意見や見解を代表するものではありません。

関連記事