AWS Lambdaのタイムアウトエラーを根絶する実践的な対処法

AWS Lambdaでタイムアウトが発生する原因を特定し、実装段階での予防策と本番環境での緊急対応を同時に習得できます。この記事で解説する3つの対処パターンを適用すれば、タイムアウトによる障害をほぼ完全に防げます。

AWS Lambdaのタイムアウト問題とは

AWS Lambdaのデフォルトタイムアウト時間は3秒で、最大15分(900秒)まで設定可能です。タイムアウトを超過するとLambda関数は強制終了され、エラーレスポンスが返されます。これにより、APIゲートウェイ経由のリクエストが失敗したり、非同期処理が中断したりします。

タイムアウトが頻発する状況では、単に制限時間を延長するのではなく、根本原因を特定して処理を最適化することが重要です。

タイムアウトの主な原因5つと診断方法

1. 外部APIへのネットワーク待機が長い

データベースやサードパーティのAPIをコール中に、レスポンスを無制限に待機している場合が大半です。CloudWatch Logsで実行時間を分析し、どの処理に時間がかかっているかを特定してください。

2. Lambdaがプライベートサブネット内にある

プライベートサブネット内のLambdaは外部APIへのアクセスにNATゲートウェイを経由するため、スタートアップオーバーヘッドが2~3秒増加します。VPC設定を確認し、必要な場合は設定の最適化が必要です。

3. コールドスタートによる初期化遅延

Lambdaが起動時にモジュールをロードする場合、特にNode.jsやPythonの重い依存ライブラリがあると、初回実行だけで2~5秒消費します。

4. ループ処理の非効率な実装

データベーストランザクション内で個別クエリを連続実行(N+1問題)している場合、データ量が増えるとタイムアウトします。

5. メモリ割り当てが不足している

Lambdaのメモリ割り当てが小さいと、CPUスロットルが発生し、実行速度が低下します。メモリを増やすことでCPU性能も向上します。

対処法1:タイムアウト制限の適切な設定

まずは現在のタイムアウト設定を確認し、関数の実行パターンに応じて調整します。

マネジメントコンソールでの設定変更

AWS Lambda コンソール → 対象の関数 → 「一般設定」 → 「タイムアウト」で値を変更できます。デフォルトの3秒から始める場合、段階的に調整することをお勧めします:

  • APIレスポンス(同期処理): 15~30秒
  • バッチ処理: 300~600秒
  • 非同期タスク: 60~180秒

CloudFormationによる Infrastructure as Code での管理

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  MyLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: my-function
      Runtime: python3.11
      Handler: index.lambda_handler
      Code:
        ZipFile: |
          def lambda_handler(event, context):
              return {'statusCode': 200}
      Timeout: 60  # 60秒に設定
      MemorySize: 512  # メモリを512MBに設定
  

対処法2:非同期処理への分割と最適化

長時間実行される処理は、Lambdaの同期実行を避け、SQS や Step Functions を使って非同期化します。

SQSを経由した非同期ジョブ処理

APIレスポンスはすぐに返し、実際の処理をSQSキューに投げることで、タイムアウトを回避できます。

import boto3
import json

sqs = boto3.client('sqs')
QUEUE_URL = 'https://sqs.ap-northeast-1.amazonaws.com/123456789/my-queue'

def lambda_handler(event, context):
    """
    即座にレスポンスを返し、重い処理をSQSにオフロード
    """
    try:
        # クイックバリデーション(タイムアウト前に完了)
        if 'data' not in event:
            return {'statusCode': 400, 'body': 'Missing data'}
        
        # 重い処理をSQSメッセージとしてキューに投入
        message_body = {
            'user_id': event['user_id'],
            'data': event['data'],
            'timestamp': event['timestamp']
        }
        
        response = sqs.send_message(
            QueueUrl=QUEUE_URL,
            MessageBody=json.dumps(message_body)
        )
        
        # クライアントには即座にレスポンス
        return {
            'statusCode': 202,
            'body': json.dumps({
                'message': 'Job queued',
                'messageId': response['MessageId']
            })
        }
    except Exception as e:
        return {'statusCode': 500, 'body': str(e)}
  

別途のワーカーLambda関数でバッチ処理

def batch_processor_lambda_handler(event, context):
    """
    SQSイベントトリガーで複数メッセージを効率的に処理
    """
    for record in event['Records']:
        message_body = json.loads(record['body'])
        user_id = message_body['user_id']
        data = message_body['data']
        
        # ここで長時間実行される処理を実施
        # データベースへの大量書き込みや外部API呼び出し
        process_heavy_task(user_id, data)
        
        # 処理完了後、メッセージをSQSから削除
        sqs.delete_message(
            QueueUrl=QUEUE_URL,
            ReceiptHandle=record['receiptHandle']
        )
  

