AWS Lambda 応用
コールドスタートの最適化、Provisioned Concurrency、Lambda Destinations、Step Functions 連携を理解し、本番運用品質のサーバーレスアプリケーションを構築する。
AWS Lambda 応用
コールドスタートの最適化、Provisioned Concurrency、Lambda Destinations、Step Functions 連携を理解し、本番運用品質のサーバーレスアプリケーションを構築する。
この章で学ぶこと
- コールドスタートの原因と最適化手法 -- コールドスタートが発生するメカニズムを理解し、ランタイム選択やパッケージ軽量化で実戦的に対処する
- Provisioned Concurrency と同時実行制御 -- レイテンシ要件が厳しいワークロードに対して予め実行環境を確保する方法を習得する
- Lambda Destinations と Step Functions -- 非同期処理の結果ルーティングとオーケストレーションでエラーハンドリングを設計する
- Lambda レイヤーとカスタムランタイム -- 共通ライブラリの効率的な管理とカスタムランタイムの構築方法を学ぶ
- Lambda のモニタリングとデバッグ -- X-Ray、CloudWatch Logs Insights、Lambda Insights を活用して本番環境の問題を迅速に特定する
- Lambda のセキュリティベストプラクティス -- 最小権限の原則、VPC 設計、シークレット管理を実践する
前提知識
このガイドを読む前に、以下の知識があると理解が深まります:
- 基本的なプログラミングの知識
- 関連する基礎概念の理解
- AWS Lambda 基礎 の内容を理解していること
1. コールドスタートの詳解
1.1 コールドスタートのライフサイクル
リクエスト到着
|
v
+-----------------------------+
| 実行環境はあるか? |
+-----------------------------+
| |
ある(Warm) ない(Cold)
| |
| v
| +------------------------+
| | 1. MicroVM 確保 | <-- AWS管理 (数百ms)
| | 2. ランタイム初期化 | <-- ランタイム依存
| | 3. デプロイパッケージ | <-- サイズ依存
| | ダウンロード・展開 |
| | 4. Init コード実行 | <-- ユーザーコード
| | (ハンドラ外) |
| +------------------------+
| |
v v
+-----------------------------+
| 5. ハンドラ関数実行 | <-- 通常の実行
+-----------------------------+
|
v
+-----------------------------+
| 6. 実行環境を Warm 状態で |
| 一定時間保持 (~5-15分) |
+-----------------------------+
1.2 ランタイム別コールドスタート時間の目安
| ランタイム | コールドスタート (128MB) | コールドスタート (1024MB) | 備考 |
|---|---|---|---|
| Python 3.12 | 200-400 ms | 150-300 ms | 軽量、高速起動 |
| Node.js 20.x | 200-400 ms | 150-250 ms | 軽量、高速起動 |
| Go (AL2023) | 50-150 ms | 30-100 ms | コンパイル済みバイナリ |
| Java 21 | 2,000-5,000 ms | 800-2,000 ms | JVM 起動が重い |
| .NET 8 | 800-2,000 ms | 400-1,000 ms | AOT で大幅改善可能 |
| Ruby 3.3 | 300-600 ms | 200-400 ms | インタプリタ起動 |
| Rust (AL2023) | 30-100 ms | 20-80 ms | Go 同様にネイティブバイナリ |
1.3 コールドスタート最適化テクニック
# [最適化] ハンドラ外で初期化を行い、Warm 起動時に再利用
import boto3
import os
# --- Init Phase (コールドスタート時のみ実行) ---
TABLE_NAME = os.environ["TABLE_NAME"]
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(TABLE_NAME)
# ------------------------------------------------
def lambda_handler(event, context):
"""ハンドラは Warm 起動時にも毎回実行される"""
user_id = event["pathParameters"]["userId"]
response = table.get_item(Key={"userId": user_id})
return {
"statusCode": 200,
"body": json.dumps(response.get("Item", {}))
}# [最適化] 遅延インポートで不要なモジュールの初期化を避ける
import json
import os
def lambda_handler(event, context):
action = event.get("action")
if action == "generate_pdf":
# PDF 生成が必要な場合のみ重いライブラリをインポート
from reportlab.pdfgen import canvas
return generate_pdf(event)
elif action == "send_email":
import boto3
ses = boto3.client("ses")
return send_email(ses, event)
else:
return {"statusCode": 400, "body": "Unknown action"}# [最適化] コネクションプールの再利用パターン
import boto3
import os
from botocore.config import Config
# Init Phase: SDK クライアントの設定を最適化
config = Config(
retries={"max_attempts": 3, "mode": "adaptive"},
max_pool_connections=10,
connect_timeout=5,
read_timeout=10,
)
# 各 AWS サービスクライアントを Init Phase で作成
dynamodb_client = boto3.client("dynamodb", config=config)
s3_client = boto3.client("s3", config=config)
sqs_client = boto3.client("sqs", config=config)
BUCKET_NAME = os.environ["BUCKET_NAME"]
QUEUE_URL = os.environ["QUEUE_URL"]
def lambda_handler(event, context):
"""全クライアントは Warm 起動時に再利用される"""
# DynamoDB からデータ取得
item = dynamodb_client.get_item(
TableName="my-table",
Key={"pk": {"S": event["id"]}}
)
# S3 にレポートを保存
s3_client.put_object(
Bucket=BUCKET_NAME,
Key=f"reports/{event['id']}.json",
Body=json.dumps(item.get("Item", {})),
ContentType="application/json"
)
# SQS に通知を送信
sqs_client.send_message(
QueueUrl=QUEUE_URL,
MessageBody=json.dumps({"status": "completed", "id": event["id"]})
)
return {"statusCode": 200, "body": "Processing complete"}1.4 デプロイパッケージの軽量化
パッケージサイズ vs コールドスタート:
サイズ コールドスタート影響
1 MB ----- 最小限 (+50ms程度)
5 MB ----- 軽微 (+100ms程度)
10 MB ----- 顕著 (+200ms程度)
50 MB ----- 深刻 (+500ms以上)
250 MB ----- 非常に深刻 (+1秒以上)
対策:
- 不要な依存を除外 (dev dependencies)
- __pycache__、テストファイルを除外
- 軽量な代替ライブラリを利用
- Lambda レイヤーで共通部分を分離
- コンテナイメージ利用時は multi-stage build
# Python での軽量パッケージ作成例
# 1. 本番依存のみインストール
pip install -r requirements.txt -t ./package --no-cache-dir
# 2. 不要ファイルの除去
cd package
find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null
find . -type d -name "*.dist-info" -exec rm -rf {} + 2>/dev/null
find . -type f -name "*.pyc" -delete 2>/dev/null
find . -type d -name "tests" -exec rm -rf {} + 2>/dev/null
# 3. ZIP パッケージの作成
zip -r9 ../function.zip .
cd ..
zip -g function.zip lambda_function.py
# 4. サイズ確認
ls -lh function.zip
# 5. デプロイ
aws lambda update-function-code \
--function-name my-function \
--zip-file fileb://function.zip# Node.js での軽量パッケージ作成例
# 1. 本番依存のみインストール
npm ci --only=production
# 2. esbuild でバンドル (tree-shaking 付き)
npx esbuild src/handler.ts \
--bundle \
--minify \
--sourcemap \
--platform=node \
--target=node20 \
--outfile=dist/handler.js \
--external:@aws-sdk/*
# 3. ZIP パッケージの作成
cd dist
zip -r9 ../function.zip .
# AWS SDK v3 は Lambda ランタイムに組み込み済みのため
# --external:@aws-sdk/* で除外してサイズ削減1.5 メモリとCPUの関係
Lambda のメモリとCPUの比例関係:
メモリ CPU パワー ネットワーク帯域
128 MB 最小 (部分) 低
256 MB 低 低
512 MB 中 中
1024 MB 中~高 中
1769 MB 1 vCPU 相当 高
3538 MB 2 vCPU 相当 高
10 GB 6 vCPU 相当 最大
ポイント:
- 1,769 MB で 1 vCPU が完全に割り当てられる
- CPU バウンドな処理はメモリ増強で高速化できる
- メモリ増強によりコールドスタートも短縮される
- コスト = 実行時間 x メモリ のため、
メモリ倍増 → 実行時間半減なら同コストで高速に
# メモリサイズの最適化を自動テストするスクリプト
import boto3
import json
import time
import statistics
lambda_client = boto3.client("lambda")
def benchmark_memory_sizes(function_name, payload, memory_sizes, iterations=10):
"""異なるメモリサイズでの実行時間を比較する"""
results = {}
for memory_size in memory_sizes:
# メモリサイズを変更
lambda_client.update_function_configuration(
FunctionName=function_name,
MemorySize=memory_size
)
time.sleep(5) # 設定反映を待つ
durations = []
for i in range(iterations):
response = lambda_client.invoke(
FunctionName=function_name,
Payload=json.dumps(payload)
)
# レスポンスヘッダから実行時間を取得
log_result = response.get("LogResult", "")
# Duration を解析
duration = float(response["ResponseMetadata"]["HTTPHeaders"]
.get("x-amz-log-result", "0"))
durations.append(duration)
results[memory_size] = {
"avg_duration_ms": statistics.mean(durations),
"p99_duration_ms": sorted(durations)[int(len(durations) * 0.99)],
"cost_per_invocation": (memory_size / 1024) * (statistics.mean(durations) / 1000) * 0.0000166667,
}
return results2. Provisioned Concurrency
2.1 仕組みと設定
Provisioned Concurrency は、指定した数の実行環境を事前に初期化しておく機能である。コールドスタートを完全に排除し、一貫したレイテンシを実現する。
通常の Lambda:
リクエスト --> [コールドスタート?] --> ハンドラ実行
↑
環境がなければ発生
Provisioned Concurrency:
+----- 事前初期化済み環境 1
|
リクエスト -------> +----- 事前初期化済み環境 2 --> ハンドラ実行
| (コールドスタートなし)
+----- 事前初期化済み環境 3
|
+----- 事前初期化済み環境 N
※ Provisioned を超える分は通常のオンデマンドで処理
# Provisioned Concurrency の設定
# まずエイリアスまたはバージョンを指定
aws lambda publish-version \
--function-name my-api-function
aws lambda put-provisioned-concurrency-config \
--function-name my-api-function \
--qualifier 1 \
--provisioned-concurrent-executions 50
# 状態確認
aws lambda get-provisioned-concurrency-config \
--function-name my-api-function \
--qualifier 1
# 設定一覧の確認
aws lambda list-provisioned-concurrency-configs \
--function-name my-api-function
# Provisioned Concurrency の削除
aws lambda delete-provisioned-concurrency-config \
--function-name my-api-function \
--qualifier 12.2 Application Auto Scaling との連携
# Auto Scaling ターゲットの登録
aws application-autoscaling register-scalable-target \
--service-namespace lambda \
--resource-id "function:my-api-function:prod" \
--scalable-dimension "lambda:function:ProvisionedConcurrency" \
--min-capacity 10 \
--max-capacity 200
# ターゲット追跡スケーリングポリシー
aws application-autoscaling put-scaling-policy \
--service-namespace lambda \
--resource-id "function:my-api-function:prod" \
--scalable-dimension "lambda:function:ProvisionedConcurrency" \
--policy-name "provisioned-concurrency-target-tracking" \
--policy-type TargetTrackingScaling \
--target-tracking-scaling-policy-configuration '{
"TargetValue": 0.7,
"PredefinedMetricSpecification": {
"PredefinedMetricType": "LambdaProvisionedConcurrencyUtilization"
},
"ScaleInCooldown": 300,
"ScaleOutCooldown": 60
}'
# スケジュールベースのスケーリング
aws application-autoscaling put-scheduled-action \
--service-namespace lambda \
--resource-id "function:my-api-function:prod" \
--scalable-dimension "lambda:function:ProvisionedConcurrency" \
--scheduled-action-name "morning-scale-up" \
--schedule "cron(0 8 * * ? *)" \
--scalable-target-action "MinCapacity=100,MaxCapacity=500"
# 夜間のスケールダウン
aws application-autoscaling put-scheduled-action \
--service-namespace lambda \
--resource-id "function:my-api-function:prod" \
--scalable-dimension "lambda:function:ProvisionedConcurrency" \
--scheduled-action-name "night-scale-down" \
--schedule "cron(0 22 * * ? *)" \
--scalable-target-action "MinCapacity=10,MaxCapacity=50"2.3 コスト比較
| 項目 | オンデマンド | Provisioned Concurrency |
|---|---|---|
| コールドスタート | あり | なし |
| 課金開始 | リクエスト時 | 設定時から常時 |
| リクエスト料金 | $0.20/100万回 | $0.20/100万回 |
| 実行時間料金 (x86) | $0.0000166667/GB-秒 | $0.0000097222/GB-秒 (実行時) + $0.0000041667/GB-秒 (待機時) |
| 向いている用途 | 不定期/バースト | 安定トラフィック/低レイテンシ |
Provisioned Concurrency のコスト試算例:
シナリオ: API バックエンド
- メモリ: 1 GB
- 平均実行時間: 200 ms
- リクエスト数: 100万回/月
- Provisioned 数: 50
オンデマンドの場合:
リクエスト料金: 100万 x $0.20/100万 = $0.20
実行時間料金: 100万 x 0.2秒 x 1GB x $0.0000166667 = $3.33
合計: $3.53/月
Provisioned Concurrency の場合:
リクエスト料金: $0.20
実行時間料金: 100万 x 0.2秒 x 1GB x $0.0000097222 = $1.94
待機時間料金: 50 x 30日 x 24時間 x 3600秒 x 1GB x $0.0000041667 = $540.00
合計: $542.14/月
→ Provisioned は高額だが、コールドスタートなしの一貫したレイテンシを実現
→ Auto Scaling でトラフィックパターンに合わせて調整することでコスト最適化可能
→ 24時間常時50ではなく、ピーク時のみ高い値に設定するのが現実的
2.4 Reserved Concurrency との組み合わせ
同時実行制御の階層:
アカウント全体の同時実行数上限: 1,000 (デフォルト)
|
+-- 関数A: Reserved Concurrency = 200
| |
| +-- Provisioned: 50 (200のうち50を事前初期化)
| +-- オンデマンド: 残り150まで利用可能
|
+-- 関数B: Reserved Concurrency = 100
| |
| +-- 全てオンデマンド
|
+-- 他の関数: 残り700を共有 (Unreserved)
Reserved Concurrency の設定:
- 関数の同時実行数の「上限」を設定
- 追加コストなし
- 他の関数からのスロットル保護
- Provisioned と併用可能
# Reserved Concurrency の設定
aws lambda put-function-concurrency \
--function-name my-api-function \
--reserved-concurrent-executions 200
# Reserved Concurrency の確認
aws lambda get-function-concurrency \
--function-name my-api-function
# Reserved Concurrency の削除 (アカウントプールに戻す)
aws lambda delete-function-concurrency \
--function-name my-api-function3. Lambda Destinations
3.1 非同期呼び出しの結果ルーティング
非同期呼び出し
|
v
+------------------+
| Lambda 関数実行 |
+------------------+
| |
成功 失敗
| |
v v
+---------+ +---------+
| OnSuccess| | OnFailure|
| 送信先 | | 送信先 |
+---------+ +---------+
| |
v v
SQS SQS
SNS SNS
Lambda Lambda
EventBridge EventBridge
# Destinations の設定
aws lambda put-function-event-invoke-config \
--function-name my-async-function \
--maximum-retry-attempts 1 \
--maximum-event-age-in-seconds 3600 \
--destination-config '{
"OnSuccess": {
"Destination": "arn:aws:sqs:ap-northeast-1:123456789012:success-queue"
},
"OnFailure": {
"Destination": "arn:aws:sqs:ap-northeast-1:123456789012:failure-queue"
}
}'
# 設定の確認
aws lambda get-function-event-invoke-config \
--function-name my-async-function
# 設定の削除
aws lambda delete-function-event-invoke-config \
--function-name my-async-function3.2 Destinations vs DLQ
| 機能 | Lambda Destinations | Dead Letter Queue (DLQ) |
|---|---|---|
| 対象イベント | 成功・失敗の両方 | 失敗のみ |
| 送信先 | SQS, SNS, Lambda, EventBridge | SQS, SNS のみ |
| ペイロード | 完全な実行コンテキスト含む | 元のイベントのみ |
| 推奨度 | 新規開発では推奨 | レガシー互換 |
3.3 Destinations のペイロード構造
{
"version": "1.0",
"timestamp": "2024-01-15T10:30:00.000Z",
"requestContext": {
"requestId": "abc123-def456-ghi789",
"functionArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:my-function",
"condition": "Success",
"approximateInvokeCount": 1
},
"requestPayload": {
"orderId": "ORD-001",
"amount": 5000
},
"responseContext": {
"statusCode": 200,
"executedVersion": "$LATEST",
"functionError": null
},
"responsePayload": {
"statusCode": 200,
"body": "{\"message\": \"Order processed successfully\"}"
}
}3.4 EventBridge を活用したイベント駆動パターン
# Lambda Destination を EventBridge に設定し、
# 複数の後続処理をイベントルールで分岐させるパターン
import json
import boto3
eventbridge = boto3.client("events")
def order_processor(event, context):
"""注文処理Lambda - 成功時にEventBridgeへ送信"""
order_id = event["orderId"]
amount = event["amount"]
# 注文処理ロジック
result = process_order(order_id, amount)
return {
"statusCode": 200,
"orderId": order_id,
"processedAmount": amount,
"status": "COMPLETED"
}
# EventBridge ルールでの後続処理分岐:
# ルール1: amount > 10000 → 高額注文通知 Lambda
# ルール2: 全注文 → 注文履歴 DynamoDB 書き込み Lambda
# ルール3: status=COMPLETED → 配送手配 Step Functions# EventBridge ルール (CloudFormation)
HighValueOrderRule:
Type: AWS::Events::Rule
Properties:
Name: high-value-order-notification
EventPattern:
source:
- "lambda"
detail-type:
- "Lambda Function Invocation Result - Success"
detail:
requestContext:
functionArn:
- !GetAtt OrderProcessorFunction.Arn
responsePayload:
processedAmount:
- numeric: [">=", 10000]
Targets:
- Arn: !GetAtt HighValueNotificationFunction.Arn
Id: HighValueNotification4. AWS Step Functions 連携
4.1 ステートマシンの基本構成
Step Functions ステートマシン:
[Start]
|
v
+-------------------+
| ValidateInput | (Lambda)
+-------------------+
|
v
+-------------------+
| ProcessOrder | (Lambda)
+-------------------+
| |
成功 失敗
| |
v v
+--------+ +-------------------+
| Notify | | HandleError | (Lambda)
| Success| +-------------------+
+--------+ |
| v
| +-------------------+
| | Notify Failure |
| +-------------------+
| |
v v
[End] [End]
4.2 ステートマシン定義 (ASL)
{
"Comment": "注文処理ワークフロー",
"StartAt": "ValidateInput",
"States": {
"ValidateInput": {
"Type": "Task",
"Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:validate-input",
"Next": "ProcessOrder",
"Catch": [
{
"ErrorEquals": ["ValidationError"],
"Next": "HandleError"
}
]
},
"ProcessOrder": {
"Type": "Task",
"Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:process-order",
"TimeoutSeconds": 300,
"Retry": [
{
"ErrorEquals": ["States.TaskFailed"],
"IntervalSeconds": 5,
"MaxAttempts": 3,
"BackoffRate": 2.0
}
],
"Next": "NotifySuccess",
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"Next": "HandleError"
}
]
},
"NotifySuccess": {
"Type": "Task",
"Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:notify-success",
"End": true
},
"HandleError": {
"Type": "Task",
"Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:handle-error",
"Next": "NotifyFailure"
},
"NotifyFailure": {
"Type": "Task",
"Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:notify-failure",
"End": true
}
}
}4.3 Standard vs Express ワークフロー
| 特性 | Standard | Express |
|---|---|---|
| 最大実行時間 | 1 年 | 5 分 |
| 実行開始レート | 2,000/秒 | 100,000/秒 |
| 状態遷移レート | 4,000/秒 | 無制限 |
| 実行保証 | 正確に1回 | 最低1回 (Async) / 正確に1回 (Sync) |
| 課金 | 状態遷移ごと | 実行回数 + 実行時間 |
| 向いている用途 | 長時間ワークフロー | 大量短時間処理、IoT |
4.4 Step Functions の Parallel 実行
{
"Type": "Parallel",
"Branches": [
{
"StartAt": "SendEmail",
"States": {
"SendEmail": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:send-email",
"End": true
}
}
},
{
"StartAt": "SendSMS",
"States": {
"SendSMS": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:send-sms",
"End": true
}
}
},
{
"StartAt": "UpdateDB",
"States": {
"UpdateDB": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:update-db",
"End": true
}
}
}
],
"Next": "AggregateResults"
}4.5 Map ステートによる動的並列処理
{
"Comment": "大量データの並列処理ワークフロー",
"StartAt": "FetchItems",
"States": {
"FetchItems": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:fetch-items",
"Next": "ProcessItems"
},
"ProcessItems": {
"Type": "Map",
"ItemsPath": "$.items",
"MaxConcurrency": 10,
"Iterator": {
"StartAt": "ProcessSingleItem",
"States": {
"ProcessSingleItem": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:process-item",
"Retry": [
{
"ErrorEquals": ["States.TaskFailed"],
"IntervalSeconds": 2,
"MaxAttempts": 3,
"BackoffRate": 2.0
}
],
"End": true
}
}
},
"Next": "AggregateResults"
},
"AggregateResults": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:aggregate-results",
"End": true
}
}
}4.6 Distributed Map (大規模並列処理)
{
"Comment": "S3 の大量ファイルを分散並列処理",
"StartAt": "DistributedProcess",
"States": {
"DistributedProcess": {
"Type": "Map",
"ItemReader": {
"Resource": "arn:aws:states:::s3:listObjectsV2",
"Parameters": {
"Bucket": "my-input-bucket",
"Prefix": "data/"
}
},
"ItemProcessor": {
"ProcessorConfig": {
"Mode": "DISTRIBUTED",
"ExecutionType": "EXPRESS"
},
"StartAt": "ProcessFile",
"States": {
"ProcessFile": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:process-file",
"End": true
}
}
},
"MaxConcurrency": 1000,
"Label": "DistributedProcess",
"ResultWriter": {
"Resource": "arn:aws:states:::s3:putObject",
"Parameters": {
"Bucket": "my-output-bucket",
"Prefix": "results/"
}
},
"End": true
}
}
}4.7 Step Functions SDK 統合 (Optimized Integration)
{
"Comment": "AWS SDK 統合による直接サービス呼び出し",
"StartAt": "PutItemToDynamoDB",
"States": {
"PutItemToDynamoDB": {
"Type": "Task",
"Resource": "arn:aws:states:::dynamodb:putItem",
"Parameters": {
"TableName": "Orders",
"Item": {
"orderId": {"S.$": "$.orderId"},
"status": {"S": "PENDING"},
"createdAt": {"S.$": "$$.State.EnteredTime"}
}
},
"Next": "PublishToSNS"
},
"PublishToSNS": {
"Type": "Task",
"Resource": "arn:aws:states:::sns:publish",
"Parameters": {
"TopicArn": "arn:aws:sns:ap-northeast-1:123456789012:order-notifications",
"Message.$": "States.Format('New order: {}', $.orderId)",
"Subject": "New Order Received"
},
"Next": "StartECSTask"
},
"StartECSTask": {
"Type": "Task",
"Resource": "arn:aws:states:::ecs:runTask.sync",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "arn:aws:ecs:...:cluster/my-cluster",
"TaskDefinition": "arn:aws:ecs:...:task-definition/process-order:1",
"Overrides": {
"ContainerOverrides": [
{
"Name": "processor",
"Environment": [
{"Name": "ORDER_ID", "Value.$": "$.orderId"}
]
}
]
},
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": ["subnet-111", "subnet-222"],
"SecurityGroups": ["sg-12345678"]
}
}
},
"End": true
}
}
}5. Lambda SnapStart (Java)
5.1 SnapStart の仕組み
従来の Java Lambda:
リクエスト --> JVM起動 --> クラスロード --> DI初期化 --> ハンドラ実行
|<---- コールドスタート (2-5秒) ---->|
SnapStart:
[事前] バージョン公開時にスナップショット作成
JVM起動 --> クラスロード --> DI初期化 --> スナップショット保存
[実行時] リクエスト --> スナップショット復元 (< 200ms) --> ハンドラ実行
# SnapStart の有効化
aws lambda update-function-configuration \
--function-name my-java-function \
--snap-start '{"ApplyOn": "PublishedVersions"}'
# バージョン公開(スナップショット作成)
aws lambda publish-version \
--function-name my-java-function
# SnapStart の状態確認
aws lambda get-function-configuration \
--function-name my-java-function \
--query 'SnapStart'5.2 SnapStart の注意点
SnapStart 利用時の注意事項:
1. 一意性の問題:
スナップショットから複数の実行環境が復元されるため、
Init Phase で生成した乱数やUUIDが重複する可能性がある。
[対策]
- java.util.Random の初期化をハンドラ内で行う
- afterRestore フックで状態をリセットする
2. ネットワーク接続の問題:
Init Phase で確立したDB接続はスナップショット復元後に無効。
[対策]
- afterRestore フックでコネクションを再確立
- コネクションプーリングライブラリの再初期化
3. 対応ランタイム:
- Java 11 以降 (Corretto)
- arm64 / x86_64 両対応
// SnapStart の afterRestore フック例
import org.crac.Context;
import org.crac.Core;
import org.crac.Resource;
public class MyHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent>,
Resource {
private Connection dbConnection;
public MyHandler() {
// Init Phase: CRaC リソースとして登録
Core.getGlobalContext().register(this);
// DB接続を確立
this.dbConnection = DriverManager.getConnection(DB_URL);
}
@Override
public void afterRestore(Context<? extends Resource> context) {
// スナップショット復元後に呼ばれる
// DB接続を再確立
this.dbConnection = DriverManager.getConnection(DB_URL);
// 乱数生成器を再シード
SecureRandom.getInstanceStrong();
}
@Override
public APIGatewayProxyResponseEvent handleRequest(
APIGatewayProxyRequestEvent event, com.amazonaws.services.lambda.runtime.Context context) {
// ハンドラロジック
return new APIGatewayProxyResponseEvent().withStatusCode(200);
}
}6. Lambda レイヤー
6.1 レイヤーの概念
Lambda レイヤーの仕組み:
+------------------------------------------+
| Lambda 関数 |
| +--------------------------------------+ |
| | /var/task (関数コード) | |
| | +----------------------------------+ | |
| | | lambda_function.py | | |
| | +----------------------------------+ | |
| +--------------------------------------+ |
| +--------------------------------------+ |
| | /opt (レイヤー 1 + 2 + ... + N) | |
| | +----------------------------------+ | |
| | | /opt/python/共通ライブラリ | | |
| | | /opt/bin/カスタムバイナリ | | |
| | | /opt/lib/共有ライブラリ | | |
| | +----------------------------------+ | |
| +--------------------------------------+ |
+------------------------------------------+
レイヤーのメリット:
- 共通ライブラリの一元管理
- デプロイパッケージの軽量化
- 最大5レイヤーまで重ね合わせ可能
- レイヤー単位でバージョン管理
6.2 レイヤーの作成と管理
# Python ライブラリのレイヤー作成
mkdir -p python/lib/python3.12/site-packages
pip install requests boto3-stubs[s3,dynamodb] \
-t python/lib/python3.12/site-packages
# ZIP パッケージ作成
zip -r9 my-layer.zip python/
# レイヤーの公開
aws lambda publish-layer-version \
--layer-name my-common-libs \
--description "共通ライブラリ (requests, boto3-stubs)" \
--compatible-runtimes python3.11 python3.12 \
--compatible-architectures x86_64 arm64 \
--zip-file fileb://my-layer.zip
# レイヤーを関数にアタッチ
aws lambda update-function-configuration \
--function-name my-function \
--layers \
arn:aws:lambda:ap-northeast-1:123456789012:layer:my-common-libs:1 \
arn:aws:lambda:ap-northeast-1:123456789012:layer:my-utilities:3
# レイヤーバージョンの一覧
aws lambda list-layer-versions \
--layer-name my-common-libs
# レイヤーの削除
aws lambda delete-layer-version \
--layer-name my-common-libs \
--version-number 16.3 共有ユーティリティレイヤーの実装例
# レイヤーに含めるユーティリティモジュール
# python/lib/python3.12/site-packages/common/response.py
import json
from typing import Any, Optional
def api_response(
status_code: int,
body: Any,
headers: Optional[dict] = None
) -> dict:
"""API Gateway 用の標準レスポンスを生成する"""
default_headers = {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Content-Type,Authorization",
"Access-Control-Allow-Methods": "GET,POST,PUT,DELETE,OPTIONS",
}
if headers:
default_headers.update(headers)
return {
"statusCode": status_code,
"headers": default_headers,
"body": json.dumps(body, ensure_ascii=False, default=str),
}
def error_response(
status_code: int,
message: str,
error_code: Optional[str] = None
) -> dict:
"""エラーレスポンスを生成する"""
body = {"error": {"message": message}}
if error_code:
body["error"]["code"] = error_code
return api_response(status_code, body)# レイヤーに含めるロギングモジュール
# python/lib/python3.12/site-packages/common/logger.py
import json
import logging
import os
import sys
from datetime import datetime, timezone
class StructuredLogger:
"""構造化ログを出力するロガー"""
def __init__(self, service_name: str = None):
self.service_name = service_name or os.environ.get("SERVICE_NAME", "unknown")
self.logger = logging.getLogger(self.service_name)
self.logger.setLevel(os.environ.get("LOG_LEVEL", "INFO"))
# JSON フォーマッタの設定
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter("%(message)s"))
self.logger.handlers = [handler]
def _format(self, level: str, message: str, **kwargs) -> str:
log_entry = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"level": level,
"service": self.service_name,
"message": message,
**kwargs
}
return json.dumps(log_entry, ensure_ascii=False, default=str)
def info(self, message: str, **kwargs):
self.logger.info(self._format("INFO", message, **kwargs))
def error(self, message: str, **kwargs):
self.logger.error(self._format("ERROR", message, **kwargs))
def warning(self, message: str, **kwargs):
self.logger.warning(self._format("WARNING", message, **kwargs))
def debug(self, message: str, **kwargs):
self.logger.debug(self._format("DEBUG", message, **kwargs))7. Lambda のモニタリングとデバッグ
7.1 CloudWatch Logs Insights によるログ分析
# エラーログの検索
fields @timestamp, @message, @requestId
| filter @message like /ERROR/
| sort @timestamp desc
| limit 50
# コールドスタートの検出
filter @message like /Init Duration/
| parse @message "Init Duration: * ms" as initDuration
| stats count() as coldStarts,
avg(initDuration) as avgInitMs,
max(initDuration) as maxInitMs,
pct(initDuration, 99) as p99InitMs
by bin(1h)
# 実行時間の分析
filter @type = "REPORT"
| parse @message "Duration: * ms" as duration
| parse @message "Billed Duration: * ms" as billedDuration
| parse @message "Memory Size: * MB" as memorySize
| parse @message "Max Memory Used: * MB" as memoryUsed
| stats avg(duration) as avgDuration,
max(duration) as maxDuration,
pct(duration, 95) as p95Duration,
pct(duration, 99) as p99Duration,
avg(memoryUsed/memorySize * 100) as avgMemoryUtilization
by bin(5m)
# タイムアウトの検出
filter @message like /Task timed out/
| parse @message "Task timed out after * seconds" as timeout
| stats count() by bin(1h)
7.2 X-Ray によるトレーシング
# Lambda 関数の X-Ray トレーシングを有効化
aws lambda update-function-configuration \
--function-name my-function \
--tracing-config Mode=Active
# X-Ray トレースの取得
aws xray get-trace-summaries \
--start-time $(date -d '1 hour ago' +%s) \
--end-time $(date +%s) \
--filter-expression 'service("my-function")'# X-Ray SDK による詳細トレーシング
from aws_xray_sdk.core import xray_recorder
from aws_xray_sdk.core import patch_all
# 全 AWS SDK 呼び出しを自動トレース
patch_all()
@xray_recorder.capture("process_order")
def process_order(order_data):
"""カスタムサブセグメントでビジネスロジックをトレース"""
# アノテーションの追加 (フィルタリング用)
subsegment = xray_recorder.current_subsegment()
subsegment.put_annotation("order_id", order_data["orderId"])
subsegment.put_annotation("customer_tier", order_data.get("tier", "standard"))
# メタデータの追加 (デバッグ用)
subsegment.put_metadata("order_details", order_data, "order")
# ビジネスロジック
result = validate_order(order_data)
return result
def lambda_handler(event, context):
return process_order(event)7.3 Lambda Insights
# Lambda Insights の有効化 (拡張モニタリング)
aws lambda update-function-configuration \
--function-name my-function \
--layers \
"arn:aws:lambda:ap-northeast-1:580247275435:layer:LambdaInsightsExtension:38"
# Lambda Insights が収集するメトリクス:
# - cpu_total_time: CPU使用時間
# - memory_utilization: メモリ使用率
# - rx_bytes / tx_bytes: ネットワーク I/O
# - init_duration: Init Phase の時間
# - tmp_max: /tmp 使用量7.4 カスタムメトリクスの埋め込み (EMF)
# Embedded Metric Format (EMF) による
# Lambda からの高解像度カスタムメトリクス出力
import json
import time
def put_metric(namespace, metric_name, value, unit="None", dimensions=None):
"""EMF形式でCloudWatchカスタムメトリクスを出力"""
emf_log = {
"_aws": {
"Timestamp": int(time.time() * 1000),
"CloudWatchMetrics": [
{
"Namespace": namespace,
"Dimensions": [list(dimensions.keys())] if dimensions else [[]],
"Metrics": [
{"Name": metric_name, "Unit": unit}
]
}
]
},
metric_name: value
}
if dimensions:
emf_log.update(dimensions)
# 標準出力に書くだけで CloudWatch メトリクスとして記録される
print(json.dumps(emf_log))
def lambda_handler(event, context):
start = time.time()
# ビジネスロジック
result = process_request(event)
# カスタムメトリクスの出力
elapsed = (time.time() - start) * 1000
put_metric(
"MyApplication",
"ProcessingTime",
elapsed,
"Milliseconds",
{"Environment": "production", "Service": "order-api"}
)
put_metric(
"MyApplication",
"OrdersProcessed",
1,
"Count",
{"Environment": "production", "Service": "order-api"}
)
return result8. Lambda のセキュリティ
8.1 最小権限の IAM ポリシー
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DynamoDBAccess",
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:Query"
],
"Resource": [
"arn:aws:dynamodb:ap-northeast-1:123456789012:table/Orders",
"arn:aws:dynamodb:ap-northeast-1:123456789012:table/Orders/index/*"
]
},
{
"Sid": "S3ReadAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::my-config-bucket/config/*"
},
{
"Sid": "SecretsManagerAccess",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:my-api-key-*"
},
{
"Sid": "CloudWatchLogs",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:ap-northeast-1:123456789012:log-group:/aws/lambda/my-function:*"
}
]
}8.2 Secrets Manager / Parameter Store 統合
# Secrets Manager からシークレットを取得する
# Lambda Extensions を使ったキャッシュ方式
import json
import os
import urllib3
# Lambda Extensions のキャッシュポート
SECRETS_EXTENSION_PORT = 2773
http = urllib3.PoolManager()
def get_secret(secret_name):
"""Secrets Manager Lambda Extension 経由でシークレットを取得"""
url = (
f"http://localhost:{SECRETS_EXTENSION_PORT}"
f"/secretsmanager/get?secretId={secret_name}"
)
headers = {
"X-Aws-Parameters-Secrets-Token": os.environ["AWS_SESSION_TOKEN"]
}
response = http.request("GET", url, headers=headers)
return json.loads(response.data)["SecretString"]
# Init Phase でシークレットを取得 (キャッシュされる)
DB_CREDENTIALS = json.loads(get_secret("prod/db-credentials"))
API_KEY = get_secret("prod/external-api-key")
def lambda_handler(event, context):
# シークレットを使用
db_host = DB_CREDENTIALS["host"]
db_password = DB_CREDENTIALS["password"]
# ...8.3 VPC Lambda のセキュリティ設計
VPC Lambda のネットワーク設計:
+----------------------------------------------------------+
| VPC (10.0.0.0/16) |
| |
| Private Subnet A Private Subnet B |
| +------------------------+ +------------------------+ |
| | Lambda ENI | | Lambda ENI | |
| | (自動生成) | | (自動生成) | |
| +------------------------+ +------------------------+ |
| | | |
| v v |
| +---------------------------------------------------+ |
| | セキュリティグループ (Lambda-SG) | |
| | Outbound: 必要なポートのみ | |
| +---------------------------------------------------+ |
| | |
| +---> RDS (DB-SG: Lambda-SG からの 3306 許可) |
| | |
| +---> ElastiCache (Cache-SG: Lambda-SG からの |
| | 6379 許可) |
| | |
| +---> VPC Endpoint (DynamoDB, S3, SQS) |
| | (NAT Gateway 不要) |
| | |
| +---> NAT Gateway --> IGW --> Internet |
| (外部API呼び出しが必要な場合のみ) |
+----------------------------------------------------------+
# VPC Lambda 用のセキュリティグループ作成
aws ec2 create-security-group \
--group-name lambda-sg \
--description "Security group for Lambda functions" \
--vpc-id vpc-12345678
# RDS へのアクセスを許可 (RDS の SG に追加)
aws ec2 authorize-security-group-ingress \
--group-id sg-rds-12345678 \
--protocol tcp \
--port 3306 \
--source-group sg-lambda-12345678
# VPC Endpoint の作成 (DynamoDB)
aws ec2 create-vpc-endpoint \
--vpc-id vpc-12345678 \
--service-name com.amazonaws.ap-northeast-1.dynamodb \
--route-table-ids rtb-12345678
# VPC Endpoint の作成 (S3)
aws ec2 create-vpc-endpoint \
--vpc-id vpc-12345678 \
--service-name com.amazonaws.ap-northeast-1.s3 \
--route-table-ids rtb-12345678
# VPC Endpoint の作成 (SQS - Interface 型)
aws ec2 create-vpc-endpoint \
--vpc-id vpc-12345678 \
--service-name com.amazonaws.ap-northeast-1.sqs \
--vpc-endpoint-type Interface \
--subnet-ids subnet-111 subnet-222 \
--security-group-ids sg-vpce-123456789. Lambda のコンテナイメージサポート
9.1 コンテナイメージでのデプロイ
# Lambda コンテナイメージの Dockerfile 例 (Python)
FROM public.ecr.aws/lambda/python:3.12
# 依存関係のインストール
COPY requirements.txt ${LAMBDA_TASK_ROOT}/
RUN pip install -r ${LAMBDA_TASK_ROOT}/requirements.txt --no-cache-dir
# 関数コードのコピー
COPY app/ ${LAMBDA_TASK_ROOT}/app/
COPY lambda_function.py ${LAMBDA_TASK_ROOT}/
# ハンドラの指定
CMD ["lambda_function.lambda_handler"]# コンテナイメージのビルドとデプロイ
# 1. ECR リポジトリの作成
aws ecr create-repository \
--repository-name my-lambda-function \
--image-scanning-configuration scanOnPush=true
# 2. イメージのビルド
docker build -t my-lambda-function:latest \
--platform linux/amd64 .
# 3. ECR にプッシュ
ECR_URI=123456789012.dkr.ecr.ap-northeast-1.amazonaws.com
aws ecr get-login-password | docker login --username AWS --password-stdin ${ECR_URI}
docker tag my-lambda-function:latest ${ECR_URI}/my-lambda-function:latest
docker push ${ECR_URI}/my-lambda-function:latest
# 4. Lambda 関数の作成
aws lambda create-function \
--function-name my-container-function \
--package-type Image \
--code ImageUri=${ECR_URI}/my-lambda-function:latest \
--role arn:aws:iam::123456789012:role/lambda-execution-role \
--memory-size 1024 \
--timeout 309.2 マルチステージビルドによる最適化
# マルチステージビルドで軽量なLambdaコンテナを作成
FROM python:3.12-slim AS builder
WORKDIR /build
COPY requirements.txt .
RUN pip install --user -r requirements.txt --no-cache-dir
# 本番ステージ
FROM public.ecr.aws/lambda/python:3.12
# ビルドステージからの依存関係コピー
COPY --from=builder /root/.local/lib/python3.12/site-packages ${LAMBDA_TASK_ROOT}/
COPY app/ ${LAMBDA_TASK_ROOT}/app/
COPY lambda_function.py ${LAMBDA_TASK_ROOT}/
CMD ["lambda_function.lambda_handler"]10. Lambda 関数 URL
10.1 関数 URL の設定
# 関数 URL の作成 (IAM 認証なし)
aws lambda create-function-url-config \
--function-name my-api-function \
--auth-type NONE \
--cors '{
"AllowCredentials": false,
"AllowHeaders": ["content-type", "authorization"],
"AllowMethods": ["GET", "POST", "PUT", "DELETE"],
"AllowOrigins": ["https://example.com"],
"ExposeHeaders": ["x-request-id"],
"MaxAge": 86400
}'
# リソースベースポリシーの追加 (パブリックアクセス)
aws lambda add-permission \
--function-name my-api-function \
--statement-id AllowPublicAccess \
--action lambda:InvokeFunctionUrl \
--principal "*" \
--function-url-auth-type NONE
# 関数 URL の取得
aws lambda get-function-url-config \
--function-name my-api-function
# IAM 認証付き関数 URL
aws lambda create-function-url-config \
--function-name my-internal-api \
--auth-type AWS_IAM10.2 API Gateway vs 関数 URL
| 機能 | API Gateway | Lambda 関数 URL |
|---|---|---|
| 料金 | リクエスト + データ転送 | 無料 (Lambda 料金のみ) |
| カスタムドメイン | あり | CloudFront 経由で可能 |
| 認証 | Cognito, API Key, Lambda オーソライザー | IAM or なし |
| レート制限 | あり | なし (Lambda 同時実行数のみ) |
| WAF 統合 | あり | CloudFront 経由で可能 |
| キャッシュ | あり | なし |
| リクエスト変換 | あり | なし |
| 用途 | 本格的な API | シンプルな API, Webhook |
11. アンチパターン
11.1 Lambda を VPC 内に不必要に配置する
[悪い例]
Lambda --VPC内--> NAT Gateway --> Internet --> DynamoDB
[良い例]
Lambda --VPC外--> DynamoDB (VPCエンドポイント不要)
Lambda --VPC内--> RDS (VPC内リソースへのアクセスが必要な場合のみ)
+-----> VPC Endpoint --> DynamoDB
問題点: VPC 配置にすると ENI アタッチ時間が追加され、コールドスタートが増加する(改善済みだが依然として若干のオーバーヘッドあり)。NAT Gateway 経由のインターネットアクセスにはコストもかかる。
改善: RDS や ElastiCache など VPC 内リソースへのアクセスが本当に必要な場合のみ VPC 配置にし、DynamoDB や S3 へは VPC エンドポイント経由でアクセスする。
11.2 Provisioned Concurrency の過剰設定
問題点: トラフィックパターンを分析せず、常に最大値を設定するとコストが無駄になる。
改善: CloudWatch メトリクスで実際の同時実行数を分析し、Application Auto Scaling でトラフィックパターンに合わせて動的に調整する。
11.3 Lambda 関数のモノリス化
[悪い例]
1つの Lambda 関数に全APIエンドポイントの処理を詰め込む:
/users GET, POST, PUT, DELETE
/orders GET, POST, PUT, DELETE
/products GET, POST, PUT, DELETE
→ パッケージが肥大化、デプロイが全APIに影響
[良い例]
機能単位で関数を分離:
user-get-function
user-create-function
order-process-function
→ 各関数が軽量、独立してデプロイ・スケール可能
[バランスの取れたアプローチ]
リソース単位で関数を分離:
user-api-function (User の CRUD をまとめる)
order-api-function (Order の CRUD をまとめる)
→ 関数数の爆発を防ぎつつ、適度に分離
11.4 同期呼び出しの連鎖
[悪い例]
API GW -> Lambda A -> Lambda B -> Lambda C
各呼び出しがタイムアウトを待つ
→ レイテンシが累積、エラーハンドリングが複雑
[良い例]
API GW -> Lambda A -> SQS -> Lambda B -> SQS -> Lambda C
非同期処理で分離
→ 各関数が独立、リトライも個別に制御
[Step Functions を使う場合]
API GW -> Step Functions
-> Lambda A (Validate)
-> Lambda B (Process)
-> Lambda C (Notify)
→ オーケストレーション、エラーハンドリング、リトライが統一的に管理
12. CloudFormation / CDK テンプレート
12.1 CloudFormation テンプレート
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: 'Lambda 応用構成テンプレート'
Parameters:
EnvironmentName:
Type: String
Default: dev
AllowedValues: [dev, stg, prod]
ProvisionedConcurrency:
Type: Number
Default: 0
Description: 'Provisioned Concurrency 数 (0=無効)'
Conditions:
EnableProvisionedConcurrency: !Not [!Equals [!Ref ProvisionedConcurrency, 0]]
IsProduction: !Equals [!Ref EnvironmentName, prod]
Globals:
Function:
Runtime: python3.12
MemorySize: 1024
Timeout: 30
Tracing: Active
Environment:
Variables:
ENVIRONMENT: !Ref EnvironmentName
TABLE_NAME: !Ref OrdersTable
LOG_LEVEL: !If [IsProduction, INFO, DEBUG]
Resources:
# Lambda 関数
OrderApiFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub '${EnvironmentName}-order-api'
Handler: app.lambda_handler
CodeUri: src/order-api/
AutoPublishAlias: live
ProvisionedConcurrencyConfig:
!If
- EnableProvisionedConcurrency
- ProvisionedConcurrentExecutions: !Ref ProvisionedConcurrency
- !Ref AWS::NoValue
DeploymentPreference:
Type: !If [IsProduction, Linear10PercentEvery1Minute, AllAtOnce]
Alarms:
- !Ref OrderApiErrorAlarm
Events:
GetOrder:
Type: Api
Properties:
Path: /orders/{orderId}
Method: get
RestApiId: !Ref ApiGateway
CreateOrder:
Type: Api
Properties:
Path: /orders
Method: post
RestApiId: !Ref ApiGateway
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref OrdersTable
- SQSSendMessagePolicy:
QueueName: !GetAtt OrderQueue.QueueName
# Step Functions ステートマシン
OrderProcessingStateMachine:
Type: AWS::StepFunctions::StateMachine
Properties:
StateMachineName: !Sub '${EnvironmentName}-order-processing'
DefinitionString: !Sub |
{
"Comment": "注文処理ワークフロー",
"StartAt": "ValidateOrder",
"States": {
"ValidateOrder": {
"Type": "Task",
"Resource": "${ValidateOrderFunction.Arn}",
"Next": "ProcessPayment",
"Catch": [{"ErrorEquals": ["ValidationError"], "Next": "FailOrder"}]
},
"ProcessPayment": {
"Type": "Task",
"Resource": "${ProcessPaymentFunction.Arn}",
"Retry": [{"ErrorEquals": ["States.TaskFailed"], "IntervalSeconds": 5, "MaxAttempts": 3, "BackoffRate": 2.0}],
"Next": "NotifySuccess",
"Catch": [{"ErrorEquals": ["States.ALL"], "Next": "FailOrder"}]
},
"NotifySuccess": {
"Type": "Task",
"Resource": "arn:aws:states:::sns:publish",
"Parameters": {
"TopicArn": "${OrderNotificationTopic}",
"Message.$": "States.Format('Order {} processed successfully', $.orderId)"
},
"End": true
},
"FailOrder": {
"Type": "Task",
"Resource": "${FailOrderFunction.Arn}",
"End": true
}
}
}
RoleArn: !GetAtt StepFunctionsRole.Arn
# DynamoDB テーブル
OrdersTable:
Type: AWS::DynamoDB::Table
DeletionPolicy: Retain
Properties:
TableName: !Sub '${EnvironmentName}-orders'
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: orderId
AttributeType: S
- AttributeName: customerId
AttributeType: S
KeySchema:
- AttributeName: orderId
KeyType: HASH
GlobalSecondaryIndexes:
- IndexName: customer-index
KeySchema:
- AttributeName: customerId
KeyType: HASH
Projection:
ProjectionType: ALL
# CloudWatch アラーム
OrderApiErrorAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub '${EnvironmentName}-order-api-errors'
MetricName: Errors
Namespace: AWS/Lambda
Statistic: Sum
Period: 60
EvaluationPeriods: 3
Threshold: 5
ComparisonOperator: GreaterThanOrEqualToThreshold
Dimensions:
- Name: FunctionName
Value: !Ref OrderApiFunction
AlarmActions:
- !Ref AlertTopic
Outputs:
ApiEndpoint:
Description: API Gateway エンドポイント
Value: !Sub 'https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/Prod'
StateMachineArn:
Description: Step Functions ステートマシン ARN
Value: !Ref OrderProcessingStateMachine13. FAQ
Q1. Provisioned Concurrency と Reserved Concurrency の違いは?
Reserved Concurrency は関数の同時実行数の「上限」を設定するもので、追加コストはない。他の関数のスロットルから特定の関数を保護する。一方、Provisioned Concurrency は指定数の実行環境を「事前に初期化」しておくもので、コールドスタートをなくす代わりに追加料金が発生する。
Q2. Step Functions のコストはどのくらいですか?
Standard ワークフローは状態遷移ごとに $0.025/1,000 遷移で課金される。1回の実行に 5 遷移あり、月間 100 万実行の場合は $125/月となる。Express ワークフローは実行回数とメモリ・実行時間で課金され、大量・短時間処理には割安になる。
Q3. Lambda の最大同時実行数を超えるとどうなりますか?
スロットリングが発生する。同期呼び出しでは HTTP 429 エラーが返り、非同期呼び出しではイベントキューに格納され最大 6 時間リトライされる。Service Quotas からクォータ引き上げを申請するか、Reserved Concurrency で重要な関数のキャパシティを確保することで対処する。
Q4. Lambda レイヤーとコンテナイメージのどちらを使うべきですか?
ZIP パッケージ + レイヤーは軽量な関数に適しており、起動速度が最も速い。コンテナイメージは 10GB までのサイズに対応し、既存の Docker ワークフローを活用できる。ML モデルや大規模な依存関係がある場合はコンテナイメージが適している。一般的な Web API やイベント処理には ZIP パッケージが推奨される。
Q5. Lambda Extension とは何ですか?
Lambda Extension は Lambda の実行環境に統合される外部プロセスで、モニタリング、セキュリティ、ガバナンスの機能を追加できる。内部 Extension (同一プロセス) と外部 Extension (別プロセス) の 2 種類がある。代表的なものに Datadog Agent、New Relic Agent、AWS Parameters and Secrets Lambda Extension がある。
Q6. Graviton (ARM) と x86 のどちらを選ぶべきですか?
Graviton (arm64) は x86_64 と比較して最大 34% のコスト削減 (20% の料金差 + 性能向上) が見込める。Python、Node.js、Java などのランタイムでは arm64 への移行は通常、コード変更なしで可能。ネイティブバイナリを含むレイヤーや依存関係がある場合は arm64 対応の確認が必要。新規関数では arm64 を優先的に検討すべきである。
FAQ
Q1: このトピックを学ぶ上で最も重要なポイントは何ですか?
実践的な経験を積むことが最も重要です。理論だけでなく、実際にコードを書いて動作を確認することで理解が深まります。
Q2: 初心者がよく陥る間違いは何ですか?
基礎を飛ばして応用に進むことです。このガイドで説明している基本概念をしっかり理解してから、次のステップに進むことをお勧めします。
Q3: 実務ではどのように活用されていますか?
このトピックの知識は、日常的な開発業務で頻繁に活用されます。特にコードレビューやアーキテクチャ設計の際に重要になります。
まとめ
| 項目 | ポイント |
|---|---|
| コールドスタート | Init コードの最適化、パッケージ軽量化、メモリ増強で緩和 |
| Provisioned Concurrency | 事前に実行環境を確保しコールドスタートを排除 |
| Lambda Destinations | 非同期実行の成功・失敗を柔軟にルーティング |
| Step Functions | 複数 Lambda のオーケストレーション、エラーハンドリング、リトライ |
| SnapStart | Java のコールドスタートをスナップショット復元で大幅短縮 |
| Lambda レイヤー | 共通ライブラリの一元管理、デプロイパッケージの軽量化 |
| モニタリング | CloudWatch Logs Insights、X-Ray、Lambda Insights で可観測性を確保 |
| セキュリティ | 最小権限 IAM、Secrets Manager 統合、VPC 設計 |
| コンテナイメージ | 大規模依存関係や既存 Docker ワークフローの活用に |
| 関数 URL | シンプルな HTTP エンドポイント (API Gateway なし) |
| VPC 配置 | 必要な場合のみ。VPC エンドポイントを積極的に活用 |
次に読むべきガイド
- サーバーレスパターン -- 実践的なアーキテクチャパターン
- CloudFormation -- Lambda のインフラをコード化
- コスト最適化 -- Lambda のコスト管理
参考文献
- AWS 公式ドキュメント「Lambda のパフォーマンスの最適化」 https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html
- AWS 公式ドキュメント「AWS Step Functions デベロッパーガイド」 https://docs.aws.amazon.com/step-functions/latest/dg/
- Yan Cui「Production-Ready Serverless」Manning Publications, 2019
- AWS re:Invent 2023「SVS404: Optimizing Lambda performance for your serverless applications」
- AWS 公式ドキュメント「Lambda レイヤー」 https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html
- AWS 公式ドキュメント「Lambda SnapStart」 https://docs.aws.amazon.com/lambda/latest/dg/snapstart.html
- AWS 公式ドキュメント「Lambda 関数 URL」 https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html