AWS Aurora · RDS · 2026-10-24 · Aurora/RDS advanced arc

Aurora and RDS Advanced Patterns for MCP Server Databases: Serverless v2, Global Database, Blue/Green Deployments, and Performance Insights

MCP server backends that grow beyond a single-region SQLite prototype and into production Aurora or RDS clusters hit four advanced operational problems in sequence, and solving them in the wrong order creates more work later. Aurora Serverless v2's continuous ACU scaling sounds like it eliminates capacity planning — it does not eliminate connection-limit planning: a Lambda-heavy MCP alert worker that bursts to 200 concurrent executions saturates the database's max_connections parameter before the ACU scale-up completes, requiring RDS Proxy in front of the cluster regardless of how generous the maxCapacity setting is; and the Data API that teams relied on in Aurora Serverless v1 is gone in v2, forcing a native-driver migration for any Lambda that used HTTP-based database access. Aurora Global Database's storage-level cross-region replication achieves sub-second RPO, but the two failover paths — managed planned failover and detach-and-promote — have incompatible data-loss profiles: managed planned failover guarantees zero data loss by stopping writes and draining replication lag before promoting the secondary, while detach-and-promote promotes immediately and accepts up to one second of data loss — using the wrong one in a DR scenario means either data loss you didn't expect or a 5-minute stall waiting for lag to drain while the primary is already down. RDS Blue/Green Deployments create a fully managed staging copy that replicates every write from production, giving teams a safe lab for schema changes — but the 60–90 second switchover write interruption is non-negotiable, the schema changes on green must be backward-compatible with the application code running against blue if you want rollback to be safe, and for PostgreSQL the source cluster needs rds.logical_replication = 1 set in a parameter group and applied (requiring a reboot) before creating the deployment. RDS Performance Insights' DBLoad metric is unitless and only alarms when it exceeds the vCPU count — a DBLoad of 3 on a two-vCPU instance is a crisis, but on an eight-vCPU instance it is headroom — and the wait event breakdown is the diagnostic layer that transforms "the database is slow" into "these specific probe insert queries are competing for the same row range, fix it by partitioning workers by server_slug prefix." This guide synthesises all four patterns. Pattern 1 covers Aurora Serverless v2 ACU configuration, the RDS Proxy necessity, Data API migration from v1, and CloudWatch monitoring — synthesised from Aurora Serverless v2 for MCP Servers — Continuous Scaling, ACU Configuration, RDS Proxy. Pattern 2 covers Aurora Global Database architecture, the two failover paths, write forwarding limitations, and replication lag monitoring — synthesised from Aurora Global Database for MCP Servers — Multi-Region Replication, Sub-Second RPO, Managed Failover. Pattern 3 covers RDS Blue/Green deployment prerequisites, the staging and switchover lifecycle, schema compatibility rules, and rollback — synthesised from RDS Blue/Green Deployments for MCP Servers — Zero-Downtime Schema Changes, Switchover, Rollback. Pattern 4 covers Performance Insights DBLoad interpretation, wait event taxonomy, Top SQL analysis, and the relationship between Enhanced Monitoring and PI — synthesised from RDS Performance Insights for MCP Servers — DB Load, Wait Events, Top SQL Analysis.

TL;DR

Pattern 1 — Aurora Serverless v2: Continuous Scaling and the Connection-Limit Problem

Why v2 is architecturally different from v1

Aurora Serverless v1 used a step-function scaling model: the cluster scaled from 4 ACUs to 8 ACUs to 16 ACUs in discrete jumps, and each jump involved a brief pause during which connections were held. v2 replaced that with continuous fractional scaling — the cluster scales from 0.5 to 128 ACUs in sub-ACU increments with no pause. This makes v2 far more suitable for production MCP backends where probe write traffic is not completely uniform.

However, the architectural differences go deeper than scaling granularity:

