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

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

- 公開日: 2026-06-26
- 著者: 友田 陽大
- タグ: AWS, Lambda, サーバーレス, アーキテクチャ設計, 冪等性
- URL: https://tomodahinata.com/blog/aws-lambda-production-guide

## 要点

- Lambdaは『イベント→使い捨ての実行環境→使った分だけ課金』。ハンドラ外の初期化はコールドスタート時に1度だけ走り、ウォーム呼び出しで再利用される——接続はここで張る
- 非同期呼び出しは既定で2回リトライ（計3回）。失敗の証跡は旧DLQではなくOn-failure Destinations（SQS/SNS/Lambda/EventBridge/S3）で受ける
- 再試行は仕様。Powertoolsの冪等化（DynamoDB永続化＋INPROGRESSロック）で『2回来ても結果が一意』にするのが信頼性の土台
- 可観測性はコード変更ゼロのJSON構造化ログ＋ログレベル制御、Powertoolsのメトリクス(EMF)とX-Rayで固める。秘密情報は環境変数の平文ではなくSecrets Manager＋拡張でキャッシュ取得
- コストは『割当メモリ×実行時間(1ms単位)＋リクエスト数』。2025年8月からマネージドランタイム+ZIPでもINIT時間が課金対象。Arm64は実行料金が20%安い

---

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

この記事は、AWS Lambda を**本番品質**で設計・運用するための実装ガイドです。題材として、私が中核開発者（主要開発者3名）として構築した、環境・カーボンクレジット／地域通貨のマルチテナント決済プラットフォーム（[サーバーレス決済プラットフォームの信頼性設計](/case-studies/payment-platform-reliability)）での設計判断も交えます。**本番稼働中の二重課金・残高不整合 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 技術選定ガイド](/blog/aws-ecs-fargate-vs-lambda-vs-app-runner-compute-selection-guide) に委ね、本稿は**「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 コールドスタート最適化ガイド](/blog/aws-lambda-cold-start-snapstart-provisioned-concurrency-performance-guide) で深掘りします。
- **2025年8月1日から、INIT 時間も課金対象に統一**された。以前は「マネージドランタイム＋ZIP のオンデマンド関数」だけ INIT が無課金だったが、現在は全構成で INIT が `Billed Duration` に含まれる（カスタムランタイム・プロビジョンド同時実行・コンテナイメージは従来から課金）。**コールドスタートの初期化はもうタダではない**——これは第10章のコスト設計に直結します。

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

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

```ts
// 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（単一責任）であり、テスト容易性の鍵です。

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

```ts
// Node.js（推奨は async ハンドラ。Node.js 24 以降は callback 形式は不可）
export const handler = async (event: MyEvent, context: Context): Promise<MyResult> => { /* ... */ };
```

```python
# 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 にし、ビジネスロジックを分離して単体テストせよ」と推奨しています。

```python
# 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))
```

```python
# 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 コールドスタート最適化ガイド](/blog/aws-lambda-cold-start-snapstart-provisioned-concurrency-performance-guide) に詳細を分離しました。

---

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

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

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

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

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

- **`MaximumRetryAttempts`**：`0〜2`（既定2）。
- **`MaximumEventAgeInSeconds`**：`60〜21600`（＝最大6時間）。古すぎるイベントは捨てる。

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

```hcl
# 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 で冪等な非同期処理を作る](/blog/aws-sqs-lambda-eventbridge-idempotent-async-processing-guide)、DynamoDB Streams を起点にした CDC/イベント駆動は [DynamoDB Streams × Lambda イベント駆動アーキテクチャ](/blog/dynamodb-streams-event-driven-architecture-cdc-lambda-eventbridge-guide) に、それぞれ専用記事があります。

---

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

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

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

```python
# 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)
```

```ts
// 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件を達成できたのは、この**冪等性キー設計を業務の不変条件に合わせた**からです。冪等化の理論的背景は [冪等な決済設計（二重課金対策）](/blog/payment-double-charge-prevention-idempotency-procurement-guide) も参照ください。

---

## 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`）が要ります。

