メインコンテンツへスキップ
友田 陽大
AWS Lambda 本番運用
AWS
Lambda
サーバーレス
アーキテクチャ設計
冪等性

AWS Lambda 本番運用ガイド:実行モデル・冪等性・可観測性・セキュリティ・コストを公式仕様で固める

AWS Lambdaを本番品質で設計・運用するための実装ガイド。3フェーズの実行環境ライフサイクル、ハンドラ外での接続再利用、ZIP/コンテナ/レイヤーのパッケージング、同期/非同期/イベントソースの失敗設計、Powertoolsによる冪等化、構造化ログ/X-Ray、最小権限とSecrets、Arm64とメモリ調整のコスト最適化までを、AWS公式仕様に忠実な実コードで解説します。

公開日
読了時間
23分
著者
友田 陽大
シェア

「サーバー管理から解放されて、イベントに反応するコードだけ書きたい」——AWS Lambda を選ぶ動機は一行で言えます。けれど本番に載せた瞬間、問いが一気に増えます。同じイベントが2回来たら? 起動が遅い(コールドスタート)のはなぜ? DBコネクションは毎回張り直すのか? 失敗したイベントはどこへ消えるのか? 秘密情報はどこに置くのか? そして請求書はなぜこの金額なのか?

この記事は、AWS Lambda を本番品質で設計・運用するための実装ガイドです。題材として、私が中核開発者(主要開発者3名)として構築した、環境・カーボンクレジット/地域通貨のマルチテナント決済プラットフォーム(サーバーレス決済プラットフォームの信頼性設計)での設計判断も交えます。本番稼働中の二重課金・残高不整合 0件を、冪等性・失敗設計・可観測性で支えた実例です。

この記事のルール:Lambda の仕様・パラメータ名・既定値・クォータは AWS 公式ドキュメント(2026年6月時点) に基づきます。クォータや料金は改定され、リージョンや新規アカウントで初期値が異なります。本番投入前に必ず公式の各ページ(末尾「参考」)で最新値を確認してください。最重要の前提:Lambda は「少なくとも1回(at-least-once)」起動されうる。リトライは異常ではなく仕様です。だから関数を冪等に作るのが、信頼性設計の出発点になります。


0. メンタルモデル:Lambda=「イベント → 使い捨ての実行環境 → 使った分だけ課金」

最初に、この記事を貫く5つのメンタルモデルを固定します。ここを腹落ちさせれば、あとの実装はすべてこの帰結です。

  • 関数=イベントハンドラ。Lambda は「リクエストを受けたら実行環境を1つ用意し、コードを走らせ、終わったら凍結(または破棄)する」サービス。サーバーは見えない。
  • 実行環境=使い捨て、でも一定時間は再利用される。同時リクエストごとに別々の実行環境が割り当てられる(=同時実行数)。1つの環境は終わると凍結され、しばらく次の呼び出しで再利用(ウォームスタート)される。
  • ハンドラ外=1度だけ、ハンドラ内=毎回。コールドスタート時にハンドラ外のコード(INIT)が1度走り、以降のウォーム呼び出しではハンドラ内だけが走る。だから接続やSDKクライアントはハンドラ外で張る(第2章)。
  • 再試行は前提。非同期呼び出しもイベントソースも at-least-once。「2回来ても結果が同じ」になるよう冪等に作る(第7章)。
  • 課金は『割当メモリ × 実行時間(1ms単位)+ リクエスト数』。速くする=安くする、が基本的に一致する(第10章)。

適材適所:Lambda は「イベント駆動・スパイク追従・スケールトゥゼロ」に強く、標準関数の最大実行時間は900秒(15分)・同期ペイロードは6MBという制約があります。長時間処理・常駐サーバー・任意TCP/UDP が要るなら Fargate が本命です。どの計算基盤を選ぶかは姉妹記事 AWS Fargate vs Lambda vs App Runner 技術選定ガイド に委ね、本稿は**「Lambda を選んだ後、どう本番で作るか」**に集中します。


1. 実行環境のライフサイクル:3フェーズと「ハンドラ外」の意味

Lambda の挙動を支配するのは、実行環境(execution environment)のライフサイクルです。公式は3フェーズで説明します。