FeatureAurora Serverless v1Aurora Serverless v2
Scaling modelStep-function; brief pause at each stepContinuous fractional; no pause
Minimum capacity0 ACUs (can fully pause)0.5 ACUs minimum; never truly pauses
Data APISupported — HTTP-based, no TCP pool neededNot supported — must use native driver or RDS Proxy
Global DatabaseNot supportedSupported as secondary capacity type
Max capacity256 ACUs128 ACUs per instance
Engine versionAurora MySQL 5.6/5.7; Aurora PostgreSQL 10/11Aurora MySQL 8.0 (3.x); Aurora PostgreSQL 13+

For MCP teams migrating from v1, the Data API removal is the most operationally disruptive change. Any Lambda function that used rds-data.ExecuteStatement to issue queries without maintaining a TCP connection pool must be rewritten. The two migration paths are RDS Proxy + IAM authentication (recommended for Lambda-heavy workloads) or a module-level connection pool in the Lambda function itself (acceptable for low-concurrency scenarios).

Setting minimum capacity for production latency

The minCapacity setting is the most consequential configuration decision for production MCP backends. Setting it to 0.5 (the minimum) means the cluster scales back to 0.5 ACUs after a sustained quiet period and must scale up again on the next request — this produces a 1–3 second delay on the first query after idle, which appears as a probe-worker timeout in the monitoring system. For production probe databases that must write a result row every 60 seconds for every monitored endpoint, set minCapacity: 2:

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: rds.PerformanceInsightRetention.DEFAULT,
  }),
  readers: [
    rds.ClusterInstance.serverlessV2("reader", {
      scaleWithWriter: true,  // reader capacity tracks writer — critical for safe failover
    }),
  ],
  serverlessV2MinCapacity: 2,   // 0.5 for dev; 2+ for production to pre-warm connections
  serverlessV2MaxCapacity: 32,  // burst headroom for registry re-scans
  iamAuthentication: true,
  deletionProtection: true,
  backup: { retention: cdk.Duration.days(7) },
});

Scale-down hysteresis works in your favor for bursty probe workloads: Aurora Serverless v2 requires 15 consecutive minutes of low utilization before scaling down. A registry re-scan that spikes the cluster to 16 ACUs for 3 minutes will scale back down slowly, keeping warm connections ready for the next re-scan rather than scaling to the floor and forcing a cold-start on the next burst.

RDS Proxy is mandatory for Lambda-heavy workloads

This is the most widely misunderstood aspect of Serverless v2: the connection limit scales with ACUs, but Lambda cold-start bursts happen faster than ACU scale-up responds. At 2 ACUs, Aurora PostgreSQL allows approximately 200 connections. When an MCP monitoring alert fires and triggers 200 concurrent Lambda alert-worker executions, each opening a native Aurora connection, the cluster hits max_connections before the scale-up event completes — the alert workers fail with "too many connections" even though the cluster has significant ACU headroom.

RDS Proxy solves this by maintaining a fixed-size native connection pool to the Aurora cluster and multiplexing application connections through it. Hundreds of Lambda executions each connect to the proxy (which is fast, connection-light, and immediately available), and the proxy manages the actual native connections to Aurora:

const proxy = new rds.DatabaseProxy(this, "McpDbProxy", {
  proxyTarget: rds.ProxyTarget.fromCluster(cluster),
  vpc,
  vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
  iamAuth: true,       // Lambda authenticates via IAM, not password
  requireTLS: true,
  idleClientTimeout: cdk.Duration.minutes(10),
  maxConnectionsPercent: 70,   // proxy claims 70%; reserve 30% for direct admin access
});

Lambda functions using IAM auth to the proxy do not need database passwords stored in Secrets Manager — they generate a short-lived IAM token using the @aws-sdk/rds-signer package and use it as the database password:

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",
});

let client: Client | null = null;

export async function handler() {
  if (!client || !client.connected) {
    const token = await signer.getAuthToken();  // IAM token rotates every 15 min
    client = new Client({
      host: process.env.PROXY_ENDPOINT,
      port: 5432,
      database: "mcp_probe",
      user: "lambda_user",
      password: token,
      ssl: { rejectUnauthorized: true },
    });
    await client.connect();
  }
  // reuse warm client across Lambda invocations
}

