Why Multi-Region Aurora
Aurora Global Database replicates your primary cluster's data to up to five secondary regions with typical replication lag under a second. The secondary regions serve read traffic locally, and in a disaster scenario, you can promote a secondary region to primary in under a minute.
Let's be clear about what this gives you: disaster recovery with an RPO (Recovery Point Objective) typically under 1 second and an RTO (Recovery Time Objective) under 1 minute. For applications that can't tolerate even brief data loss or extended downtime, this is one of the few managed database options that delivers both.
Architecture Layout
The primary region hosts the writer instance plus up to 15 read replicas. Each secondary region gets its own Aurora cluster with up to 16 read replicas. Writes go to the primary region only — there's no multi-master write capability in Aurora Global Database (that's a separate Aurora feature with different tradeoffs).
# Primary cluster in us-east-1
resource "aws_rds_global_cluster" "main" {
global_cluster_identifier = "myapp-global"
engine = "aurora-postgresql"
engine_version = "15.4"
storage_encrypted = true
}
resource "aws_rds_cluster" "primary" {
provider = aws.us_east_1
cluster_identifier = "myapp-primary"
global_cluster_identifier = aws_rds_global_cluster.main.id
engine = "aurora-postgresql"
engine_version = "15.4"
master_username = "admin"
master_password = var.db_password
db_subnet_group_name = aws_db_subnet_group.primary.name
vpc_security_group_ids = [aws_security_group.aurora_primary.id]
}
# Secondary cluster in eu-west-1
resource "aws_rds_cluster" "secondary" {
provider = aws.eu_west_1
cluster_identifier = "myapp-secondary"
global_cluster_identifier = aws_rds_global_cluster.main.id
engine = "aurora-postgresql"
engine_version = "15.4"
db_subnet_group_name = aws_db_subnet_group.secondary.name
vpc_security_group_ids = [aws_security_group.aurora_secondary.id]
depends_on = [aws_rds_cluster.primary]
}
Failover Process in Detail
When you need to promote a secondary region, there are two approaches: planned failover and unplanned (disaster) failover.
Planned failover (managed failover) is what you use for maintenance or region migration. Aurora ensures the secondary is fully caught up with the primary before switching. Zero data loss guaranteed. The process takes 1-2 minutes and your application needs to reconnect to the new endpoint.
Unplanned failover happens when the primary region is unavailable. You detach the secondary cluster from the global database and promote it. This is faster — typically under a minute — but any transactions that hadn't replicated to the secondary are lost. With sub-second replication lag, the data loss window is tiny but nonzero.
Application-Level Failover Handling
Your application needs to handle the endpoint switch. Aurora Global Database doesn't automatically redirect connections. Two approaches work:
- Use Route53 health checks with CNAME records pointing to the Aurora cluster endpoints. When the primary fails, Route53 fails over to the secondary region's endpoint.
- Use a connection proxy (like RDS Proxy) in each region. Your application connects to the local proxy, and the proxy handles writer routing.
The Route53 approach is simpler but adds DNS propagation delay. RDS Proxy gives faster failover but adds another managed component and its associated cost.
Replication Lag Monitoring
Aurora exposes replication lag through the AuroraGlobalDBReplicationLag CloudWatch metric. Set alarms on this — if lag exceeds 5 seconds, something's wrong. Normal operation should show sub-100ms lag most of the time.
Common causes of increased lag: network issues between regions (rare with AWS backbone), heavy write volume on the primary that the secondary can't keep up with, or storage volume size triggering internal reorganization. The last one resolves itself but can spike lag to 2-3 seconds temporarily.
Cost Implications
You pay for the Aurora instances in every region, the storage in every region, and the cross-region data replication traffic. For a db.r6g.xlarge primary with one reader and a secondary region with one reader, you're looking at roughly $2,400/month in compute alone before storage and I/O costs.
The replication data transfer isn't free either. Cross-region Aurora replication runs about $0.02/GB. For a write-heavy workload generating 100GB of WAL data per month, that's an additional $2. Not significant for most workloads, but worth tracking for extremely high write volumes.
The real cost question is: what's an hour of downtime worth? If the answer is more than $2,400/month, Global Database pays for itself as an insurance policy.
Write Forwarding: Secondary Region Writes
Aurora Global Database now supports write forwarding. Secondary regions can accept write queries and forward them to the primary region transparently. This simplifies application architecture for globally distributed apps.
The tradeoff is latency. A write forwarded from eu-west-1 to us-east-1 adds the cross-region round trip (~70-100ms) to the write latency. For write-light applications, this is acceptable. For write-heavy workloads, route writes directly to the primary region.
resource "aws_rds_cluster" "secondary" {
provider = aws.eu_west_1
cluster_identifier = "myapp-secondary"
global_cluster_identifier = aws_rds_global_cluster.main.id
engine = "aurora-postgresql"
engine_version = "15.4"
enable_global_write_forwarding = true
}
Connection Management During Failover
When a failover happens, your application's existing connections break. Configure your connection pool to validate connections before use and to reconnect automatically. Set aggressive connection timeouts during failover.
Backup and Point-in-Time Recovery
Aurora provides continuous backup to S3 with a retention period of 1-35 days. You can restore to any second within that window. The restore creates a new cluster, not a rollback of the existing one.
A practice I recommend: test your restore process quarterly. Spin up a restored cluster, point a test application at it, and verify data integrity. The restore process itself is automated, but the reconnect-and-verify part always has surprises the first time you do it under pressure.
Performance Tuning for Global Deployments
Secondary region read replicas can serve read traffic with single-digit millisecond latency for cached queries. But cache warmth matters. A freshly promoted secondary has a cold buffer pool and will be slow until it warms up. Expect 10-15 minutes of degraded read performance while the buffer pool fills.
For critical applications, run synthetic read traffic against secondary region replicas continuously. This keeps the buffer pool warm and ensures that promotion performance is immediately good. The synthetic traffic also serves as a continuous health check.
Cost Optimization for Global Databases
Aurora Global Database costs scale with the number of regions and instances. For a setup with us-east-1 (writer + 1 reader) and eu-west-1 (1 reader), you're paying for 3 instances total. Each db.r6g.xlarge runs about $800/month. Add storage at $0.10/GB/month replicated across both regions, and I/O charges at $0.20 per million requests.
One optimization: use different instance sizes per region based on traffic. If eu-west-1 handles 20% of your read traffic, it doesn't need the same instance size as us-east-1. A db.r6g.large in the secondary region versus db.r6g.xlarge in the primary saves $400/month without affecting the global replication.
Another approach: use Aurora Serverless v2 for the reader instances. Serverless v2 scales based on actual demand, so your secondary region's reader only consumes (and charges for) the capacity it actually uses. During off-peak hours for that region, the instance scales down to the minimum ACU (Aurora Capacity Unit), saving significantly versus a fixed-size instance running at low utilization.
resource "aws_rds_cluster_instance" "secondary_reader" {
provider = aws.eu_west_1
cluster_identifier = aws_rds_cluster.secondary.id
instance_class = "db.serverless"
engine = "aurora-postgresql"
}
resource "aws_rds_cluster" "secondary" {
serverlessv2_scaling_configuration {
min_capacity = 0.5
max_capacity = 16
}
}
The serverless configuration with min_capacity of 0.5 ACU means the reader scales down to nearly nothing during quiet periods. At $0.12 per ACU-hour, 0.5 ACU costs about $43/month — compared to $800/month for a fixed db.r6g.xlarge. The tradeoff is that scaling up from 0.5 ACU takes 15-30 seconds, so latency spikes during sudden traffic increases are possible.
Testing Failover Without an Actual Disaster
Run failover drills quarterly. Aurora supports managed planned failover for Global Database, which you can trigger through the console or CLI. The drill takes 1-2 minutes to complete, during which writes are paused. Your application should handle the write interruption gracefully — queue writes locally or return a "try again" response to clients.
Document the failover runbook in detail: who initiates it, which DNS records need updating (if you're not using automated DNS failover), which services need to be restarted, and what verification steps confirm the new primary is operating correctly. The runbook should be executable by any on-call engineer, not just the database team. During a real disaster, the database team might be unreachable.