フェーズ何が起きるかいつ
Init① Extension init(拡張の起動)② Runtime init(ランタイム起動)③ Function init(ハンドラの静的コード実行)コールドスタート時に1度
Invokeハンドラ関数の実行。関数のタイムアウトはこのフェーズ全体に効くリクエストごと
Shutdown一定時間呼び出しが無いと環境を破棄。拡張に猶予を与えるアイドル後

ここで本番運用に効く事実を3つ押さえます(すべて公式に基づく)。

  • Init には10秒の制限がある(標準のオンデマンド関数)。ハンドラ外の初期化が10秒を超えると、最初の呼び出しでタイムアウト扱いになりリトライされる。重い初期化は要注意。
  • コールドスタートは全呼び出しの1%未満に収まるのが典型で、所要は「100ms未満〜1秒超」と幅がある。頻度より**裾の重さ(P99)**が問題になる。詳しくは姉妹記事 Lambda コールドスタート最適化ガイド で深掘りします。
  • 2025年8月1日から、INIT 時間も課金対象に統一された。以前は「マネージドランタイム+ZIP のオンデマンド関数」だけ INIT が無課金だったが、現在は全構成で INIT が Billed Duration に含まれる(カスタムランタイム・プロビジョンド同時実行・コンテナイメージは従来から課金)。コールドスタートの初期化はもうタダではない——これは第10章のコスト設計に直結します。

1.1 「ハンドラ外で接続を張る」が最重要パターン

凍結された実行環境はメモリ状態ごと再利用されます。公式も明言する通り、ハンドラ外で宣言したオブジェクトはウォーム呼び出し間で生き続ける。だから DB 接続・SDK クライアントはハンドラで1度だけ初期化し、再利用します。これを怠ると毎回接続を張り直し、レイテンシとコストとコネクション枯渇を招きます。

// handler.ts — 「薄いアダプタ」。重い初期化はハンドラ外(INIT)で1度だけ。
import { Logger } from "@aws-lambda-powertools/logger";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
import type { SQSEvent, SQSBatchResponse, Context } from "aws-lambda";

// ── INIT フェーズ:コールドスタート時に1度だけ実行され、ウォーム呼び出しで再利用される ──
const logger = new Logger({ serviceName: "orders-worker" });
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient({})); // 接続はここで張る
const TABLE = process.env.ORDERS_TABLE; // 設定の読み出しもここで(毎回 process.env を引かない)

// ── INVOKE フェーズ:リクエストごとにここだけが走る ──
export const handler = async (
  event: SQSEvent,
  _context: Context,
): Promise<SQSBatchResponse> => {
  if (!TABLE) throw new Error("ORDERS_TABLE is not configured"); // 起動時設定の検証
  const batchItemFailures: { itemIdentifier: string }[] = [];

  for (const record of event.Records) {
    try {
      await processOrder(JSON.parse(record.body), { ddb, table: TABLE, logger });
    } catch (err) {
      // 失敗した messageId だけを再表示させる(部分バッチ失敗。第6章)
      logger.error("record processing failed", { messageId: record.messageId, err });
      batchItemFailures.push({ itemIdentifier: record.messageId });
    }
  }
  return { batchItemFailures };
};

注意:ハンドラ外の状態は「再利用されることがある」だけで、保証はされない。コールドスタートのたびにリセットされ、同時実行ごとに別環境になります。だから「グローバル変数をキャッシュ代わりに使う」のは可(高速化)でも、「グローバル変数を一意性や整合性の拠り所にする」のは不可。一意なIDやトークンはハンドラ内で都度生成します(SnapStart では特に重要——姉妹記事参照)。


2. ハンドラの作り方:薄いアダプタ+テスト容易性

ハンドラは「イベントを受け取り、ビジネスロジックに渡す薄いアダプタ」に徹します。これが SRP(単一責任)であり、テスト容易性の鍵です。

公式の標準シグネチャは言語ごとに決まっています。

// Node.js(推奨は async ハンドラ。Node.js 24 以降は callback 形式は不可)
export const handler = async (event: MyEvent, context: Context): Promise<MyResult> => { /* ... */ };
# Python(async ハンドラは不可。引数は必ず event, context の2つ)
def lambda_handler(event, context):
    ...