Monitoring: two Serverless v2-specific CloudWatch metrics

ServerlessDatabaseCapacity (current ACU count as a float) and ACUUtilization (percentage of maxCapacity consumed) are the primary Serverless v2 observability signals. Standard Aurora metrics like CPUUtilization still apply but are less directly actionable — ACU utilization is the primary capacity signal:

# Alarm: cluster pegged at max capacity for 5+ minutes = ceiling too low
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

Pattern 2 — Aurora Global Database: Multi-Region Replication and the Two Failover Paths

Storage-level vs engine-level replication

Aurora Global Database uses dedicated replication infrastructure at the storage layer — below the database engine, running in parallel with the engine's normal I/O path. This is architecturally distinct from Aurora read replicas within a region (which use InnoDB/WAL-based replication through the engine) or RDS cross-region read replicas (which use binlog replication). Because the replication bypasses the engine, it achieves typical lag under 1 second between even distant regions (us-east-1 to ap-southeast-1 averages 200–400 ms lag), and the replication cost scales with write I/O volume rather than with query complexity.

For MCP monitoring platforms that need probe data accessible in multiple regions — either for read locality (lower-latency status dashboard for European users) or disaster recovery — Global Database is the right tool. Application-level replication (writing to two separate Aurora clusters independently) is operationally more complex, produces divergence risk, and cannot guarantee RPO.

The two failover paths and when to use each

This is the most operationally consequential distinction in Global Database: there are two distinct failover mechanisms with incompatible data-loss guarantees, and using the wrong one in a DR scenario either wastes minutes waiting for lag to drain from an unreachable primary or accepts unexpected data loss in a planned migration.

Managed planned failover (failover-global-cluster) — use when the primary region is healthy and you are performing a scheduled maintenance migration or a regional load rebalancing:

aws rds failover-global-cluster \
  --global-cluster-identifier mcp-probe-global \
  --target-db-cluster-identifier arn:aws:rds:eu-west-1:123456789012:cluster:mcp-probe-secondary-eu \
  --allow-data-loss false

# Monitor: the primary stops accepting writes, lag drains to zero, secondary is promoted
# Old primary becomes a secondary after the operation (~30-60 seconds total)
aws rds describe-global-clusters \
  --global-cluster-identifier mcp-probe-global \
  --query "GlobalClusters[0].GlobalClusterMembers[*].{ARN:DBClusterArn,IsWriter:IsWriter}"

The API blocks writes on the primary, waits for AuroraGlobalDBReplicationLag to reach zero on the target secondary, then promotes. If the primary has long-running open transactions preventing lag drain, the operation stalls — terminate long-running transactions on the primary before initiating the failover.

Detach-and-promote (remove-from-global-cluster) — use only when the primary region is unreachable and you need to restore write capability in the secondary region. This operation does not wait for lag to drain:

# Use only in DR: promotes the secondary immediately with up to ~1 second data loss
aws rds remove-from-global-cluster \
  --global-cluster-identifier mcp-probe-global \
  --db-cluster-identifier arn:aws:rds:eu-west-1:123456789012:cluster:mcp-probe-secondary-eu \
  --region eu-west-1

# The secondary is now a standalone Aurora cluster with write capability
# Any replication lag at promotion moment = data written to primary but not yet replicated = lost

After the primary region recovers, re-add the old primary as a new secondary to reconstitute the Global Database. Do not attempt to merge the diverged write histories — the detach creates a clean split point.

Write forwarding: the 50 ms tax

Global write forwarding lets an application connected to a secondary cluster issue writes that are forwarded to the primary region over the storage replication network. This is useful for occasional administrative writes (triggering a manual probe re-check, creating an alert acknowledgment) from secondary-region tooling, without requiring a separate connection to the primary region.

