# Connecting from Lambda to RDS/Aurora: RDS Proxy, Data API, VPC design to prevent connection exhaustion, and cost optimization

> An implementation guide for connecting from AWS Lambda to RDS/Aurora (PostgreSQL/MySQL) at production quality. It explains, in real code faithful to the AWS official spec: the root problem of connection exhaustion from concurrency fan-out, RDS Proxy's connection pool and avoiding pinning, the connection-free RDS Data API, IAM database authentication, VPC/NAT cost and PrivateLink, and Aurora Serverless v2's scale-to-zero.

- Published: 2026-06-28
- Author: 友田 陽大
- Tags: AWS, Lambda, RDS, Aurora, サーバーレス
- URL: https://tomodahinata.com/en/blog/aws-lambda-rds-aurora-connection-management-rds-proxy-vpc-guide
- Category: AWS Lambda in production
- Pillar guide: https://tomodahinata.com/en/blog/aws-lambda-production-guide

## Key points

- The root problem of Lambda × RDB is connection exhaustion: each concurrent execution opens a separate connection from a separate execution environment, and thousands of concurrent executions eat up RDS's finite max_connections.
- Reuse by opening a connection outside the handler goes only as far as 'one connection per warm environment' and doesn't solve the fan-out. In production, pool/share connections with RDS Proxy.
- RDS Proxy's nemesis is pinning. SET, temporary tables, session-level advisory locks, etc., fix the client to a DB connection so multiplexing stops working.
- To eliminate connection management itself, the RDS Data API: execute SQL over HTTP, no persistent connection or VPC needed. Available on Aurora PostgreSQL/MySQL Serverless v2 + provisioned.
- Cost: a VPC Lambda needs a NAT (hourly + per-GB) to reach outside. Secrets Manager avoids NAT with PrivateLink. Aurora Serverless v2 can auto-pause down to a minimum of 0 ACU.

---

"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](/case-studies/lumber-industry-dx)) in production on PostgreSQL. The execution model of Lambda itself is left to the sister article [AWS Lambda production-operations guide](/blog/aws-lambda-production-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_connections` is 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).

```ts
// プールはハンドラ外（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's `max_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.

```ts
// 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 `SET` in 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 `DatabaseConnectionsCurrentlySessionPinned` metric.** 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](https://aws.amazon.com/rds/proxy/pricing/). 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).

```ts
// 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.

```text
[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](/blog/aws-lambda-cold-start-snapstart-provisioned-concurrency-performance-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 `0` **pauses 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](/blog/dynamodb-vs-rds-aurora-postgresql-when-to-use-nosql-decision-guide), and for the general theory of connection pooling, [serverless connection pooling with PgBouncer](/blog/postgresql-connection-pooling-pgbouncer-serverless-guide).

---

## 6. Conclusion: Lambda × relational DB cheat sheet

- **Root problem**: a separate connection per concurrent execution. **Lambda scales to thousands, RDS's `max_connections` is 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](https://docs.aws.amazon.com/lambda/latest/dg/services-rds.html) — direct connection vs. proxy, RDS Proxy recommended
- [Amazon RDS Proxy](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-proxy.html) — connection pool, IAM auth, supported engines
- [Avoiding pinning (RDS Proxy)](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-proxy-pinning.html) — pinning triggers and avoidance, `DatabaseConnectionsCurrentlySessionPinned`
- [RDS Proxy connections / MaxConnectionsPercent](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-proxy-connections.html) — upper-bound control against `max_connections`
- [Using RDS Data API](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html) / [limitations](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.limitations.html) — no connection needed, IAM + Secrets Manager, 1 MiB cap
- [IAM database authentication](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html) — 15-minute token, 200 connections/sec rule of thumb
- [Lambda networking / VPC](https://docs.aws.amazon.com/lambda/latest/dg/foundation-networking.html) — non-internet by default inside a VPC, the need for NAT
- [Secrets Manager VPC endpoints (PrivateLink)](https://docs.aws.amazon.com/secretsmanager/latest/userguide/vpc-endpoint-overview.html) — avoiding NAT
- [Aurora Serverless v2 auto-pause / scale to zero](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2-auto-pause.html) — minimum 0 ACU, no pause when used with RDS Proxy
- [Amazon RDS Proxy pricing](https://aws.amazon.com/rds/proxy/pricing/) — billing model per vCPU-hour/ACU-hour