```python
# 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で本番の可観測性を作る](/blog/opentelemetry-observability-production-tracing-metrics-logs) を参照。

---

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

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

- **実行ロール（execution role）**：関数が**assume する** IAM ロール。AWSサービスへのアクセス権はここ。**最小権限**で。まずは `AWSLambdaBasicExecutionRole`（ログ権限のみ）から始め、必要な操作だけ足す。IAM Access Analyzer で CloudTrail の利用実績からポリシーを生成するのが安全。
- **リソースベースポリシー**：**誰がこの関数を呼べるか**。AWSサービスが起動する場合はこちらだけが評価されます。

```json
// 実行ロールの最小権限例：この関数が触る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_IAM` か `NONE`。**公開API以外は `AWS_IAM` を選ぶ**。`NONE`（公開）にするなら、最低限 WAF・レート制限（予約済み同時実行）・`lambda:InvokedViaFunctionUrl` 条件キーで締める。2025年10月以降、新規 Function URL は `lambda:InvokeFunctionUrl` と `lambda:InvokeFunction` の両権限を要します。

---

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

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

```text
月額 ≒ リクエスト課金 + 実行時間課金
  リクエスト課金 = 総リクエスト数 × $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 をクラウドで回すのが公式の推奨です。

```python
# 単体テスト：純粋ロジックは 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)
```

```bash
# 結合の入口：手元で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 か）からでも、お気軽にご相談ください。

---

### 参考（公式ドキュメント）

- [Lambda execution environment lifecycle](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html) — Init/Invoke/Shutdown の3フェーズ、コールドスタート、ハンドラ外オブジェクトの再利用
- [AWS Lambda standardizes billing for the INIT phase](https://aws.amazon.com/blogs/compute/aws-lambda-standardizes-billing-for-init-phase/) — 2025年8月1日からの INIT 課金統一
- [Lambda quotas](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html) — メモリ128MB〜10,240MB（1,769MBで1vCPU）、タイムアウト900秒、ペイロード、`/tmp`、パッケージサイズ、同時実行1,000
- [Lambda runtimes](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html) — サポートランタイムと非推奨スケジュール、x86_64/arm64
- [Building Lambda functions with Node.js](https://docs.aws.amazon.com/lambda/latest/dg/lambda-nodejs.html) / [Python](https://docs.aws.amazon.com/lambda/latest/dg/lambda-python.html) — ハンドラと context
- [Lambda concurrency](https://docs.aws.amazon.com/lambda/latest/dg/lambda-concurrency.html) / [Scaling behavior](https://docs.aws.amazon.com/lambda/latest/dg/scaling-behavior.html) — 同時実行、リトルの法則、予約/プロビジョンド、1関数10秒1,000環境
- [Asynchronous invocation](https://docs.aws.amazon.com/lambda/latest/dg/invocation-async.html) / [Error handling and retries](https://docs.aws.amazon.com/lambda/latest/dg/invocation-async-error-handling.html) — 既定2回リトライ、`MaximumRetryAttempts`、`MaximumEventAge`、Destinations
- [Idempotency - Powertools for AWS Lambda](https://docs.aws.amazon.com/powertools/python/latest/utilities/idempotency/) — `@idempotent` / `IdempotencyConfig` / `DynamoDBPersistenceLayer`
- [Advanced logging controls](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs-logformat.html) — JSON形式・ログレベル制御（コード変更不要）
- [Lambda security & permissions](https://docs.aws.amazon.com/lambda/latest/dg/lambda-permissions.html) / [Env var encryption](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars-encryption.html) / [VPC](https://docs.aws.amazon.com/lambda/latest/dg/configuration-vpc.html) — 実行ロール、リソースベースポリシー、KMS、Hyperplane ENI
- [Testing Lambda functions](https://docs.aws.amazon.com/lambda/latest/dg/testing-guide.html) — クラウド優先のテスト戦略
- [AWS Lambda Pricing](https://aws.amazon.com/lambda/pricing/) — リクエスト/実行時間課金、無料枠、Arm/プロビジョンド/`/tmp` 料金