The tax is real: every forwarded write adds the full cross-region network roundtrip (20–50 ms for eu-west-1 to us-east-1). Do not route probe workers' high-frequency status inserts through write forwarding — the 50 ms per insert at 167 writes per second translates to steady-state latency that a direct connection to the primary's writer endpoint does not incur.

Route 53 CNAME for transparent endpoint switching

After any failover — planned or DR — application connection strings must be updated from the old primary's writer endpoint to the new one. The cleanest way to automate this is a Route 53 CNAME with a short TTL (30 seconds) pointing to the current primary's cluster writer endpoint:

# Before failover: db-writer.internal.example.com → mcp-probe-primary.cluster-xxx.us-east-1.rds.amazonaws.com (TTL 30s)
# After failover: update the CNAME to the new primary in eu-west-1
# Application sees the new endpoint within 30 seconds, no code change needed

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch '{
    "Changes": [{
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "db-writer.internal.example.com",
        "Type": "CNAME",
        "TTL": 30,
        "ResourceRecords": [{"Value": "mcp-probe-secondary-eu.cluster-yyy.eu-west-1.rds.amazonaws.com"}]
      }
    }]
  }'

Pattern 3 — RDS Blue/Green Deployments: Safe Schema Migrations with a Managed Staging Database

What Blue/Green creates

RDS Blue/Green Deployments provision a complete replica of your production cluster (green) connected to production (blue) via a managed replication channel. During the staging period, green receives every write made to blue in near-real-time — it is a continuously synchronized mirror, not a point-in-time snapshot. This makes it safe to run schema migrations on green, test application behavior against the migrated schema, and discard green entirely if the migration looks wrong, all without touching the production database.

The lifecycle has four phases:

PhaseBlue (production)Green (staging)
ProvisioningNormal production trafficSnapshot restore + initial replication catch-up (minutes to hours depending on data size)
StagingNormal production trafficContinuously replicating from blue; you apply schema changes and test here
Switchover (~60–90s)Writes paused (write quorum stop); DB identifiers swapLag drains to zero; becomes the new production writer
Post-switchoverNow the old blue — read-only fallback; replication from new blue (old green) to old blue beginsNow the production writer

Prerequisites: logical replication must be enabled before you create the deployment

For PostgreSQL source clusters, this is a reboot-required change that must happen in advance of the migration window. Setting rds.logical_replication = 1 in the parameter group does not take effect until the instance is rebooted — if you set it and try to create the Blue/Green deployment without rebooting, the deployment creation fails with "logical replication is not enabled":

# For PostgreSQL: enable logical replication in parameter group (requires reboot)
# Verify the parameter group is applied to the cluster
aws rds describe-db-clusters \
  --db-cluster-identifier mcp-probe-db \
  --query "DBClusters[0].DBClusterParameterGroup"

# Check the parameter value
aws rds describe-db-cluster-parameters \
  --db-cluster-parameter-group-name mcp-probe-pg16-params \
  --query "Parameters[?ParameterName=='rds.logical_replication'].{Name:ParameterName,Value:ParameterValue,ApplyType:ApplyType}"

# If ApplyType is "static", a reboot is required before the parameter takes effect
# Schedule the reboot during a low-traffic window BEFORE the migration sprint

# For MySQL/Aurora MySQL: binlog is typically enabled by default on Aurora MySQL 3.x
# Verify:
# SELECT @@binlog_format, @@log_bin;  -- should return ROW, 1
# Create the deployment (after verifying prerequisites)
aws rds create-blue-green-deployment \
  --blue-green-deployment-name mcp-probe-bgd-schema-v3 \
  --source arn:aws:rds:us-east-1:123456789012:cluster:mcp-probe-db \
  --target-engine-version "16.3" \   # optional: also upgrade engine version on green
  --tags Key=Purpose,Value=SchemaV3Migration

Schema compatibility rule: the expand-contract pattern

