DSQLの最大接続時間超過エラーが発生しないLambdaの実装について検証してみた

DSQLの最大接続時間超過エラーが発生しないLambdaの実装について検証してみた

2025.08.16

リテールアプリ共創部@大阪の岩田です。

Aurora DSQLにはいくつか固有の制約が存在しますが、その中の1つに最大接続時間が60分という制約が存在します。今回のブログではDSQLに接続するLambdaを5分毎に定期実行して、この制約について確認してみました。

環境

今回検証に利用した環境です。

  • Lambda
    • ランタイム: Node.js22x
    • アーキテクチャ: x86
    • リージョン: バージニア(us-east-1)
    • メモリ割り当て: 128M
  • ライブラリ等
    • pg: 8.16.3
    • @aws-sdk/dsql-signer: 3.864.0

やってみる

それではさっそく検証用のLambdaを実装していきましょう。

pgのClientを使う場合

まずはpgClientを使う場合の実装例です。

dsql-with-pg-client.mjs
import { Client } from "pg";
import { DsqlSigner } from "@aws-sdk/dsql-signer";

const region = process.env.AWS_REGION;
const clusterEndpoint = `${process.env.DSQL_CLUSTER_IDENTIFIER}.dsql.${region}.on.aws`;
const signer = new DsqlSigner({
	hostname: clusterEndpoint,
	region,
});
const token = await signer.getDbConnectAdminAuthToken();
const client = new Client({
	host: clusterEndpoint,
	port: 5432,
	database: "postgres",
	user: "admin",
	password: token,
	ssl: true,
});
await client.connect();
client.password;

export const handler = async (event, context) => {
	await client.query("SELECT now()");
};

handler外でDSQLとの接続を確立したまま接続を維持し続け、handler内の処理でSELECT now()を実行しています。このLambdaをデプロイし、Event Bridgeスケジューラーから5分毎に実行してみます。しばらく待ってからログを確認すると、以下のようなログが出力されていました。

2025-08-16T05:08:03.735Z	668be42f-8fb4-40b9-b922-917b4961fd6a	ERROR	Uncaught Exception 	
{
    "errorType": "Error",
    "errorMessage": "Connection terminated unexpectedly",
    "stack": [
        "Error: Connection terminated unexpectedly",
        "    at Connection.<anonymous> (/var/task/node_modules/pg/lib/client.js:136:73)",
        "    at Object.onceWrapper (node:events:632:28)",
        "    at Connection.emit (node:events:518:28)",
        "    at Socket.<anonymous> (/var/task/node_modules/pg/lib/connection.js:62:12)",
        "    at Socket.emit (node:events:530:35)",
        "    at TCP.<anonymous> (node:net:351:12)",
        "    at TCP.callbackTrampoline (node:internal/async_hooks:130:17)"
    ]
}
[ERROR] [1755320883795] LAMBDA_RUNTIME Failed to post handler success response. Http response code: 400.
END RequestId: 3ae7d2b9-386f-4cf5-8be4-8a07c441bb17
REPORT RequestId: 3ae7d2b9-386f-4cf5-8be4-8a07c441bb17	Duration: 424.60 ms	Billed Duration: 425 ms	Memory Size: 128 MB	Max Memory Used: 109 MB	Status: error	Error Type: Runtime.ExitError
XRAY TraceId: 1-68a01233-79a00cff59419f8a19543d5a	SegmentId: 19f811623978708f	Sampled: true

その後以下のように同一リクエストIDのログが出力されていました。これはEvent BridgeスケジューラーからLambdaの非同期実行に失敗したため、Lambdaのランタイムによって自動的な再試行が発生したためです。

AWS Application Signals enabled.
AWS Application Signals metrics export interval capped to 60000
Enabled batch unsampled span processor for Lambda environment.
OTEL_LOGS_EXPORTER is empty. Using default otlp exporter.
OTEL_METRICS_EXPORTER contains "none". Metric provider will not be initialized.
Setting TraceProvider for instrumentations at the end of initialization
AWS Distro of OpenTelemetry automatic instrumentation started successfully
(node:2) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
START RequestId: 3ae7d2b9-386f-4cf5-8be4-8a07c441bb17 Version: $LATEST
END RequestId: 3ae7d2b9-386f-4cf5-8be4-8a07c441bb17
REPORT RequestId: 3ae7d2b9-386f-4cf5-8be4-8a07c441bb17 Duration: 1115.67 ms Billed Duration: 1116 ms Memory Size: 128 MB Max Memory Used: 112 MB XRAY TraceId: 1-68a01233-79a00cff59419f8a19543d5a SegmentId: 4f516b95af47814f Sampled: true

対象トレースIDに紐づくトレース結果は以下の通りです。エラーの発生とLambdaランタイムによる再試行が分かりやすく表示されていますね。

最大接続時間超過エラー発生時のトレース結果.jpg

ということで、最大接続時間のことを考慮せずにDSQLとの接続を維持し続けると最大接続時間超過エラーを引き起こすことが分かりました。これはDSQLを使う上での要注意事項ですね。

pgのPoolを使う場合

最大接続時間超過エラーについて確認できたので、今度はエラーが発生しないように実装を修正してみましょう。handler内で毎回DSQLに接続/切断すれば最大接続時間超過エラーは発生しませんが、無駄な処理が多くなりパフォーマンスを最適化できないため、このアプローチは取らないこととします。今回はpgPoolを利用し、一定時間を経過した接続をコネクションプールから自動で破棄することでDSQLとの接続を効率良く利用できるようにしてみます。

実装は以下の通りです。

dsql-with-pg-pool.mjs
import { Pool } from "pg";
import { DsqlSigner } from "@aws-sdk/dsql-signer";

