"I connected normally from Lambda to PostgreSQL, and the moment access increased, the DB fell over with too many connections" — this is the wall you always hit first with the combination of serverless and a traditional relational DB. Lambda scales to hundreds or thousands of execution environments per second, but RDS/Aurora's concurrent-connection count is finite. Unless you absorb this collision of two worldviews in your design, production stops on connection exhaustion.
This article is an implementation guide for connecting from AWS Lambda to RDS / Aurora (PostgreSQL, MySQL) at production quality. It explains end-to-end, from the root problem of connection exhaustion through RDS Proxy, Data API, IAM authentication, VPC/NAT cost, to Aurora Serverless v2. As a subject, it also weaves in connection-management knowledge gained while running a B2B SaaS (the lumber-industry DX) in production on PostgreSQL. The execution model of Lambda itself is left to the sister article AWS Lambda production-operations guide; this piece concentrates on the single point of "connecting to a relational DB."
Rules for this article: specs, parameter names, and limits are based on the AWS official documentation (as of June 2026). Pricing (RDS Proxy, NAT, etc.) changes by region and period, so this piece doesn't assert specific amounts but shows the billing model. Always confirm the latest values/pricing in the official docs (see "References" at the end) before production.
0. Mental model: Lambda runs on the premise that it "doesn't share connections"
First, pin down on one page why this is hard.
- Concurrent execution = separate execution environments = separate DB connections. Lambda assigns an independent execution environment per concurrent request. Each opens its own dedicated DB connection.
- Lambda scales to thousands, RDS's
max_connectionsis finite. For example, if 1,000 concurrent executions each open 1 connection, the DB is asked for 1,000 connections. This is connection exhaustion. - Reusing a connection outside the handler goes only as far as "one connection per warm environment." This helps reduce latency but doesn't solve the concurrency fan-out itself (with 1,000 environments, connections reach up to 1,000).
- So in production, choose "a layer that shares connections (RDS Proxy)" or "a method that holds no connections (Data API)." That's the two-way choice of this article.
The official docs state it too — "direct connection is useful for simple cases; in production a proxy is recommended. A database proxy manages a shared pool of connections, letting functions reach high concurrency without exhausting DB connections."
1. First, the foundation: connect outside the handler and prepare for idle disconnection
Before advancing to RDS Proxy or Data API, grasp the basics that work in any method. Open the connection once, outside the handler, and reuse it on warm invocations (the official best practice).
// プールはハンドラ外(INIT)で生成。ウォーム環境内で再利用される。
import { Pool } from "pg";
// 1環境あたり小さなプール。max=1〜数。大きくしても同時実行のファンアウトは別問題
const pool = new Pool({
host: process.env.DB_HOST,
max: 1, // Lambdaは並列処理しない(1呼び出し1処理)ので小さく
idleTimeoutMillis: 0, // Lambda側で勝手に切らない(下のkeep-aliveと併用)
keepAlive: true, // アイドル接続の切断対策
});
export const handler = async (event: { id: string }) => {
// pool.query は同一環境で接続を使い回す(再接続コストを払わない)
const { rows } = await pool.query("SELECT id, total FROM orders WHERE id = $1", [event.id]);
return rows[0] ?? null;
};
Official caution: Lambda discards idle connections over time. Trying to reuse a discarded connection causes a connection error, so enable keep-alive and design so you can check liveness before a query. But to repeat, this is "in-environment reuse," and as concurrency grows the connection count grows — which is why you need the pool layer in the next chapter.
2. The main option: pool and share connections with Amazon RDS Proxy
RDS Proxy is a fully managed connection pool that sits between the Lambda fleet and the DB. It multiplexes many client connections onto a small number of DB connections, preventing DB connection exhaustion. It also preserves connections during failover to raise resilience.
2.1 What's good about it
- Pooling/sharing connections: control the upper bound of DB-side connections with
MaxConnectionsPercent(a ratio against the DB'smax_connections). No matter how many thousands Lambda scales to, connections to the DB stay within the pool's range. - IAM auth + Secrets Manager: the client (Lambda) authenticates with IAM, and the proxy connects to the DB with Secrets Manager credentials or IAM DB auth. No DB password in code.
- Supported engines: Aurora (MySQL/PostgreSQL), RDS for PostgreSQL / MySQL / MariaDB / SQL Server.
// RDS Proxy 経由:接続先をプロキシのエンドポイントにするだけ。IAM認証トークンでパスワードレス
import { Signer } from "@aws-sdk/rds-signer";
import { Pool } from "pg";
const signer = new Signer({
hostname: process.env.PROXY_HOST!, // RDS Proxy のエンドポイント
port: 5432,
username: process.env.DB_USER!,
region: process.env.AWS_REGION!,
});
// 認証トークンは15分で失効するため、接続のたびに発行する(password を関数化)
const pool = new Pool({
host: process.env.PROXY_HOST,
user: process.env.DB_USER,
password: () => signer.getAuthToken(), // ← パスワードの代わりにIAMトークン
ssl: { rejectUnauthorized: true },
max: 1,
});
export const handler = async () => (await pool.query("SELECT now()")).rows[0];
2.2 RDS Proxy's nemesis: pinning
This is the point that trips people up most in production. The proxy multiplexes connections, but when it detects an operation that depends on session state, it pins that client connection to a specific DB connection. A pinned connection can no longer be reused by other clients, and the benefit of multiplexing disappears.
The main pinning triggers the official docs list:
| Common to engines | PostgreSQL example | MySQL example |
|---|---|---|
| SQL statement over 16KB | SET (variable setting), PREPARE/DEALLOCATE/EXECUTE, creating temporary tables/sequences/views, declaring cursors, LISTEN, session-level advisory lock (pg_advisory_lock) | table lock, GET_LOCK, setting user variables, creating temporary tables, prepared statements |
Guidelines for avoidance (official):
- Avoid unnecessary session-state changes. If you do the same initialization on every connection, consolidate it into the proxy's initialization query (don't
SETin each query). - PostgreSQL advisory locks pin at the session level (
pg_advisory_lock) but not at the transaction level (pg_advisory_xact_lock). If you use locks for idempotency/exclusion control, choose the transaction level. - Monitor the
DatabaseConnectionsCurrentlySessionPinnedmetric. If pinning is high, eliminate the triggers above.
Billing model: RDS Proxy is billed per vCPU-hour on provisioned DBs (per ACU-hour on Aurora Serverless), with a minimum of 10 minutes of billing after a billing-state change. Specific amounts vary by region/period, so confirm on the official pricing page. As a rule of thumb, the more a Lambda workload "frequently opens/closes connections even with a bit of constant traffic," the higher Proxy's cost-effectiveness.
3. Another solution: hold no connections with the RDS Data API
An option that eliminates connection management itself is the RDS Data API. It's a method to send SQL with the SDK to an HTTP endpoint, requiring no persistent connection. A big advantage is that you can access the DB without putting Lambda in a VPC (freed from designing proxies or ENIs).
// Data API:接続プールもVPCも不要。SDKでSQLをHTTP実行。認証はIAM+Secrets Manager
import { RDSDataClient, ExecuteStatementCommand } from "@aws-sdk/client-rds-data";
const rds = new RDSDataClient({}); // 永続接続なし。ハンドラ外で1度だけ生成
export const handler = async (event: { id: string }) => {
const res = await rds.send(new ExecuteStatementCommand({
resourceArn: process.env.CLUSTER_ARN, // Auroraクラスタ
secretArn: process.env.SECRET_ARN, // DB資格情報(Secrets Manager)。コードに持たない
database: "app",
sql: "SELECT id, total FROM orders WHERE id = :id",
parameters: [{ name: "id", value: { stringValue: event.id } }], // パラメータ化でSQLi対策
}));
return res.records ?? [];
};
Official spec and limits:
- Support: Aurora PostgreSQL (Serverless v2 + provisioned, 13.11/14.8/15.3/16.1/17.4 and later), Aurora MySQL (3.07 and later). Executable only on the writer instance; T-class is not supported.
- Authentication: IAM + Secrets Manager. Don't pass credentials in the call (Secrets Manager is the source of truth).
- Limits: the response is capped at 1 MiB (truncated beyond that). Unsuited to fetching many rows. There's also latency overhead, so it suits short transactions and small results.
3.1 RDS Proxy or Data API
| Aspect | RDS Proxy | Data API |
|---|---|---|
| Connection management | Shares a pool (connections exist) | No connection (HTTP) |
| VPC | Required (same VPC) | Not required |
| Large results/streaming | Good at it | Unsuited (1 MiB cap) |
| Existing driver/ORM | Use as-is | Needs the SDK/dedicated driver |
| Suited workload | General OLTP, existing assets | Lightweight queries, want to avoid the VPC, minimal setup |
When in doubt: if you have existing ORM/driver assets and handle sizable results, RDS Proxy. If greenfield, lightweight, and you want to avoid VPC complexity, Data API (Aurora only).
4. VPC and networking: the "connects but can't get out" trap and the cost
To connect from Lambda to RDS, place Lambda in the same VPC as RDS (for Proxy/direct connection). Two pitfalls here.
- Putting it in a VPC means, by default, it can no longer reach the internet. The official docs state plainly: "once attached to a VPC, it can only access resources within that VPC" and "placing it in a public subnet does not grant internet access." To reach Secrets Manager's public endpoint or external APIs, you need a NAT gateway.
- NAT is billed hourly + per data processing (GB). With constant operation it adds up steadily. You can avoid NAT for AWS services like Secrets Manager with a VPC interface endpoint (PrivateLink) — advantageous in latency, cost, and security all at once.
[Lambda(私有サブネット)] --(同一VPC)--> [RDS Proxy] --> [Aurora]
|
+--(PrivateLink/VPCエンドポイント)--> [Secrets Manager] ← NATを使わない
|
+--(NATゲートウェイ:時間+GB課金)----> [外部API/パブリックEP] ← 必要な時だけ
Hyperplane ENIs (the network foundation of VPC functions) are shared/reused per subnet + SG combination and each handle 65,000 connections. The reason VPC cold starts are "fast now" and the design details are separated into the VPC chapter of the cold-start optimization guide.
4.1 Authentication: the choice of IAM DB authentication
For DB credential management, besides Secrets Manager there is IAM database authentication. It's password-free, connecting with a 15-minute-valid auth token generated with SigV4. Supports MySQL/MariaDB/PostgreSQL. However, the official recommendation is "use it at fewer than 200 new connections per second; beyond that, use connection pooling/RDS Proxy." For high-frequency new connections, leaning on RDS Proxy is the correct answer.
5. Which DB to choose: the fit of Aurora Serverless v2
Against "spiky serverless Lambda," a DB that automatically expands/contracts capacity meshes well. Aurora Serverless v2 auto-scales by the second in ACU units (≈2 GiB memory) and can throttle down to a minimum of 0 ACU (auto-pause).
- Scale-to-zero (since 2024): setting the minimum ACU to
0pauses and makes capacity billing 0 while there are no user connections, and auto-resumes when a connection arrives (typically 15 seconds; idle timeout from 5 minutes to 1 day). Intermittent Lambda load and cost-efficiency mesh. - An important incompatibility: associating RDS Proxy keeps the proxy maintaining connections, so the instance won't auto-pause. "Prevent connection exhaustion with Proxy" or "erase paused-time cost with scale-to-zero" is a trade-off you choose by workload.
There are also many situations where NoSQL (DynamoDB) meshes more naturally with serverless. For selection including whether you really need relational (joins, transactions, existing schema), see the DynamoDB vs RDS/Aurora selection guide, and for the general theory of connection pooling, serverless connection pooling with PgBouncer.
6. Conclusion: Lambda × relational DB cheat sheet
- Root problem: a separate connection per concurrent execution. Lambda scales to thousands, RDS's
max_connectionsis finite → connection exhaustion. - Basics: open the connection/pool once, outside the handler, and prepare for idle disconnection with keep-alive. But this doesn't solve the fan-out.
- Main option = RDS Proxy: pool/share connections, password-less with IAM auth + Secrets Manager. The nemesis is pinning — avoid
SET, temporary tables, and session-level advisory locks, move initialization to the proxy's init query, and monitor…SessionPinned. - Hold no connections = Data API: SQL over HTTP, no VPC needed, Aurora only, response capped at 1 MiB. For lightweight queries.
- VPC cost: a Lambda in a private subnet needs a NAT (hourly + GB) to get out. AWS services avoid NAT with PrivateLink. For authentication, Secrets Manager/RDS Proxy if high-frequency, IAM DB auth if low-frequency.
- DB selection: Aurora Serverless v2 scales to zero, a good fit for spiky load. But it won't auto-pause when used with RDS Proxy.
While running a B2B SaaS in production on PostgreSQL, I've faced the constraint of "Lambda/container concurrency × finite DB connections" many times. Absorbing connections in a pool layer, excluding secrets from code, and cutting NAT cost with endpoints — this design discipline is the foundation for operating a DB without knocking it down, safely, and cheaply.
"Our serverless stalls/falls over on DB connections, and the cost is unpredictable" — from resolving connection exhaustion through RDS Proxy/Data API selection to VPC cost optimization, I accompany you at the speed of one person × generative AI (Claude Code). Feel free to consult me even starting from a diagnosis of your current connection design.
References (official documentation)
- Using Amazon RDS with Lambda — direct connection vs. proxy, RDS Proxy recommended
- Amazon RDS Proxy — connection pool, IAM auth, supported engines
- Avoiding pinning (RDS Proxy) — pinning triggers and avoidance,
DatabaseConnectionsCurrentlySessionPinned - RDS Proxy connections / MaxConnectionsPercent — upper-bound control against
max_connections - Using RDS Data API / limitations — no connection needed, IAM + Secrets Manager, 1 MiB cap
- IAM database authentication — 15-minute token, 200 connections/sec rule of thumb
- Lambda networking / VPC — non-internet by default inside a VPC, the need for NAT
- Secrets Manager VPC endpoints (PrivateLink) — avoiding NAT
- Aurora Serverless v2 auto-pause / scale to zero — minimum 0 ACU, no pause when used with RDS Proxy
- Amazon RDS Proxy pricing — billing model per vCPU-hour/ACU-hour