The most important constraint for Blue/Green Deployments is one that is easy to miss: the schema changes you apply to green must be backward-compatible with the application code version currently running against blue. This matters for rollback. If you switch over to green, discover a problem, and want to roll back by deleting the blue/green deployment (which restores the old blue as writer), the application code that shipped for the green schema will now run against the old blue schema.

Backward-compatible changes (safe for rollback): adding a nullable column, adding an index, adding a table, widening a varchar, relaxing a constraint.

Backward-incompatible changes (rollback is not safe): dropping a column, renaming a column, adding a NOT NULL column without a default, narrowing a type. If you must make an incompatible change, use the expand-contract pattern: in one deployment, add the new column alongside the old one and ship application code that writes to both; in a second deployment, drop the old column after all readers are confirmed migrated.

Switchover execution and the 90-second gap

The switchover itself is non-negotiable: applications experience approximately 60–90 seconds of write interruption for Aurora, and up to 3 minutes for large RDS MySQL instances. During this window, the probe workers' INSERT statements queue at the connection pool and resume automatically after the identifier swap completes — but the uptime timeline has a gap. AliveMCP handles this by recording a "monitoring interrupted — database migration in progress" status for any endpoint whose probe result falls within the switchover window, rather than recording a false outage.

# Pre-switchover checklist:
# 1. Replication lag < 100ms consistently for 10+ minutes
# 2. Application code tested against green and ready for the new schema
# 3. Maintenance window selected (low MCP endpoint traffic)
# 4. RDS Proxy (if used) will auto-reroute — no proxy config change needed

aws cloudwatch get-metric-statistics \
  --namespace "AWS/RDS" \
  --metric-name "AuroraBinlogReplicaLag" \
  --dimensions Name=DBClusterIdentifier,Value=mcp-probe-db \
  --period 60 --statistics Maximum \
  --start-time $(date -u -d '10 minutes ago' +%Y-%m-%dT%H:%M:%SZ) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ)

# Execute switchover (blocks until complete or timeout)
aws rds switchover-blue-green-deployment \
  --blue-green-deployment-identifier bgd-1234567890abcdef0 \
  --switchover-timeout 300    # fail if lag hasn't drained within 300 seconds

If you use RDS Proxy in front of the cluster, the proxy transparently handles the endpoint swap — applications connected to the proxy endpoint experience a brief connection drop and automatic reconnect; no DNS TTL propagation or connection string change is needed. This is the primary reason to put RDS Proxy in front of any cluster that will use Blue/Green Deployments.

Pattern 4 — RDS Performance Insights: Reading Wait Events and DB Load

DBLoad is not a percentage

The most common misunderstanding of Performance Insights is treating DBLoad like CPU utilization. DBLoad measures Average Active Sessions (AAS) — the average number of database sessions that are actively executing or waiting at any given moment. A DBLoad of 1.5 means 1.5 sessions were active on average. This number only becomes alarming when it exceeds the instance's vCPU count, because once more sessions are active than there are CPUs to run them, query latency increases regardless of what the sessions are waiting on.

DBLoad vs instance vCPUsInterpretationAction
DBLoad < 0.5 × vCPULightly loaded; probe workers have write headroomNo action needed
DBLoad ~= vCPUHealthy working loadMonitor; no action needed
DBLoad 1–2 × vCPUSaturated; query latency increasingInvestigate Top SQL and wait events
DBLoad > 2 × vCPUSeverely saturated; timeouts likelyImmediate action: scale or fix query

Enable Performance Insights on each instance — the 7-day retention tier is free for most instance classes:

aws rds modify-db-instance \
  --db-instance-identifier mcp-probe-db-writer \
  --enable-performance-insights \
  --performance-insights-retention-period 7 \
  --apply-immediately

Wait event taxonomy: the diagnostic layer

DBLoad tells you the database is overloaded. Wait events tell you why. Each wait event maps to a specific root cause and a specific fix:

