DSQLの最大接続時間超過エラーが発生しないLambdaの実装について検証してみた
リテールアプリ共創部@大阪の岩田です。
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を使う場合
まずはpg
のClient
を使う場合の実装例です。
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ランタイムによる再試行が分かりやすく表示されていますね。
ということで、最大接続時間のことを考慮せずにDSQLとの接続を維持し続けると最大接続時間超過エラーを引き起こすことが分かりました。これはDSQLを使う上での要注意事項ですね。
pgのPoolを使う場合
最大接続時間超過エラーについて確認できたので、今度はエラーが発生しないように実装を修正してみましょう。handler内で毎回DSQLに接続/切断すれば最大接続時間超過エラーは発生しませんが、無駄な処理が多くなりパフォーマンスを最適化できないため、このアプローチは取らないこととします。今回はpg
のPool
を利用し、一定時間を経過した接続をコネクションプールから自動で破棄することでDSQLとの接続を効率良く利用できるようにしてみます。
実装は以下の通りです。
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
を生成する時のオプションでmaxLifetimeSeconds
に60 * 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と接続していることが分かります。
その後ウォームスタート時は以下のようにpg-pool.connect
でコネクションプールからDSQLとの接続を取得しており、pg.connect
が呼び出されていないことが分かります。
コールドスタートから50分以上経過後のウォームスタートのトレース結果です。50分以上経過したため、コールドスタート時に確立したDSQLとの接続はコネクションプールから破棄されており、再度pg.connect
が呼び出されていることが分かります。
Transaction Searchで以下のクエリを実行し該当時間帯のログを確認してみます。
filter ispresent(attributes.db.name)
| display @timestamp, name
| sort startTimeUnixNano
こちらのログからも50分以上経過後にpg.connectが再実行されていることが分かります。
トレースマップを見ても特に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文を発行するだけの処理で検証しましたが、トランザクションを実行する場合はまた接続管理の考慮点が増えそうなので今後改めて検証していきたいと思います。