context には運用で効く情報が入っています。context.aws_request_id(Python)/ context.awsRequestId(Node.js)は相関IDとして全ログに通す。get_remaining_time_in_millis() / getRemainingTimeInMillis()残り時間を返すので、タイムアウト直前に安全に切り上げる制御に使えます。

2.1 ビジネスロジックは「Lambda を知らない関数」に切り出す

ハンドラ=アダプタ、ロジック=純粋関数。こう分けると、ロジックは Lambda ランタイム無しで単体テストできます(第11章)。AWS のテストガイドも「ハンドラは slim adapter にし、ビジネスロジックを分離して単体テストせよ」と推奨しています。

# domain.py — Lambda を一切 import しない純粋ロジック。単体テストが容易。
from dataclasses import dataclass

@dataclass(frozen=True)
class Order:
    id: str
    amount: int  # 最小通貨単位(整数)。金額に float を使わない

def total_with_tax(order: Order, tax_rate: float) -> int:
    if order.amount < 0:
        raise ValueError("amount must be non-negative")  # 境界で検証
    return round(order.amount * (1 + tax_rate))
# handler.py — 薄いアダプタ。境界での検証+ロジック呼び出しに専念。
import json
from domain import Order, total_with_tax

def lambda_handler(event, context):
    body = json.loads(event["body"])                 # 外部入力は境界でパース
    order = Order(id=str(body["id"]), amount=int(body["amount"]))
    return {"statusCode": 200, "body": json.dumps({"total": total_with_tax(order, 0.10)})}

3. パッケージング:ZIP / コンテナ / レイヤー

デプロイ方式は2つ、依存の共有手段が1つあります。判断軸を表で固定します。

方式サイズ上限(公式)向くケース
ZIP アーカイブ直接アップロード50MB / 解凍後250MB(レイヤー・カスタムランタイム込み)一般的な関数。最速のデプロイ。レイヤーが使える
コンテナイメージ(OCI)最大10GB大きな依存(MLライブラリ等)、独自OS/ランタイム、既存コンテナCIとの統合
レイヤー関数あたり最大5個/opt に展開、解凍後250MBの枠を共有共通依存・共通ユーティリティをZIP関数群で共有

実務の指針:

  • まずは ZIP。デプロイが速く、レイヤーで共通依存を切り出せる。250MBの解凍後上限に当たったらコンテナへ。
  • コンテナは「大きい・特殊・既存資産」のとき。10GBまで積めるが、コールドスタートと運用は重くなりがち。レイヤーは使えない(依存はイメージに同梱)。
  • Go / Rust ではレイヤー非推奨(公式)。実行ファイルに依存を同梱した方がコールドスタートに有利。

アンチパターンnode_modules を丸ごと固める。AWS SDK v3 は必要なサブクライアントだけ import し、不要な依存を削るとパッケージが小さくなり、ダウンロード=コールドスタートが速くなります。バンドラ(esbuild 等)でツリーシェイクするのが定石です。


4. 同時実行とスケール:予約・プロビジョンド・スロットリング

Lambda の「スケール」を数字で理解します。

  • 同時実行数(concurrency)=同時に処理中のリクエスト数。1リクエストにつき実行環境1つ。
  • リトルの法則同時実行数 = 平均リクエスト/秒 × 平均処理時間(秒)。例:100 req/s × 0.5s = 50。これが必要な同時実行数の見積もり式。
  • アカウント既定の同時実行上限は1,000(リージョン単位、引き上げ申請可)。さらにRPS上限はアカウント同時実行の10倍(既定なら10,000 RPS)——処理が数十msと短い関数で効いてきます。
  • スケール速度は「1関数あたり10秒で1,000環境ずつ」。各関数が独立してこの速度でバーストできる(リージョン共有の旧バースト枠は撤廃済み)。

同時実行の制御レバーは2つあります。混同しないよう表で固定します。

レバー何をするか課金主な用途
予約済み同時実行(Reserved)関数の同時実行に上限と下限を設定。他関数はその枠を使えない追加課金なし(ただしアカウント枠を消費)下流(RDS等)の保護、暴走の封じ込め、0で緊急停止(キルスイッチ)
プロビジョンド同時実行(Provisioned)環境を事前に初期化しておきコールドスタートを消す追加課金あり二桁ミリ秒の応答が必須のレイテンシ重視API