const region = process.env.AWS_REGION;
const clusterEndpoint = `${process.env.DSQL_CLUSTER_IDENTIFIER}.dsql.${region}.on.aws`;
const getToken = async () => {
    const signer = new DsqlSigner({
        hostname: clusterEndpoint,
        region,
    });
    return signer.getDbConnectAdminAuthToken()
}

const pool = new Pool({
	host: clusterEndpoint,
	port: 5432,
	database: "postgres",
	user: "admin",
	password: getToken,
	ssl: true,
	maxLifetimeSeconds: 60 * 50,
	idleTimeoutMillis: 1000 * 60 * 50
});

export const handler = async (event, context) => {
	await pool.query("SELECT now()");
};

ポイントは以下の点です。

  • Poolを生成する時のオプションでmaxLifetimeSeconds60 * 50を指定しています。この指定によって、コネクションプール内のDSQLとの接続は最大で50分までしか生存しないように管理され最大接続時間超過のエラーが未然に防止できます。
  • idleTimeoutMillisにも 1000 * 60 * 50を指定しており、最大50分まではアイドル状態のDSQLとの接続を破棄せずコネクションプール内に留めます。これによってLambdaの実行頻度が低い場合でもOPEN済のDSQLとの接続を再利用しやすくなります。 ※idleTimeoutMillisのデフォルト値は10秒で、Lambdaの実行間隔が10秒以上空いた場合はDSQLとの接続を再OPENするのがデフォルトの挙動となります。
  • DSQLに接続するための認証トークンはデフォルトで有効期間が15分に設定されているので、Pool生成時のオプションpasswordには認証トークンそのものではなく、認証トークンを取得するためのgetTokenという関数を渡すようにしています。これで新たにDSQLとの接続をするたびに新しい認証トークンを取得してパスワードとして利用してくれるようになります。

こちらのLambdaもデプロイしてEvent Bridgeスケジューラーから5分毎に実行、ログやトレースを確認してみます。まずデプロイ直後のコールドスタート時のトレース結果です。コールドスタート時はコネクションプール内にDSQLとの接続が存在しないので、pg.connectでDSQLと接続していることが分かります。

コネクションプールを使う実装でコールドスタートした時のトレース結果.jpg

その後ウォームスタート時は以下のようにpg-pool.connectでコネクションプールからDSQLとの接続を取得しており、pg.connectが呼び出されていないことが分かります。

コネクションプールを使う実装でウォームスタートスタート時のトレース結果.jpg

コールドスタートから50分以上経過後のウォームスタートのトレース結果です。50分以上経過したため、コールドスタート時に確立したDSQLとの接続はコネクションプールから破棄されており、再度pg.connectが呼び出されていることが分かります。

コネクションプールを使う実装でウォームスタートスタートかつDSQLに再接続した時のトレース結果.jpg

Transaction Searchで以下のクエリを実行し該当時間帯のログを確認してみます。

filter ispresent(attributes.db.name)
| display @timestamp, name
| sort startTimeUnixNano

Transaction Searchの検索結果.jpg

こちらのログからも50分以上経過後にpg.connectが再実行されていることが分かります。

コネクションプールを使う実装時のトレースマップ.jpg

トレースマップを見ても特にDSQLとの接続でエラーは発生していません。同様にLambdaのログからも Connection terminated unexpectedlyのエラーは確認できませんでした。

SAMテンプレート

今回の検証に使ったSAMテンプレートです。参考までに載せておきます。

AWSTemplateFormatVersion: 2010-09-09
Transform:
- AWS::Serverless-2016-10-31
Globals:
  Function:
    Timeout: 10
    Tracing: Active
    Runtime: nodejs22.x
    Layers:
      - arn:aws:lambda:us-east-1:615299751070:layer:AWSOpenTelemetryDistroJs:8
    Environment:
      Variables:
        OTEL_NODE_ENABLED_INSTRUMENTATIONS: pg
        AWS_LAMBDA_EXEC_WRAPPER: /opt/otel-instrument
        DSQL_CLUSTER_IDENTIFIER: !Ref Dsql
Resources:  
  Dsql:
    Type: AWS::DSQL::Cluster
    Properties:
      DeletionProtectionEnabled: false
  DsqlWithPgClient:
    Type: AWS::Serverless::Function
    Properties:      
      Handler: src/dsql-with-pg-client.handler
      Role: !GetAtt LambdaRole.Arn
      Events:
        CloudWatchEvent:
          Type: Schedule
          Properties:
            Schedule: rate(5 minutes)
  DsqlWithPgPool:
    Type: AWS::Serverless::Function
    Properties:      
      Handler: src/dsql-with-pg-pool.handler
      Role: !GetAtt LambdaRole.Arn
      Events:
        CloudWatchEvent:
          Type: Schedule
          Properties:
            Schedule: rate(5 minutes)
  LambdaRole:
      Type: AWS::IAM::Role
      Properties:
        AssumeRolePolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Principal:
              Service:
              - 'lambda.amazonaws.com'
            Action: sts:AssumeRole
        Policies:
        -
          PolicyName: dsql-blog-policy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
            -
              Effect: "Allow"
              Action:
                - 'dsql:*'
                - "logs:*"
                - "xray:*"
              Resource: '*'

まとめ

DSQLの最大接続時間について検証してみました。最大接続時間を超過しないように自前で実装するのは面倒なので、各種ライブラリが提供してくれるコネクションプールの機構をうまく活用して実装を省力化していきたいですね。

今回はシンプルなSELECT文を発行するだけの処理で検証しましたが、トランザクションを実行する場合はまた接続管理の考慮点が増えそうなので今後改めて検証していきたいと思います。

参考

この記事をシェアする

facebookのロゴhatenaのロゴtwitterのロゴ

© Classmethod, Inc. All rights reserved.