Wait eventRoot causeFix for MCP probe workloads
CPUQuery plans doing full table scans, complex aggregations, index rebuildAdd composite index on (server_slug, checked_at); avoid SELECT * on probe_results
Lock:relationTable-level lock from DDL (ALTER TABLE) competing with concurrent probe insertsRun schema changes during maintenance window; use Blue/Green Deployments
Lock:tupleRow-level lock contention — multiple probe workers updating the same server_slug rowPartition probe workers by server_slug hash prefix; use INSERT ... ON CONFLICT DO UPDATE with row-level locking
IO:DataFileReadBuffer cache miss — hot dataset exceeds Aurora buffer pool or IOPS insufficientScale to larger instance class (more memory = larger buffer pool); check coverage of hot indexes
IO:BufFileReadSort or hash join spilling to disk — work_mem too low for the query's sort bufferIncrease work_mem for analytics queries; avoid large aggregations in the probe write path
Client:ClientReadDB waiting for the application to read rows — network saturation or application-side processing delayAdd LIMIT to dashboard queries; stream result rows; check ECS task network bandwidth

Top SQL: sort by DB load per call, not by execution count

The Top SQL tab ranks queries by their contribution to DBLoad. "Total load" (total AAS contribution) finds the highest-impact queries for overall throughput. "DB load per call" finds the slowest individual queries — a query running 100 times per minute at 500 ms average wait contributes more per invocation than a query running 10,000 times per minute at 1 ms. For MCP probe databases, the highest-impact query is almost always the bulk status insert during registry re-scans; the highest per-call load is usually an unindexed dashboard query joining across the probe_results table without a covering index:

# Via PI API: top 10 SQL statements by AAS over the past hour
aws pi get-resource-metrics \
  --service-type RDS \
  --identifier db-EXAMPLE123EXAMPLEABCDEFG \
  --metric-queries '[{"Metric":"db.load.avg","GroupBy":{"Group":"db.sql","Dimensions":["db.sql.id","db.sql.statement"],"Limit":10}}]' \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --period-in-seconds 300

For Aurora PostgreSQL, cross-reference PI's Top SQL output with pg_stat_statements for historical query statistics (total execution time, mean time per call, rows returned per call) that complement PI's real-time AAS view:

-- Enable pg_stat_statements if not already loaded
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- Find slowest queries by mean execution time, filtered to probe-related tables
SELECT
  substring(query, 1, 120) AS query_snippet,
  calls,
  round((total_exec_time / 1000)::numeric, 2) AS total_sec,
  round(mean_exec_time::numeric, 1) AS avg_ms,
  rows / calls AS avg_rows
FROM pg_stat_statements
WHERE query ILIKE '%probe_results%' OR query ILIKE '%mcp_endpoints%'
ORDER BY mean_exec_time DESC
LIMIT 20;

Enhanced Monitoring: the OS layer alongside PI's DB layer

Performance Insights and Enhanced Monitoring are complementary, not redundant. PI sees what the database engine sees — session states, wait events, SQL plans. Enhanced Monitoring sees what the operating system sees — per-core CPU usage, memory, swap, disk IOPS, network bytes. The combination is what makes diagnosis actionable:

# Enable Enhanced Monitoring at 15-second OS sample granularity
aws rds modify-db-instance \
  --db-instance-identifier mcp-probe-db-writer \
  --monitoring-interval 15 \
  --monitoring-role-arn arn:aws:iam::123456789012:role/rds-monitoring-role \
  --apply-immediately

# The IAM role needs: arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole

AliveMCP's actual probe-database diagnosis

When AliveMCP scaled from 5,000 to 10,000 monitored endpoints, DBLoad during registry re-scans spiked from 0.8 (below vCPU capacity) to 3.5 (1.75× vCPU capacity on a db.r7g.large). The dominant wait event during the spike was Lock:tuple — not CPU, not IO:DataFileRead. This was the diagnostic signal that ruled out index issues and buffer pool pressure and pointed directly at row-lock contention: the bulk re-scan INSERT and the real-time probe worker were racing for the same server_slug rows.