スロットリング:同時実行(または10倍のRPS)上限を超えると、同期呼び出しは HTTP 429(TooManyRequestsException を返します。予約済み同時実行 = 0 にすると全リクエストを 429 で弾くので、インシデント時のキルスイッチとして使えます。非同期・イベントソースはスロットル時に内部キューで保持・再試行されます(第6章)。

プロビジョンド同時実行・SnapStart・コールドスタートの使い分けと費用対効果は、これだけで一本の記事になります。Lambda コールドスタート最適化ガイド に詳細を分離しました。


5. 呼び出しモデルと失敗の扱い:sync / async / イベントソース

「誰がリトライするのか」は呼び出しモデルで決まります。ここを取り違えると、失敗が静かに消える永遠に詰まるかのどちらかになります。

モデルリトライの主体
同期(RequestResponse)API Gateway、Function URL、直接 InvokeLambda は自動リトライしない。呼び出し側が判断
非同期(Event)S3通知、SNS、EventBridge ルールLambda が内部キューで再試行(既定2回・計3回)
イベントソースマッピングSQS、Kinesis、DynamoDB StreamsLambda がポーリングして同期起動。再試行はソース仕様に従う

5.1 非同期呼び出し:既定2回リトライ、証跡は Destinations で受ける

非同期呼び出しは内部キューに入り、関数エラー時に**既定で2回再試行(計3回)**します(1回目まで1分、2回目まで2分待つ)。制御パラメータは2つ:

  • MaximumRetryAttempts0〜2(既定2)。
  • MaximumEventAgeInSeconds60〜21600(=最大6時間)。古すぎるイベントは捨てる。

失敗イベントの行き先は、旧来のDLQ(SQS/SNSのみ)ではなく On-failure Destinations を推奨します。Destinations は **SQS / SNS / Lambda / EventBridge(失敗時はS3も)**を宛先にでき、関数のレスポンス詳細も記録されます。成功時の宛先(on-success)も指定できます。

# Terraform: 非同期呼び出しのリトライ上限・イベント寿命・失敗時の宛先
resource "aws_lambda_function_event_invoke_config" "orders" {
  function_name                = aws_lambda_function.orders.function_name
  maximum_retry_attempts       = 2      # 0〜2(既定2)
  maximum_event_age_in_seconds = 3600   # 60〜21600。1時間より古い失敗は捨てる

  destination_config {
    on_failure {
      destination = aws_sqs_queue.orders_dlq.arn # 調査・再処理できる隔離先へ
    }
  }
}

5.2 イベントソース(SQS/ストリーム):部分バッチ失敗で「全部やり直し」を防ぐ

イベントソースマッピングは関数をバッチで起動します。ここで初学者が必ず踏む地雷:バッチ処理中に例外を投げると、成功済みも含めてバッチ全体が再処理される

これを防ぐのが部分バッチ失敗(partial batch response)FunctionResponseTypes=ReportBatchItemFailures を設定し、関数は失敗した ID だけbatchItemFailures に返します(第1章のTSコード参照)。SQS・Kinesis・DynamoDB Streams で共通の仕組みです。ストリームでは BisectBatchOnFunctionError(失敗時にバッチを二分割して再試行)や MaximumRetryAttempts / MaximumRecordAgeInSeconds、On-failure 宛先で「毒レコードがシャードを最大7日詰まらせる」事故を防ぎます。

SQS×Lambda の冪等consumer・可視性タイムアウト・DLQ 設計の詳細は SQS + Lambda + EventBridge で冪等な非同期処理を作る、DynamoDB Streams を起点にした CDC/イベント駆動は DynamoDB Streams × Lambda イベント駆動アーキテクチャ に、それぞれ専用記事があります。


6. 冪等性:再試行を「正常系」に変える

at-least-once の世界では、冪等性こそが信頼性の土台です。「同じイベントが2回来ても、課金は1回・在庫引当は1回」にする。これを自前で作ると、冪等性キーの条件付き挿入・TTL・進行中ロックを正しく実装する必要があり、競合状態で穴が開きがちです。

新規実装なら Powertools for AWS Lambda の冪等化ユーティリティが定石です。ペイロード(または特定フィールド)から冪等性キーを導出し、DynamoDB に処理状態を記録。進行中は INPROGRESS レコードでロックし、並行する重複呼び出しを弾く。完了後は結果をキャッシュして返します。

# Python: Powertools の冪等化。デコレータ1つで「2回来ても1回」にする。
from aws_lambda_powertools.utilities.idempotency import (
    DynamoDBPersistenceLayer, idempotent_function, IdempotencyConfig,
)

persistence = DynamoDBPersistenceLayer(table_name="idempotency")
# 全ペイロードではなく注文IDだけを冪等性キーにする(再送で payload が微妙に変わっても同一視)
config = IdempotencyConfig(event_key_jmespath="id", expires_after_seconds=3600)

@idempotent_function(data_keyword_argument="order", persistence_store=persistence, config=config)
def charge(order: dict) -> dict:
    # ここが2回呼ばれても、課金は1回しか実行されない
    return run_charge(order)
// TypeScript: @aws-lambda-powertools/idempotency(peer依存に @aws-sdk/client-dynamodb と lib-dynamodb)
import { makeIdempotent } from "@aws-lambda-powertools/idempotency";
import { DynamoDBPersistenceLayer } from "@aws-lambda-powertools/idempotency/dynamodb";

const persistenceStore = new DynamoDBPersistenceLayer({ tableName: "idempotency" });
export const handler = makeIdempotent(async (event: { id: string }) => runCharge(event), {
  persistenceStore,
});

設計の勘所:冪等性キーは「同じ業務操作なら同じ値」になるよう選ぶ(注文ID・請求期間など)。全ペイロードをキーにすると、リトライ時にタイムスタンプ等が混ざって別物と誤認します。TTL(既定3600秒)は「再送が来うる窓」より長く。私が決済プラットフォームで二重課金0件を達成できたのは、この冪等性キー設計を業務の不変条件に合わせたからです。冪等化の理論的背景は 冪等な決済設計(二重課金対策) も参照ください。


7. 可観測性:コード変更ゼロの構造化ログ+メトリクス+トレース

「止まった処理を一目で追える」状態を作ります。Lambda は CloudWatch Logs に自動でログを送ります(実行ロールに AWSLambdaBasicExecutionRole が必要)。本番では3点セットで固めます。

  • JSON 構造化ログ+ログレベル制御(コード変更不要)。Lambda の「高度なロギング設定」で、ログ形式(Text/JSON)とアプリケーションログレベル(TRACE〜FATAL、既定INFO)を関数設定で切り替えられます(JSON形式が前提)。console.log を JSON で検索・集計可能にし、本番は INFO・調査時だけ DEBUG に上げる、をデプロイ無しで実現します。
  • Powertools の Logger / Tracer / Metrics。Logger は相関ID付きの構造化ログ、Tracer は X-Ray の薄いラッパ、Metrics は EMF(Embedded Metric Format)で標準出力にメトリクスを書き出し、CloudWatch が非同期に抽出します(PutMetricData を同期呼びしないので速くて安い)。
  • X-Ray アクティブトレーシングActive にするとサンプリングされたリクエストのトレースを自動生成。xray:PutTraceSegments 等の権限(AWSXRayDaemonWriteAccess)が要ります。
# Powertools: 構造化ログ・トレース・EMFメトリクスを最小コードで
from aws_lambda_powertools import Logger, Tracer, Metrics
from aws_lambda_powertools.metrics import MetricUnit

logger = Logger(service="orders")     # JSON構造化ログ+相関ID
tracer = Tracer(service="orders")     # X-Ray セグメント
metrics = Metrics(namespace="Orders") # EMFでCloudWatchメトリクス(同期API呼び出し不要)

@metrics.log_metrics            # 集計したメトリクスをフラッシュ
@tracer.capture_lambda_handler  # ハンドラをトレース
@logger.inject_lambda_context   # request_id等を全ログに付与
def lambda_handler(event, context):
    metrics.add_metric(name="OrdersProcessed", unit=MetricUnit.Count, value=1)
    logger.info("processing order", extra={"order_id": event.get("id")})
    return {"ok": True}

アラートは症状で鳴らすErrors / Throttles / Duration(P99) / ConcurrentExecutions / Destinations の失敗キュー件数(1件で即通知)。Lambda Insights(拡張レイヤー)を足すと、CPU・メモリ・コールドスタートまで1呼び出し1イベントで取れます。可観測性の体系は OpenTelemetryで本番の可観測性を作る を参照。


8. セキュリティ:最小権限・秘密情報・VPC・公開エンドポイント

Lambda のセキュリティは「2つのポリシー」を分けて理解するところから始まります。

  • 実行ロール(execution role):関数がassume する IAM ロール。AWSサービスへのアクセス権はここ。最小権限で。まずは AWSLambdaBasicExecutionRole(ログ権限のみ)から始め、必要な操作だけ足す。IAM Access Analyzer で CloudTrail の利用実績からポリシーを生成するのが安全。
  • リソースベースポリシー誰がこの関数を呼べるか。AWSサービスが起動する場合はこちらだけが評価されます。
// 実行ロールの最小権限例:この関数が触る1テーブルへの必要操作のみ。ワイルドカード '*' を避ける
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["dynamodb:PutItem", "dynamodb:GetItem"],
    "Resource": "arn:aws:dynamodb:ap-northeast-1:123456789012:table/orders"
  }]
}

