AWS Lambdaのタイムアウトエラーを解決する実装パターン

AWS Lambdaの実行時間制限により、長時間かかる処理が突然中断される問題に直面していませんか。この記事では、タイムアウトの仕組みを理解した上で、非同期処理、バッチ処理分割、ステートマシンといった具体的な解決策を実装レベルで解説します。

AWS Lambdaのタイムアウトとは

AWS Lambdaには関数の最大実行時間に制限があります。デフォルトでは3秒、最大15分(900秒)に設定できます。この時間を超えると、Lambda関数は強制的に終了され、Task timed out after X.XX secondsというエラーが発生します。

タイムアウトが発生する主な原因は、データベースクエリの遅延、外部API呼び出しの待機時間、大量データの処理です。単にタイムアウト時間を延長するだけでなく、設計レベルでの改善が重要です。

タイムアウト時間の確認と設定変更

まずは現在の設定を確認し、必要に応じて調整する方法を確認しましょう。

AWSマネジメントコンソールでの確認

Lambda関数の詳細ページで「設定」→「全般設定」を開くと、現在のタイムアウト時間が表示されます。この値を増やすだけでは根本的な解決にはなりませんが、一時的な対応として有効です。

AWS CLIでの設定変更

# タイムアウト時間を600秒(10分)に設定
aws lambda update-function-configuration \
  --function-name my-function \
  --timeout 600
  

CloudFormationやTerraformを使用している場合も同様に設定可能です。

解決策1:非同期処理への切り替え

長時間かかる処理を別のLambda関数に委譲し、即座に呼び出し元に応答を返すパターンです。

SNSを経由した非同期実行

import boto3
import json

sns_client = boto3.client('sns')

def lambda_handler(event, context):
    # 呼び出し元にはすぐに応答
    
    # 長時間かかる処理は別のLambda関数に委譲
    sns_client.publish(
        TopicArn='arn:aws:sns:ap-northeast-1:123456789012:long-task-topic',
        Message=json.dumps({
            'user_id': event['user_id'],
            'data': event['data']
        })
    )
    
    return {
        'statusCode': 202,
        'body': json.dumps('Processing started in background')
    }
  

この実装により、メイン関数はすぐに応答を返し、タイムアウトのリスクを軽減します。

SQSキューを使用したバッチ処理

import boto3
import json

sqs_client = boto3.client('sqs')
queue_url = 'https://sqs.ap-northeast-1.amazonaws.com/123456789012/long-task-queue'

def lambda_handler(event, context):
    # タスクをキューに追加
    for item in event['items']:
        sqs_client.send_message(
            QueueUrl=queue_url,
            MessageBody=json.dumps({
                'id': item['id'],
                'timestamp': event['timestamp']
            })
        )
    
    return {
        'statusCode': 200,
        'body': json.dumps(f"Added {len(event['items'])} items to queue")
    }

# 別のLambda関数でキューから取得して処理
def process_queue_messages(event, context):
    for record in event['Records']:
        message_body = json.loads(record['body'])
        # ここで長時間の処理を実行
        print(f"Processing item: {message_body['id']}")
  

解決策2:処理の分割とバッチ化

大量のデータを一度に処理せず、小分けにして複数回のLambda実行に分散させる方法です。

S3への中間データ保存によるバッチ処理

import boto3
import json

s3_client = boto3.client('s3')
lambda_client = boto3.client('lambda')

def lambda_handler(event, context):
    records = event['records']  # 1000件のデータ
    
    # 100件ずつバッチに分割
    batch_size = 100
    for i in range(0, len(records), batch_size):
        batch = records[i:i + batch_size]
        batch_number = i // batch_size
        
        # S3に一時保存
        s3_client.put_object(
            Bucket='temp-processing-bucket',
            Key=f"batches/batch-{batch_number}.json",
            Body=json.dumps(batch)
        )
        
        # 次のLambda関数を非同期で実行
        lambda_client.invoke(
            FunctionName='process-batch',
            InvocationType='Event',
            Payload=json.dumps({
                'batch_number': batch_number,
                'bucket': 'temp-processing-bucket'
            })
        )
    
    return {
        'statusCode': 200,
        'body': json.dumps(f"Divided into {(len(records) + batch_size - 1) // batch_size} batches")
    }
  

この方法により、各バッチは15分以内に処理完了できるようになります。

解決策3:Step Functions(ステートマシン)の活用

複数のLambda関数を組み合わせて長時間の処理フローを構築する際、Step Functionsが有効です。

Step Functions定義の例