対処法3:コールドスタートの短縮とコネクション再利用

グローバルスコープでのコネクション初期化

データベースコネクションやHTTPクライアントをLambdaハンドラの外側(グローバルスコープ)で初期化することで、ウォームスタート時は初期化をスキップできます。

import json
import boto3
import requests
from datetime import datetime

# グローバルスコープで初期化(最初の1回だけ実行)
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('users')
session = requests.Session()

def lambda_handler(event, context):
    """
    2回目以降の実行は初期化をスキップ
    """
    try:
        user_id = event['user_id']
        
        # DynamoDBからデータ取得(再利用コネクション)
        response = table.get_item(Key={'userId': user_id})
        
        # 外部APIをコール(再利用セッション)
        api_response = session.get(
            'https://api.example.com/data',
            timeout=5  # タイムアウト明示的に指定
        )
        
        return {
            'statusCode': 200,
            'body': json.dumps({'data': response.get('Item')})
        }
    except Exception as e:
        return {'statusCode': 500, 'body': str(e)}
  

Lambda Layerで依存ライブラリを事前パッケージ化

pandas や numpy などの重いライブラリは、Lambda Layerとして別途パッケージ化することで、関数コードのサイズを削減し、コールドスタート時間を短縮できます。

# ローカル環境での準備
mkdir -p python/lib/python3.11/site-packages
pip install -t python/lib/python3.11/site-packages/ pandas numpy

# Layerとしてzipファイルを作成
zip -r my-layer.zip python/

# AWS CLIでアップロード
aws lambda publish-layer-version \
  --layer-name my-dependencies \
  --zip-file fileb://my-layer.zip \
  --compatible-runtimes python3.11
  

対処法4:メモリ割り当ての最適化

Lambdaのメモリ割り当てはCPU性能に直結します。メモリを増やすことで処理速度が向上し、結果的に実行時間が短縮されます。

メモリサイズ vCPU相当 用途
128 MB 0.05 シンプルなロジック、I/O待機メイン
512 MB 0.21 標準的なAPI、軽度なデータ処理
1024 MB 0.42 複雑な計算、大容量JSON処理
3008 MB 1 機械学習推論、大規模データ集計

メモリを512MBから1024MBへ増やした場合、実行時間が40~50%短縮されるケースが多いです。

対処法5:タイムアウトをキャッチして確実に処理を完了する

Context オブジェクトで残り時間を監視

Lambda関数実行中、コンテキストオブジェクトから残り実行時間を取得できます。タイムアウト直前に状態を保存し、次回実行で続行する設計にします。

import json

def lambda_handler(event, context):
    """
    残り時間を監視して、タイムアウト前に状態保存
    """
    items_to_process = event.get('items', [])
    start_index = event.get('start_index', 0)
    
    processed_items = []
    
    for i, item in enumerate(items_to_process[start_index:], start=start_index):
        # 残り時間を確認(秒単位)
        remaining_time = context.get_remaining_time_in_millis() / 1000
        
        # 残り時間が10秒未満の場合、次のバッチに委譲
        if remaining_time < 10:
            return {
                'statusCode': 202,
                'body': json.dumps({
                    'message': 'Continuing in next invocation',
                    'processed_count': len(processed_items),
                    'next_start_index': i
                })
            }
        
        # 処理を実行
        process_item(item)
        processed_items.append(item)
    
    return {
        'statusCode': 200,
        'body': json.dumps({
            'message': 'All items processed',
            'processed_count': len(processed_items)
        })
    }
  

よくある質問

いいえ、AWS Lambdaの最大タイムアウトは900秒(15分)に固定されています。それ以上の長時間処理が必要な場合は、AWS Fargate や EC2 インスタンスの使用を検討してください。

メトリクスフィルタを作成して、「Task timed out」というエラーメッセージを検出します。CloudWatch Alarms と連携させて、タイムアウト発生時に通知を送ることもできます。

セキュリティグループでLambdaのインバウンドトラフィックを許可しているか確認してください。また、RDSプロキシを使用することで、接続プールをLambda側で管理でき、タイムアウトを軽減できます。

ハマりやすいポイントと解決策

ポイント1: SQSの可視性タイムアウトとLambdaのタイムアウトの

K
AWS・Python・生成AIを専門とするソフトウェアエンジニア。AI・クラウド・開発ワークフローの実践ガイドを執筆しています。詳しく見る →