秘密情報:環境変数は KMS で保存時暗号化されます(既定は AWS マネージドキー、要件次第で CMK)。ただしAPI キーやDB資格情報を環境変数に平文で置くのは非推奨。公式の指針は Secrets Manager / SSM Parameter Store に置き、AWS Parameters and Secrets Lambda Extension で取得・キャッシュ(既定TTL 300秒)すること。アプリは「拡張のローカルエンドポイントから読む」だけになり、ローテーションにも追従できます。

VPC:プライベートな RDS 等へは Lambda を VPC にアタッチして接続します。外向き通信には NAT ゲートウェイが必要(プライベートサブネット配置だけではインターネットに出られない)。Lambda が作る ENI は Hyperplane ENI で、同じサブネット+セキュリティグループの関数間で共有・再利用されるため、VPC でも高速にスケールします。AWSサービスへは VPC エンドポイントで NAT を介さず到達できます。

Function URL:関数に直結する HTTPS エンドポイント。認証は AWS_IAMNONE公開API以外は AWS_IAM を選ぶNONE(公開)にするなら、最低限 WAF・レート制限(予約済み同時実行)・lambda:InvokedViaFunctionUrl 条件キーで締める。2025年10月以降、新規 Function URL は lambda:InvokeFunctionUrllambda:InvokeFunction の両権限を要します。