{
  "Comment": "長時間処理のワークフロー",
  "StartAt": "データ準備",
  "States": {
    "データ準備": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:prepare-data",
      "Next": "並列処理"
    },
    "並列処理": {
      "Type": "Parallel",
      "Branches": [
        {
          "StartAt": "処理1",
          "States": {
            "処理1": {
              "Type": "Task",
              "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:process-part-1",
              "End": true
            }
          }
        },
        {
          "StartAt": "処理2",
          "States": {
            "処理2": {
              "Type": "Task",
              "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:process-part-2",
              "End": true
            }
          }
        }
      ],
      "Next": "結果統合"
    },
    "結果統合": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:aggregate-results",
      "End": true
    }
  }
}
  

Step Functionsは個別のステップが15分以内で完了すれば、ワークフロー全体は数時間かかっても問題ありません。

ハマりやすいポイントと対処法

Cold Startによる予期しないタイムアウト

Lambda関数が一定時間使用されないと、コンテナが停止される「Cold Start」が発生します。再起動時の初期化処理に数秒かかり、制限時間を圧迫することがあります。

対策として、Provisioned Concurrencyを設定することで、Lambda関数を常時起動状態に保つことができます。ただしコスト増加につながるため、本当に必要な場合に限定してください。

外部API呼び出しのタイムアウト設定

import requests
import boto3

def lambda_handler(event, context):
    # requestsのタイムアウトはLambdaのタイムアウトより短くする
    try:
        response = requests.get(
            'https://api.example.com/data',
            timeout=5  # Lambda全体の余裕を考慮
        )
    except requests.Timeout:
        print("API call timed out")
        # リトライまたは代替処理を実行
        return {'statusCode': 503, 'body': 'Service temporarily unavailable'}
  

ネットワークI/Oの最適化

RDSやDynamoDBへのクエリが遅い場合、コネクション数の制限やクエリの最適化が必要です。特にコールドスタート直後は数秒のレイテンシが発生しやすいため、接続プーリングの導入も検討してください。

使うべき場面と使うべきでない場面

Lambda+タイムアウト対策が適切な場面

  • APIリクエストの処理(数秒〜数分)
  • イベント駆動の軽量な変換処理
  • スケジュール実行による定期タスク

他のサービスを検討すべき場面

  • 常時稼働が必要な長時間処理 → EC2やECS
  • 数時間単位のバッチ処理 → AWS Batch
  • 複雑なワークフロー → Step Functions + Lambda

実装時のベストプラクティス

タイムアウト対策を実装する際は、以下の点に注意してください。

  • Lambda関数のタイムアウト値を、実際に必要な時間より30秒程度短く設定し、エラーハンドリングの時間を確保する
  • CloudWatch Logsで実行時間を監視し、タイムアウト発生前に異常を検知するアラートを設定する
  • 非同期処理を使う場合、DLQ(Dead Letter Queue)を設定して失敗したタスクを記録する
  • 定期的に実行時間をレビューし、タイムアウト設定の妥当性を検証する

よくある質問

A: いいえ。AWS Lambdaの最大実行時間は15分(900秒)に固定されており、これを超える設定はできません。それ以上の処理時間が必要な場合は、処理を分割するか、EC2やECS、AWS Batchなど他のサービスの利用を検討してください。

A: Lambda関数が強制終了される時点での処理状態に依存します。データベースへの書き込みが完了していればそのまま保持され、DynamoDBのトランザクション中であれば自動的にロールバックされます。分散トランザクション対応が必要な場合は、冪等性(同じ処理を複数回実行しても結果が変わらない特性)の設計が重要です。

A: Step Functionsのワークフロー実行は1年間の有効期限がありますが、事実上は無制限です。各Lambda呼び出しは15分の制限を受けますが、ステップ間の待機時間は制限されません。ただし、Step Functionsの状態遷移数が多い場合、コストが増加することに注意してください。

まとめ

  • AWS Lambdaのタイムアウトは最大15分に制限されており、超過すると関数は強制終了される
  • 非同期処理(SNS/SQS)により、長時間の処理を別のLambda関数に委譲して対応できる
  • 大量データはバッチに分割し、複数回のLambda実行に分散させることでタイムアウトを回避できる
  • Step Functionsを使うことで、複数のLambda関数を組み合わせた長時間のワークフロー構築が可能
  • Cold Start、外部API呼び出しなどのハマりポイントに注意し、実行時間を定期的に監視する必要がある

これらの対策を適切に組み合わせることで、AWS Lambda

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