AWS Lambdaはサーバーレスの代名詞ですが、「関数を書いてデプロイすれば終わり」ではありません。本番運用を見据えた設計を怠ると、予期しないエラーやパフォーマンス問題に悩まされます。この記事ではLambda関数の実践的な設計パターンを解説します。
コールドスタートとの付き合い方
Lambdaはリクエストがないとき実行環境が破棄され、次のリクエスト時に再構築されます。これがコールドスタートで、Python/Node.jsで数百ms、Java/.NETでは数秒かかることもあります。
対策としては、Provisioned Concurrencyの利用、Lambda Layersでの依存パッケージ分離、初期化処理をハンドラの外に出す(グローバル変数活用)の3つが有効です。
import boto3
import os
# ハンドラの外で初期化 → コンテナ再利用時にスキップされる
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['TABLE_NAME'])
def lambda_handler(event, context):
# ここでは初期化済みのtableを使うだけ
response = table.get_item(Key={'id': event['id']})
return {
'statusCode': 200,
'body': response.get('Item', {})
}
メモリとタイムアウトの設定指針
Lambdaのメモリ設定はCPU割り当てにも影響します。メモリを増やすと処理が速くなり、結果的にコストが下がるケースも少なくありません。AWS Lambda Power Tuningツールを使えば、最適なメモリ量を自動的に測定できます。
タイムアウトはデフォルトの3秒から、処理内容に応じて適切に設定します。API Gateway経由の場合は最大29秒、非同期呼び出しの場合は最大15分まで設定可能です。
エラーハンドリングの設計
Lambdaのエラーハンドリングは呼び出しパターンによって挙動が異なります。同期呼び出し(API Gateway経由)ではクライアントにエラーが直接返りますが、非同期呼び出し(S3イベントやSNS)ではLambdaが自動的に2回リトライします。
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
try:
# メイン処理
result = process_event(event)
logger.info(f"処理成功: {json.dumps(result)}")
return {
'statusCode': 200,
'body': json.dumps(result)
}
except ValueError as e:
# クライアントエラー(リトライ不要)
logger.warning(f"バリデーションエラー: {str(e)}")
return {
'statusCode': 400,
'body': json.dumps({'error': str(e)})
}
except Exception as e:
# サーバーエラー(リトライ可能)
logger.error(f"予期しないエラー: {str(e)}", exc_info=True)
raise # 非同期呼び出しの場合はリトライさせる
Dead Letter Queueの設定
非同期呼び出しでリトライしても失敗したイベントは、Dead Letter Queue(DLQ)に送ることで後から調査・再処理が可能です。SQSまたはSNSをDLQとして設定できます。失敗したイベントを見逃さないために、本番環境では必ず設定しましょう。
まとめ
Lambdaは手軽に始められますが、本番品質にするには設計パターンの理解が必要です。コールドスタート対策、適切なメモリ/タイムアウト設定、呼び出しパターン別のエラーハンドリング、DLQの設定を押さえれば、安定したサーバーレスアーキテクチャを構築できます。

コメント