Amaozn API Gateway の Lambda プロキシ統合を使用している場合に、Lambda 関数のスロットリング時にクライアントにどのようなエラーレスポンスが返るのか検証してみた

Amaozn API Gateway の Lambda プロキシ統合を使用している場合に、Lambda 関数のスロットリング時にクライアントにどのようなエラーレスポンスが返るのか検証してみた

Clock Icon2025.05.14

こんにちは、製造ビジネステクノロジー部の若槻です。

AWS Lambda 関数には 同時実行数 という概念があり、同時に実行できる Lambda 関数の数の制限があります。この制限を超えて Lambda 関数が実行されると、スロットリングが発生し、Lambda 関数はエラーを返します。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-concurrency.html

今回は、Amaozn API Gateway の Lambda プロキシ統合を使用している場合に、Lambda 関数のスロットリング時にクライアントにどのようなエラーレスポンスが返るのか検証する機会があったので共有します。

やってみた

環境構築

Lambda ハンドラーのコード

Serverless Express を使用して実装した Lambda ハンドラーのコードです。Lambda プロキシ統合による Lambda-lith アプローチを実装する際によく使用されるパッケージです。

src/rest-api-router.ts
import serverlessExpress from "@codegenie/serverless-express";
import cors from "cors";
import express, { Request, Response } from "express";

const app = express();
app.use(cors());
app.use(express.json());

app.get("/companies", (_: Request, res: Response): void => {
  // 同時実行数上限を超過しやすいように2秒待機してからレスポンスを返す
  setTimeout(() => {
    res.status(200).send({ message: "Hello, world!" });
  }, 2000);
});

export const handler = serverlessExpress({ app });

AWS CDK コード

AWS CDK の LambdaRestApi コンストラクトを使用して Lambda プロキシ統合の REST API を作成します。

lib/main-stack.ts
import * as cdk from "aws-cdk-lib";
import * as apigateway from "aws-cdk-lib/aws-apigateway";
import * as lambda_nodejs from "aws-cdk-lib/aws-lambda-nodejs";
import { Construct } from "constructs";

export class MainStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: cdk.StackProps) {
    super(scope, id, props);

    /**
     * Lambda Function
     */
    const handler = new lambda_nodejs.NodejsFunction(this, "Handler", {
      entry: "src/rest-api-router.ts",
      reservedConcurrentExecutions: 1, // 同時実行数を1に制限してスロットリングを発生しやすくする
    });

    /**
     * API Gateway REST API
     */
    new apigateway.LambdaRestApi(this, "RestApi", {
      handler,
      deployOptions: {
        //実行ログの設定
        dataTraceEnabled: true,
        loggingLevel: apigateway.MethodLoggingLevel.INFO,
        // アクセスログの設定
        accessLogDestination: new apigateway.LogGroupLogDestination(
          new cdk.aws_logs.LogGroup(this, "AccessLogGroup", {
            logGroupName: "/aws/api/RestApi",
            removalPolicy: cdk.RemovalPolicy.DESTROY,
          })
        ),
      },
    });
  }
}

動作確認

REST API のエンドポイントに2回連続でリクエストを送信して、スロットリングを発生させます。

1回目のリクエストは正常にレスポンスを返し、2回目のリクエストは 500 エラーを返しました。

# 1回目のリクエスト
$ curl -s -w "\nステータスコード: %{http_code}\n" https://w0m2k255f1.execute-api.ap-northeast-1.amazonaws.com/prod/companies
{"message":"Hello, world!"}
ステータスコード: 200

# 2回目のリクエスト。1回目のリクエスト開始の直後に実行
$ curl -s -w "\nステータスコード: %{http_code}\n" https://w0m2k255f1.execute-api.ap-northeast-1.amazonaws.com/prod/companies
{"message": "Internal server error"}
ステータスコード: 500

Lambda でスロットリングが発生した場合は下記ドキュメントで言うところの「Lambda API が呼び出しリクエストを拒否した場合」に該当するようです。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/services-apigateway-errors.html

関数の Throttles CloudWatch メトリクスを確認しても、スロットルが発生していることがわかります。

API Gateway の実行ログを確認すると、1回目のリクエストは正常にレスポンスを返しているのに対し、2回目のリクエストは 500 エラーを返していることがわかります。

# 1回目のリクエスト
XXX.XXX.XXX.XXX - - [13/May/2025:14:08:42 +0000] "GET /{proxy+} HTTP/1.1" 200 27 fec49cae-b462-4c53-947a-ee0ad2de5966

# 2回目のリクエスト
XXX.XXX.XXX.XXX - - [13/May/2025:14:08:42 +0000] "GET /{proxy+} HTTP/1.1" 500 36 be1bad04-3f12-4185-b90f-d4f84c5d029a

一方で API Gateway のアクセスログを確認すると、1回目のリクエストは正常にレスポンスを返しているのに対し、2回目のリクエストは 429 エラーを返していることがわかります。

# 1回目のリクエスト
(47d9fbd0-426d-4760-b40d-2c75a922d0ee) Received response. Status: 200, Integration latency: 2165 ms
{
    "statusCode": 200,
    "body": "{\"message\":\"Hello, world!\"}",
    "multiValueHeaders": {
        "x-powered-by": [
            "Express"
        ],
        "access-control-allow-origin": [
            "*"
        ],
        "content-type": [
            "application/json; charset=utf-8"
        ],
        "content-length": [
            "27"
        ],
        "etag": [
            "W/\"1b-tDnArHZ232y12bSoTiMc+/cz4as\""
        ]
    },
    "isBase64Encoded": false
}

# 2回目のリクエスト
(db85798a-4d3d-4050-99c3-d7b0d9e03a99) Received response. Status: 429, Integration latency: 10 ms
(db85798a-4d3d-4050-99c3-d7b0d9e03a99) Endpoint response body before transformations:
{
    "Reason": "ReservedFunctionConcurrentInvocationLimitExceeded",
    "Type": "User",
    "message": "Rate Exceeded."
}

これは Lambda 関数はスロットリング時に 429 エラーを返すのに対し、
https://repost.aws/ja/knowledge-center/lambda-troubleshoot-throttling

それを受け取った API Gateway は 500 エラーに変換してクライアントには返しているためです。これは API Gateway のデフォルトの挙動で、バックエンドの詳細なエラー(429)をクライアントに直接公開せず、一般的な 500 エラーに置き換えるための仕様のようですね。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/services-apigateway-errors.html

このことから、それぞれのログで記録される情報には以下のような違いがあるようです。デバッグの際には意識するようにしましょう。

  • アクセスログ:統合先(Lambda など)からの生のレスポンス情報を記録
  • 実行ログ:クライアントに返された最終的なレスポンス情報を記録

おわりに

Amaozn API Gateway の Lambda プロキシ統合を使用している場合に、Lambda 関数のスロットリング時にクライアントにどのようなエラーレスポンスが返るのか検証する機会があったので共有しました。

どなたかの参考になれば幸いです。

参考

https://dev.classmethod.jp/articles/enable-logging-for-api-gateway-and-peeking-at-the-logs-in-cloudwatch-logs/

以上

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.