9. コスト最適化:請求書の式を理解し、速さで安くする

Lambda の料金は2要素です。式が分かれば請求書が読めます。

月額 ≒ リクエスト課金 + 実行時間課金
  リクエスト課金 = 総リクエスト数 × $0.20 / 100万
  実行時間課金   = 割当メモリ(GB) × 実行時間(秒, 1ms単位で切り上げ) × 単価(GB-秒)
  • x86 の実行時間単価は約 $0.0000166667 / GB-秒(第1ティア・US East基準)。Arm64(Graviton2)は実行料金が x86 比20%安い。互換性があるなら Arm64 が基本的に有利。
  • 無料利用枠:月100万リクエスト+40万 GB-秒。
  • 実行時間は1ms単位で切り上げ/tmp を512MB超にする・プロビジョンド同時実行を使う、は別課金。
  • 2025年8月から INIT 時間も課金対象(マネージドランタイム+ZIPのオンデマンド関数も対象に統一)。重い初期化はコストにも乗る。

9.1 メモリ調整は「速さ=安さ」の単一ノブ

CPU はメモリに比例配分され、1,769MB で1 vCPU 相当。つまりメモリを上げると CPU も増え、CPUバウンドな処理は短時間化してトータルコストが下がることがよくあります(128MBで遅く回すより1024MBで速く終える方が安い、という逆転が起きる)。メモリは推測せず計測して決めるのが鉄則です。AWS Lambda Power Tuning(公式サンプル)でコスト最小点を探します。

