Guide · AWS Aurora · Serverless Database
Aurora Serverless v2 for MCP Servers — Continuous Scaling, ACU Configuration, RDS Proxy
Aurora Serverless v2 replaces the step-function scaling model of v1 with continuous fractional scaling from 0.5 to 128 Aurora Capacity Units (ACUs) — MCP server teams use it for backends that need automatic burst headroom without provisioning peak-capacity instances full-time. Three production subtleties matter most: the minimum ACU setting cannot be zero in most configurations — unlike Aurora Serverless v1 which could pause completely, v2 requires a minimum of 0.5 ACUs and keeps the database running at all times, so you pay a small baseline even at idle; the Data API is not available on Aurora Serverless v2 (it was supported on v1), which breaks any Lambda functions that connected to v1 via HTTP rather than a TCP connection pool — those callers must switch to RDS Proxy or a native driver; and adding RDS Proxy in front of Serverless v2 is not optional for Lambda-heavy workloads — when hundreds of concurrent Lambda executions each open a native Aurora connection the database's max_connections limit is hit regardless of how many ACUs it has, because the connection limit scales with ACUs but cold-start Lambda bursts can saturate it before the scale-up completes.
TL;DR
Create an Aurora cluster with engineMode: "provisioned" and set serverlessV2ScalingConfiguration to minCapacity: 0.5, maxCapacity: 128. Use DBInstanceClass: "db.serverless" for the instance. Add RDS Proxy if Lambda functions connect to the cluster. Aurora Serverless v2 scales continuously without pausing — set minCapacity: 2 for production to pre-warm connections and avoid cold-scale delays on traffic spikes.
Aurora Serverless v2 vs v1 differences
v1 and v2 are architecturally distinct products despite the name similarity:
| Feature | Aurora Serverless v1 | Aurora Serverless v2 |
|---|---|---|
| Scaling model | Step-function (4 → 8 → 16 ACUs); brief pause during scale event | Continuous fractional scaling; no pause |
| Minimum capacity | 0 ACUs (can pause); 1 ACU non-pausing | 0.5 ACUs minimum; no true pause (0 ACU allowed on Aurora MySQL 3.04+/Aurora PostgreSQL 15.2+ only) |
| Data API | Supported — HTTP-based connections, no TCP pool needed | Not supported — must use native driver or RDS Proxy |
| Global database | Not supported | Supported as secondary replica capacity type |
| Multi-AZ | Single AZ (writer only) | Multi-AZ via additional reader instance |
| Engine version | Aurora MySQL 5.6/5.7; Aurora PostgreSQL 10/11 | Aurora MySQL 8.0 (3.x); Aurora PostgreSQL 13+ |
| Max capacity | 256 ACUs | 128 ACUs (writer); 128 ACUs (reader) |
Capacity configuration and scaling behavior
One ACU provides approximately 2 GB of memory and proportional CPU and network. The minCapacity and maxCapacity settings define the scaling range; Aurora scales the instance up or down within this range based on load signals — primarily CPU utilization, active connections, and buffer pool pressure.
# Create an Aurora Serverless v2 PostgreSQL cluster
aws rds create-db-cluster \
--db-cluster-identifier mcp-probe-db \
--engine aurora-postgresql \
--engine-version "16.2" \
--serverless-v2-scaling-configuration "MinCapacity=0.5,MaxCapacity=32" \
--db-subnet-group-name mcp-db-subnets \
--vpc-security-group-ids sg-0abc123456 \
--enable-iam-database-authentication \
--deletion-protection \
--region us-east-1
# Add the first instance with db.serverless class
aws rds create-db-instance \
--db-instance-identifier mcp-probe-db-writer \
--db-cluster-identifier mcp-probe-db \
--db-instance-class db.serverless \
--engine aurora-postgresql
# Scale-up is fast; scale-down is conservative (requires ~15 minutes of sustained low load)
# Monitor with CloudWatch metric: ServerlessDatabaseCapacity
Minimum capacity choice: Setting minCapacity: 0.5 (the floor) means cold traffic hitting the database after a quiet period may experience a 1-3 second delay as the instance scales from 0.5 to its working capacity. For production MCP backends that receive steady traffic, set minCapacity to 2 or 4 ACUs to pre-warm memory and avoid this scale-up latency on the first post-idle request. For dev/staging environments, 0.5 is appropriate.
Scale-down hysteresis: Aurora Serverless v2 will not scale down until the instance has been below the scale-down threshold for at least 15 consecutive minutes. This prevents oscillation during bursty but repeated traffic patterns common in probe-worker deployments.
CDK setup
import * as rds from "aws-cdk-lib/aws-rds";
import * as ec2 from "aws-cdk-lib/aws-ec2";
const cluster = new rds.DatabaseCluster(this, "McpProbeDb", {
engine: rds.DatabaseClusterEngine.auroraPostgres({
version: rds.AuroraPostgresEngineVersion.VER_16_2,
}),
vpc,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
writer: rds.ClusterInstance.serverlessV2("writer", {
// performanceInsightRetention for v2 works the same as provisioned
performanceInsightRetention: rds.PerformanceInsightRetention.DEFAULT,
}),
// Optional: add a reader for HA (promotes automatically on writer failure)
readers: [
rds.ClusterInstance.serverlessV2("reader", {
scaleWithWriter: true, // reader capacity tracks the writer capacity
}),
],
serverlessV2MinCapacity: 2, // ACUs — set to 0.5 for dev; 2+ for production
serverlessV2MaxCapacity: 32, // ACUs — burst headroom
iamAuthentication: true,
deletionProtection: true,
backup: { retention: cdk.Duration.days(7) },
});
// RDS Proxy for Lambda-intensive workloads
const proxy = new rds.DatabaseProxy(this, "McpDbProxy", {
proxyTarget: rds.ProxyTarget.fromCluster(cluster),
vpc,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
iamAuth: true, // Lambda functions authenticate via IAM, not password
requireTLS: true,
idleClientTimeout: cdk.Duration.minutes(10),
maxConnectionsPercent: 70, // proxy claims 70% of max_connections; reserve 30% for direct admin
});
scaleWithWriter: true on readers: by default a reader instance scales independently. Setting scaleWithWriter: true makes the reader's capacity track the writer's capacity, which ensures the reader is ready to serve traffic at full throughput immediately after a writer failover promotes it.
Data API migration from v1
Aurora Serverless v1 callers that used the Data API (rds-data.ExecuteStatement) cannot directly migrate to v2 — the Data API is not available. The two migration paths are:
- RDS Proxy + IAM auth: Create an RDS Proxy in front of the v2 cluster. Lambda functions authenticate to the proxy using IAM credentials (
rds:connectIAM action). The proxy maintains a warm connection pool to the database; each Lambda invocation borrows a connection from the pool without the overhead of a full TCP handshake and TLS negotiation to the cluster. - Connection pool in Lambda layer: Use
pg(PostgreSQL) ormysql2(MySQL) with a module-level connection pool that Lambda reuses across invocations in the same execution environment. This works but provides weaker isolation between concurrent invocations than RDS Proxy.
// Lambda using RDS Proxy + IAM auth (no password needed)
import { Signer } from "@aws-sdk/rds-signer";
import { Client } from "pg";
const signer = new Signer({
hostname: process.env.PROXY_ENDPOINT!,
port: 5432,
region: process.env.AWS_REGION!,
username: "lambda_user",
});
// Module-level (reused across warm invocations)
let client: Client | null = null;
export async function handler() {
if (!client || !client.connected) {
const token = await signer.getAuthToken();
client = new Client({
host: process.env.PROXY_ENDPOINT,
port: 5432,
database: "mcp_probe",
user: "lambda_user",
password: token, // IAM token acts as the password; rotates every 15 min
ssl: { rejectUnauthorized: true },
});
await client.connect();
}
// ... use client
}
CloudWatch monitoring for Serverless v2
Two metrics are specific to Aurora Serverless v2:
ServerlessDatabaseCapacity— current ACU count (float); alarm when this stays nearmaxCapacityfor more than 5 minutes indicates the ceiling is too low.ACUUtilization— percentage of the maximum ACU used; values above 80% consistently = raisemaxCapacityor optimize queries.
# CloudWatch alarm: v2 cluster has been pegged at max capacity for 5+ minutes
aws cloudwatch put-metric-alarm \
--alarm-name "mcp-probe-db-capacity-ceiling" \
--namespace "AWS/RDS" \
--metric-name "ServerlessDatabaseCapacity" \
--dimensions Name=DBClusterIdentifier,Value=mcp-probe-db \
--statistic Maximum \
--period 60 \
--evaluation-periods 5 \
--threshold 31.5 \
--comparison-operator GreaterThanOrEqualToThreshold \
--alarm-actions arn:aws:sns:us-east-1:123456789012:mcp-ops-alerts
AliveMCP probe-database pattern
AliveMCP's probe workers write one status row per MCP endpoint per 60-second probe cycle. At 10,000 monitored endpoints, that is roughly 167 writes per second at steady state. Aurora Serverless v2 at 4 ACUs handles this workload comfortably; the cluster scales up to 16 ACUs during registry re-scans (which do bulk inserts) and scales back down within 20 minutes. RDS Proxy sits in front because the alert-worker Lambda function spawns up to 50 concurrent executions when a mass-outage event fires many alert webhooks simultaneously. Without the proxy, each Lambda invocation opens a native Aurora connection, potentially saturating the connection pool; the proxy amortizes the native connections to approximately 10, regardless of Lambda concurrency.
Common failure modes
| Symptom | Cause | Fix |
|---|---|---|
| First query after idle takes 2-3 seconds | Cluster scaled down to minimum ACU during inactivity; first query triggers a scale-up event while memory re-warms | Increase minCapacity to 2 for latency-sensitive paths; or use a keep-warm cron that pings the database every 10 minutes |
| "Data API is not available for this cluster" error after migration from v1 | Data API is not supported on Aurora Serverless v2; it was available on v1 | Migrate callers to RDS Proxy + native driver; see migration section above |
| Too many connections error despite Serverless v2 scaling | max_connections scales with ACUs (~200 at 2 ACUs, ~800 at 8 ACUs) but Lambda burst can spike faster than the scale-up response time | Add RDS Proxy — the proxy maintains a fixed connection pool to the database, multiplexing thousands of Lambda connections through it |
ServerlessDatabaseCapacity stuck at maxCapacity | Workload exceeds the configured ceiling; database is CPU/memory saturated | Raise maxCapacity (up to 128 ACUs); investigate slow queries with Performance Insights |
| Reader capacity lags writer on failover | Reader has scaleWithWriter: false (default); reader was at minimum capacity when promoted to writer | Set scaleWithWriter: true on the reader instance so its capacity tracks the writer |
| CDK deploy fails: "Invalid DB instance class" for serverless | instanceType was set instead of leaving it as the implicit db.serverless for Serverless v2 instances | Use rds.ClusterInstance.serverlessV2() factory method instead of rds.ClusterInstance.provisioned() |