The fix: partition probe workers by the first hex digit of the server_slug's MD5 hash (0–7 and 8–f), so the bulk re-scan worker and the real-time probe worker never target the same row range simultaneously. After deploying the partition, DBLoad during re-scans stayed below 1.8 (under vCPU capacity) and Lock:tuple disappeared from the Top Wait Events chart entirely. The fix required a schema change (adding a hash-partition column) — delivered via Blue/Green Deployment without a production outage.

Failure modes across all four patterns

SymptomPatternCauseFix
First query after idle takes 2–3 secondsServerless v2Cluster scaled to 0.5 ACU minimum during idle; scale-up latency on first requestSet minCapacity: 2 for production; or keep-warm cron pinging database every 10 minutes
"Too many connections" despite Serverless v2 scalingServerless v2Lambda burst saturates max_connections before ACU scale-up respondsAdd RDS Proxy — multiplexes thousands of Lambda connections through a fixed native pool
Data API error after v1 → v2 migrationServerless v2Data API not available on Aurora Serverless v2Migrate callers to RDS Proxy + @aws-sdk/rds-signer IAM auth pattern
Managed failover stuck "failing-over" for >5 minutesGlobal DatabaseLong-running open transactions on primary prevent lag from draining to zeroSet statement_timeout on background workers; terminate transactions before initiating failover
Write forwarding returns "cannot execute insert in read-only transaction"Global DatabaseWrite forwarding not enabled on the secondary cluster, or not enabled in the sessionEnable via modify-db-cluster --enable-global-write-forwarding; verify with SHOW aurora_read_replica_read_consistency
Blue/Green deployment stuck in PROVISIONING for hoursBlue/GreenLarge database (500 GB+) requires multi-hour initial snapshot restore; heavy write load on blue slows replication catch-upWait; avoid creating deployments during bulk imports on blue
"logical replication is not enabled" on deployment creationBlue/GreenPostgreSQL rds.logical_replication = 1 not set, or set but not yet applied (reboot pending)Update parameter group, apply immediately, reboot the source cluster
Application throws "column does not exist" after switchoverBlue/GreenSchema on green dropped a column; application code rolled back to old blue schema after rollbackUse expand-contract pattern: add new column before removing old one; never drop until all code is migrated
Performance Insights tab greyed out in consolePerformance InsightsInstance class does not support PI (db.t2 family)Upgrade instance to db.t3.medium or larger; or use Aurora Serverless v2 which always supports PI
Top SQL shows "SQL not available" for all queriesPerformance InsightsRDS MySQL with performance_schema disabledEnable performance_schema = 1 in parameter group and reboot
DBLoad above vCPU but no single slow query visible in Top SQLPerformance InsightsLoad distributed across many short queries; no single query dominatesLook at wait event type — if Lock:tuple, reduce concurrency via RDS Proxy or workload partitioning

Sequencing these four patterns in a real MCP backend rollout

These four patterns are not independent — they have dependencies that determine the right sequence to deploy them:

  1. Deploy Serverless v2 first, with RDS Proxy in front. This establishes the connection pooling layer that Blue/Green Deployments and failover patterns depend on — RDS Proxy transparently handles endpoint swaps during both Blue/Green switchovers and Global Database failovers.
  2. Enable Performance Insights during initial deployment, not after a performance problem appears. PI data from the first weeks of production load becomes the baseline that makes future anomalies detectable.
  3. Add Global Database after the primary cluster is stable, not during initial provisioning. The first 2–4 weeks of production usage will reveal the real write patterns — knowing the actual write throughput helps right-size the secondary clusters' minCapacity (secondary clusters that serve read-only status dashboards need far less capacity than the writer).
  4. Use Blue/Green Deployments for the first schema migration that arrives after the cluster is live. Setting up logical replication on an already-live cluster requires a reboot — schedule that reboot during the initial quieter phase of production traffic rather than under production load pressure.