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:

FeatureAurora Serverless v1Aurora Serverless v2
Scaling modelStep-function (4 → 8 → 16 ACUs); brief pause during scale eventContinuous fractional scaling; no pause
Minimum capacity0 ACUs (can pause); 1 ACU non-pausing0.5 ACUs minimum; no true pause (0 ACU allowed on Aurora MySQL 3.04+/Aurora PostgreSQL 15.2+ only)
Data APISupported — HTTP-based connections, no TCP pool neededNot supported — must use native driver or RDS Proxy
Global databaseNot supportedSupported as secondary replica capacity type
Multi-AZSingle AZ (writer only)Multi-AZ via additional reader instance
Engine versionAurora MySQL 5.6/5.7; Aurora PostgreSQL 10/11Aurora MySQL 8.0 (3.x); Aurora PostgreSQL 13+
Max capacity256 ACUs128 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:

  1. RDS Proxy + IAM auth: Create an RDS Proxy in front of the v2 cluster. Lambda functions authenticate to the proxy using IAM credentials (rds:connect IAM 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.
  2. Connection pool in Lambda layer: Use pg (PostgreSQL) or mysql2 (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:

# 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

SymptomCauseFix
First query after idle takes 2-3 secondsCluster scaled down to minimum ACU during inactivity; first query triggers a scale-up event while memory re-warmsIncrease 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 v1Data API is not supported on Aurora Serverless v2; it was available on v1Migrate callers to RDS Proxy + native driver; see migration section above
Too many connections error despite Serverless v2 scalingmax_connections scales with ACUs (~200 at 2 ACUs, ~800 at 8 ACUs) but Lambda burst can spike faster than the scale-up response timeAdd RDS Proxy — the proxy maintains a fixed connection pool to the database, multiplexing thousands of Lambda connections through it
ServerlessDatabaseCapacity stuck at maxCapacityWorkload exceeds the configured ceiling; database is CPU/memory saturatedRaise maxCapacity (up to 128 ACUs); investigate slow queries with Performance Insights
Reader capacity lags writer on failoverReader has scaleWithWriter: false (default); reader was at minimum capacity when promoted to writerSet scaleWithWriter: true on the reader instance so its capacity tracks the writer
CDK deploy fails: "Invalid DB instance class" for serverlessinstanceType was set instead of leaving it as the implicit db.serverless for Serverless v2 instancesUse rds.ClusterInstance.serverlessV2() factory method instead of rds.ClusterInstance.provisioned()