serverless aws databases claude-curated
Putting AWS Lambda in front of a relational database (RDS, Aurora) is common but mismatched at the connection layer. Lambda scales horizontally without warning; relational databases have hard connection limits. The combination needs deliberate handling.
The connection-explosion problem
Each Lambda execution environment (container) holds its own database connection — there is no shared pool across containers. If your function runs at 1000 concurrent invocations and each opens one connection, you’ve asked the database for 1000 connections.
A typical PostgreSQL db.t3.medium allows ~85 connections by default. A db.r5.large allows a few hundred. Burst traffic exhausts the pool, new Lambdas hang on connect, downstream timeouts cascade. Worse: every cold start that fails to connect still bills you for the duration.
RDS Proxy
Amazon RDS Proxy is a managed connection pooler that sits between Lambda and the database. It maintains a warm pool of database connections and multiplexes Lambda requests across them.
Benefits:
- Lambda concurrency decoupled from DB connection count.
- Connection reuse across cold starts (Lambda connects to the proxy, not the DB).
- IAM authentication support.
- Failover times improved (proxy holds connections through DB failover).
Cost: per-vCPU-hour of the underlying DB instance. Usually worth it for any non-trivial Lambda + RDS workload.
VPC-attached Lambda
Lambda can attach to a VPC to reach private RDS. Things to know:
- ENI cold start — historically attaching a Lambda to a VPC added ~10 s to cold start while an Elastic Network Interface was provisioned. Since the 2019 HyperPlane rework, ENIs are pre-attached at function configuration time and shared across invocations. Cold-start penalty is now essentially negligible.
- No public internet by default — a VPC-attached Lambda loses the public NAT it had outside the VPC. To reach external APIs (Stripe, AWS public endpoints) you need a NAT Gateway in a public subnet plus a route, or VPC endpoints for AWS services.
- NAT egress cost — NAT Gateway charges per GB processed. Chatty Lambdas calling external APIs through NAT can rack up surprisingly large bills.
- DNS — the function uses VPC-resolved DNS. Make sure
enableDnsHostnamesandenableDnsSupportare on for the VPC.
Connection lifecycle in handler code
Two competing patterns:
Open in init, reuse across warm invocations
# module scope — runs in init phase
conn = psycopg2.connect(...)
def handler(event, context):
with conn.cursor() as cur:
...Pro: no per-invocation connect cost on warm calls. Con: idle connections accumulate; the DB sees them as active until the container is destroyed.
Open per invocation
def handler(event, context):
conn = psycopg2.connect(...)
try:
...
finally:
conn.close()Pro: clean lifecycle. Con: every invocation pays connect latency (~50–200 ms for Postgres TLS).
The right answer is usually reuse + RDS Proxy — get the warm-path speed-up without breaking the database.
IAM authentication
Instead of putting a username/password in environment variables (or even Secrets Manager), use IAM database authentication. The function’s execution role generates a short-lived auth token (15 min) and passes it as the password. Benefits:
- No long-lived credentials at rest.
- Centralised access control via IAM.
- Audit trail through CloudTrail.
Costs: tokens have a rate limit (~200 new connections per second per DB instance for IAM auth). Combine with RDS Proxy to amortise.