安くする手効き方
Arm64 に切り替え実行料金20%減。多くのランタイムでビルドし直すだけ
メモリを計測で最適化CPUバウンドは増メモリで短時間化→総額減のことが多い
パッケージを小さくコールドスタート短縮=INIT課金とレイテンシ減
接続をハンドラ外で再利用ウォーム呼び出しの実行時間短縮
不要な同期待ちを消す外部API待ちの間も課金される。並行化・非同期化

10. テスト容易性:ロジックは手元で、結合はクラウドで

Lambda のテストは2層で考えます。

  • 単体テスト:ハンドラを薄く保ち(第2章)、ビジネスロジックは Lambda 非依存の純粋関数として普通の単体テストで回す。context はモックでよい。最速・最安・最も多くのバグをここで捕まえる。
  • 結合テスト:AWS は**「クラウドでのテストを優先せよ」と明言しています。エミュレータ(sam local invoke / start-api、LocalStack 等)は速い反面、IAM・サービス設定・クォータ・API差分を再現しきれず「手元では通るがクラウドで落ちる」を生む。エミュレータは控えめに**使い、本物のサービスに対する結合・E2E をクラウドで回すのが公式の推奨です。
# 単体テスト:純粋ロジックは Lambda 無しで即テストできる(第2章の domain.py)
import pytest
from domain import Order, total_with_tax

def test_total_with_tax_rounds_half_up():
    assert total_with_tax(Order(id="o1", amount=1000), 0.10) == 1100

def test_negative_amount_rejected():
    with pytest.raises(ValueError):
        total_with_tax(Order(id="o2", amount=-1), 0.10)
# 結合の入口:手元で1度だけ叩く(Docker必須)。本格的な検証はクラウドにデプロイして行う
sam local invoke OrdersFunction --event events/order.json

11. まとめ:本番Lambdaチートシート

最後に、迷ったときの早見表です。

  • 実行モデル:Init→Invoke→Shutdown。接続・SDKクライアントはハンドラ外で1度だけ。一意なIDはハンドラ内で都度生成。
  • ハンドラ:薄いアダプタ。ビジネスロジックはLambda非依存の純粋関数に切り出してテストする。
  • パッケージ:まずZIP+レイヤー、250MB超でコンテナ。node_modules丸ごとはNG、ツリーシェイクする。
  • スケール:必要同時実行=RPS×処理秒。下流保護は予約済み、レイテンシ保証はプロビジョンド、予約=0で緊急停止。
  • 失敗設計:同期は自動リトライ無し。非同期は既定2回+On-failure Destinations。イベントソースは部分バッチ失敗で全件やり直しを防ぐ。
  • 冪等性:再試行は仕様。Powertoolsの冪等化+業務に沿った冪等性キー+TTL。
  • 可観測性:JSON構造化ログ+ログレベル制御(コード変更ゼロ)、Powertools Metrics(EMF)、X-Ray。症状でアラート。
  • セキュリティ:実行ロールは最小権限。秘密はSecrets Manager+拡張でキャッシュ。VPCはNAT/エンドポイント。Function URLは原則AWS_IAM
  • コストメモリ×時間(1ms)+リクエストArm64で20%減、メモリは計測で最適化、INITも課金される。

Lambda は「サーバーレスだから楽」ではなく、at-least-once・コールドスタート・最小権限・課金の式という制約の上で、冪等性・可観測性・コストを設計する仕事です。私は環境分野のマルチテナント決済プラットフォームで、月次一括課金・CO2集計を冪等・自動再処理・最小権限・症状ベースのアラートで固め、本番稼働中の二重課金・残高不整合 0件を達成しました。

「自社のこのワークロードを、Lambda で速く・安く・落ちても自動で立ち直る形にしたい」——その設計から実装・運用・コスト最適化まで、一人 × 生成AI(Claude Code)の速さで一気通貫に伴走します。 計算基盤の選定段階(Lambda か Fargate か)からでも、お気軽にご相談ください。


参考(公式ドキュメント)

友田

友田 陽大

経済産業大臣賞 受賞プロダクト開発者。TypeScript + Python + AWS で、SaaS・業界DX・ 実用レベルの生成AI(RAG)を、要件定義からインフラ・運用まで一人で完遂します。

この記事で解説した技術の適用事例

環境分野のサーバーレス決済プラットフォーム(フルスタック開発・決済信頼性レイヤーを主導)

ケーススタディを見る