AWS Builder Center: Learn, Build and Connect with builders in the AWS community
AWS Builder Center is the official home for builders on AWS. Share and read what others are working on, follow people who inspire you, explore training and workshops, and find tools to support what you're building.
How Do I Analyze Aurora Global Database Replication Lag to Predict Failover Readiness Across Transactional and Analytics Workloads?
You've deployed an Aurora Global Database across two regions for disaster recovery. But when an actual impairment occurs, how do you know your secondary cluster is ready to take over? If replication lag spikes during peak hours, a failover could mean data loss — and you won't know until it's too late. This article shows you how to monitor, analyze, and alarm on replication lag metrics to continuously assess failover readiness and predict your actual RPO.
Key Metrics for Aurora Global Database Replication
Aurora Global Database exposes several CloudWatch metrics (available only in secondary regions):
| Metric | What It Measures | Unit |
|---|---|---|
AuroraGlobalDBReplicationLag | How far the secondary cluster's storage volume is behind the primary — for both user and system transactions | Milliseconds |
AuroraGlobalDBProgressLag | Lag between primary and secondary cluster volumes | Milliseconds |
AuroraReplicaLag | Lag between the secondary cluster volume and its DB instances (reader endpoints) | Milliseconds |
End-to-end lag = AuroraGlobalDBProgressLag + AuroraReplicaLag
This is your true RPO indicator — the total time gap between what's committed on the primary writer and what's readable on the secondary region's instances.
Step 1: Establish Your Baseline
Before you can detect anomalies, you need to understand normal behavior. Query 14 days of lag data at 1-minute granularity:
# Get replication lag statistics for last 14 days aws cloudwatch get-metric-statistics \ --namespace AWS/RDS \ --metric-name AuroraGlobalDBReplicationLag \ --dimensions Name=DBClusterIdentifier,Value=my-global-cluster-secondary \ --start-time $(date -u -v-14d '+%Y-%m-%dT%H:%M:%S') \ --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') \ --period 3600 \ --statistics Average Maximum p99 \ --region us-east-2
Build a baseline profile:
| Time Window | Typical p50 Lag | Typical p99 Lag | Max Observed |
|---|---|---|---|
| Off-peak (00:00–06:00 UTC) | <50ms | <200ms | 500ms |
| Business hours (14:00–22:00 UTC) | <100ms | <500ms | 2,000ms |
| Batch window (02:00–04:00 UTC) | <200ms | <2,000ms | 5,000ms |
Your baseline will differ — the point is to establish what "normal" looks like for your workload before setting alarms.
Step 2: Create a Failover Readiness Dashboard
Build a CloudWatch dashboard that gives you at-a-glance readiness:
{ "widgets": [ { "type": "metric", "properties": { "title": "Failover Readiness — Replication Lag (ms)", "metrics": [ ["AWS/RDS", "AuroraGlobalDBReplicationLag", "DBClusterIdentifier", "my-global-cluster-secondary", {"stat": "Average", "label": "Avg Lag"}], ["...", {"stat": "Maximum", "label": "Max Lag"}], ["AWS/RDS", "AuroraGlobalDBProgressLag", "DBClusterIdentifier", "my-global-cluster-secondary", {"stat": "Average", "label": "Progress Lag"}], ["AWS/RDS", "AuroraReplicaLag", "DBClusterIdentifier", "my-global-cluster-secondary", {"stat": "Maximum", "label": "Instance Lag"}] ], "period": 60, "region": "us-east-2", "annotations": { "horizontal": [ {"label": "RPO Target (1s)", "value": 1000, "color": "#ff7f0e"}, {"label": "Critical (5s)", "value": 5000, "color": "#d62728"} ] } } }, { "type": "metric", "properties": { "title": "End-to-End Lag (ProgressLag + ReplicaLag)", "metrics": [ [{"expression": "m1 + m2", "label": "Total E2E Lag", "id": "e1"}], ["AWS/RDS", "AuroraGlobalDBProgressLag", "DBClusterIdentifier", "my-global-cluster-secondary", {"id": "m1", "visible": false}], ["AWS/RDS", "AuroraReplicaLag", "DBClusterIdentifier", "my-global-cluster-secondary", {"id": "m2", "visible": false}] ], "period": 60, "region": "us-east-2" } } ] }
Step 3: Define Readiness Thresholds
Map your business RPO requirements to lag thresholds:
| Readiness State | Lag Threshold | Meaning | Action |
|---|---|---|---|
| 🟢 READY | < 1,000ms (1s) | Failover will lose < 1 second of data | No action needed |
| 🟡 DEGRADED | 1,000ms – 5,000ms | Failover possible but RPO exceeds target | Investigate cause |
| 🔴 NOT READY | > 5,000ms (5s) | Failover would cause unacceptable data loss | Critical — resolve before DR is viable |
Step 4: Configure CloudWatch Alarms
Alarm 1: RPO Breach Warning (lag exceeds target)
aws cloudwatch put-metric-alarm \ --alarm-name "aurora-global-db-rpo-warning" \ --alarm-description "Replication lag exceeds 1s RPO target" \ --namespace AWS/RDS \ --metric-name AuroraGlobalDBReplicationLag \ --dimensions Name=DBClusterIdentifier,Value=my-global-cluster-secondary \ --statistic Maximum \ --period 60 \ --evaluation-periods 5 \ --threshold 1000 \ --comparison-operator GreaterThanThreshold \ --alarm-actions arn:aws:sns:us-east-2:111111111111:dr-readiness-alerts \ --treat-missing-data breaching \ --region us-east-2
Alarm 2: Critical — Failover Not Viable
aws cloudwatch put-metric-alarm \ --alarm-name "aurora-global-db-failover-not-viable" \ --alarm-description "CRITICAL: Replication lag >5s — failover would cause unacceptable data loss" \ --namespace AWS/RDS \ --metric-name AuroraGlobalDBReplicationLag \ --dimensions Name=DBClusterIdentifier,Value=my-global-cluster-secondary \ --statistic Maximum \ --period 60 \ --evaluation-periods 3 \ --threshold 5000 \ --comparison-operator GreaterThanThreshold \ --alarm-actions arn:aws:sns:us-east-2:111111111111:dr-critical-alerts \ --treat-missing-data breaching \ --region us-east-2
Alarm 3: Lag Trend — Anomaly Detection
Use CloudWatch Anomaly Detection to catch gradual degradation that static thresholds miss:
aws cloudwatch put-metric-alarm \ --alarm-name "aurora-global-db-lag-anomaly" \ --alarm-description "Replication lag anomaly detected — investigate degradation trend" \ --namespace AWS/RDS \ --metric-name AuroraGlobalDBReplicationLag \ --dimensions Name=DBClusterIdentifier,Value=my-global-cluster-secondary \ --evaluation-periods 10 \ --threshold-metric-id ad1 \ --comparison-operator GreaterThanUpperThreshold \ --metrics '[ {"id":"m1","metricStat":{"metric":{"namespace":"AWS/RDS","metricName":"AuroraGlobalDBReplicationLag","dimensions":[{"name":"DBClusterIdentifier","value":"my-global-cluster-secondary"}]},"period":300,"stat":"Average"},"returnData":true}, {"id":"ad1","expression":"ANOMALY_DETECTION_BAND(m1, 2)","returnData":true} ]' \ --alarm-actions arn:aws:sns:us-east-2:111111111111:dr-readiness-alerts \ --region us-east-2
Step 5: Automate Readiness Reporting
Create a Lambda function that queries lag metrics and publishes a daily readiness score:
import boto3 from datetime import datetime, timedelta cloudwatch = boto3.client('cloudwatch', region_name='us-east-2') def lambda_handler(event, context): end_time = datetime.utcnow() start_time = end_time - timedelta(hours=24) response = cloudwatch.get_metric_statistics( Namespace='AWS/RDS', MetricName='AuroraGlobalDBReplicationLag', Dimensions=[ {'Name': 'DBClusterIdentifier', 'Value': 'my-global-cluster-secondary'} ], StartTime=start_time, EndTime=end_time, Period=300, # 5-minute intervals Statistics=['Average', 'Maximum'], ) datapoints = response['Datapoints'] if not datapoints: return {'readiness': 'UNKNOWN', 'reason': 'No metric data available'} max_lag = max(dp['Maximum'] for dp in datapoints) avg_lag = sum(dp['Average'] for dp in datapoints) / len(datapoints) # Calculate percentage of time within RPO within_rpo = sum(1 for dp in datapoints if dp['Maximum'] < 1000) rpo_compliance = (within_rpo / len(datapoints)) * 100 # Readiness scoring if max_lag < 1000: readiness = 'READY' elif max_lag < 5000: readiness = 'DEGRADED' else: readiness = 'NOT_READY' report = { 'readiness_state': readiness, 'avg_lag_ms': round(avg_lag, 2), 'max_lag_ms': round(max_lag, 2), 'rpo_compliance_pct': round(rpo_compliance, 1), 'assessment_window': '24h', 'recommendation': get_recommendation(readiness, max_lag, rpo_compliance) } # Publish custom metric for tracking readiness over time cloudwatch.put_metric_data( Namespace='Custom/DRReadiness', MetricData=[ { 'MetricName': 'FailoverReadinessScore', 'Value': rpo_compliance, 'Unit': 'Percent', 'Timestamp': end_time }, { 'MetricName': 'MaxReplicationLag', 'Value': max_lag, 'Unit': 'Milliseconds', 'Timestamp': end_time } ] ) return report def get_recommendation(readiness, max_lag, compliance): if readiness == 'READY': return 'Failover viable at any time. RPO within target.' elif readiness == 'DEGRADED': return (f'Failover possible but {100-compliance:.1f}% of intervals exceed RPO. ' 'Investigate: heavy write workload, network latency between regions, ' 'or secondary cluster resource constraints.') else: return ('CRITICAL: Failover would cause unacceptable data loss. ' 'Check: primary write throughput exceeding replication bandwidth, ' 'secondary cluster CPU/memory pressure, cross-region network issues.')
Step 6: Common Causes of Lag Spikes and Remediation
| Symptom | Likely Cause | Fix |
|---|---|---|
| Lag spikes during batch windows | Large write bursts exceeding replication throughput | Throttle batch writes or schedule during low-traffic periods |
| Gradual lag increase over weeks | Growing dataset with same secondary instance size | Scale up secondary cluster instances |
| Sudden sustained lag | DDL operations (schema changes, index builds) | Schedule DDL during maintenance windows |
| Intermittent spikes >5s | Cross-region network congestion | Monitor NetworkTransmitThroughput; consider regions with lower inter-region latency |
| Lag after failover/switchover | Secondary promoted but old primary rejoining as new secondary | Allow time for catch-up; monitor AuroraGlobalDBProgressLag |
Step 7: Correlating Replication Lag with Downstream Analytics Pipeline Health
Aurora replication lag doesn't just affect your transactional database — if Aurora feeds downstream analytics via zero-ETL integration to Redshift, or CDC (Change Data Capture) to Kinesis Data Streams or MSK, the lag cascades through your entire data pipeline.
Monitor the Full Data Path
Track these metrics together to get true end-to-end data freshness:
| Layer | Metric | What It Tells You |
|---|---|---|
| Aurora replication | AuroraGlobalDBReplicationLag | DB-level lag between regions |
| Kinesis consumer | GetRecords.IteratorAgeMilliseconds | How far behind your streaming consumer is |
| MSK consumer | MaxOffsetLag (consumer group) | Kafka consumer lag in records |
| Redshift (zero-ETL) | IntegrationLatency | Delay from Aurora commit to Redshift availability |
If Aurora lag is 500ms but your Kinesis iterator age is 45 seconds, your analytics are 45 seconds stale — not 500ms. The weakest link determines your true RPO for analytics consumers.
Add Analytics Lag to Your Dashboard
{ "type": "metric", "properties": { "title": "Analytics Pipeline Lag — End-to-End Data Freshness", "metrics": [ ["AWS/RDS", "AuroraGlobalDBReplicationLag", "DBClusterIdentifier", "my-global-cluster-secondary", {"stat": "Maximum", "label": "Aurora Replication Lag"}], ["AWS/Kinesis", "GetRecords.IteratorAgeMilliseconds", "StreamName", "cdc-events-stream", {"stat": "Maximum", "label": "Kinesis Consumer Lag"}], ["AWS/Kafka", "MaxOffsetLag", "Consumer Group", "analytics-consumer", "Cluster Name", "my-msk-cluster", {"stat": "Maximum", "label": "MSK Consumer Lag"}] ], "period": 60, "region": "us-west-2" } }
Step 8: Stress-Test Failover Readiness with AWS Fault Injection Service (FIS)
Static monitoring tells you the current state — but how do you know your secondary region and analytics pipeline will actually recover under stress? Use AWS FIS to simulate failures and measure real recovery behavior.
Test 1: Inject Aurora Lag via Network Disruption
Use FIS to disrupt cross-region network connectivity and observe how replication lag responds:
# FIS experiment: disrupt network between regions to stress replication aws fis create-experiment-template \ --description "Stress Aurora Global DB replication by disrupting cross-region network" \ --targets '{ "subnet": { "resourceType": "aws:ec2:subnet", "resourceArns": ["arn:aws:ec2:us-west-2:111111111111:subnet/subnet-primary-db"], "selectionMode": "ALL" } }' \ --actions '{ "disruptNetwork": { "actionId": "aws:network:disrupt-connectivity", "parameters": {"scope": "all", "duration": "PT5M"}, "targets": {"Subnets": "subnet"} } }' \ --stop-conditions '[{"source": "none"}]' \ --role-arn arn:aws:iam::111111111111:role/FISRole
What to observe:
- How fast does
AuroraGlobalDBReplicationLagspike? - Does your readiness alarm fire correctly?
- After the 5-minute disruption ends, how long does lag take to recover to baseline?
Test 2: Validate Streaming Pipeline Recovery with Kinesis FIS Actions
If Aurora CDC feeds Kinesis Data Streams, use FIS native Kinesis actions to test your analytics consumer's resilience:
# FIS experiment: inject throttling on Kinesis stream to simulate backpressure aws fis create-experiment-template \ --description "Test analytics pipeline handling of Kinesis throttling during DR scenario" \ --targets '{ "kinesisStream": { "resourceType": "aws:kinesis:stream", "resourceArns": ["arn:aws:kinesis:us-east-2:111111111111:stream/cdc-events-stream"], "selectionMode": "ALL" } }' \ --actions '{ "injectThrottling": { "actionId": "aws:kinesis:inject-api-error", "parameters": { "errorCode": "ProvisionedThroughputExceededException", "percentage": "80", "duration": "PT10M" }, "targets": {"Streams": "kinesisStream"} } }' \ --stop-conditions '[{"source": "none"}]' \ --role-arn arn:aws:iam::111111111111:role/FISRole
What to validate:
- Does your consumer (Lambda, Glue Streaming, KDA) handle
ProvisionedThroughputExceededExceptionwith proper exponential backoff? - Do records land in a dead-letter queue or get silently dropped?
- After the fault clears, monitor
GetRecords.IteratorAgeMilliseconds— does it recover to baseline, or does the consumer fall permanently behind? - Run Athena queries against your downstream Redshift or S3 data lake to verify no data gaps exist post-recovery
Test 3: Combined Failover + Analytics Catch-Up Drill
Run a full end-to-end quarterly drill:
- Inject FIS network disruption → Aurora replication lag spikes → readiness alarm fires
- Trigger ARC Region Switch → secondary promoted, traffic shifts
- Validate analytics pipeline → Kinesis consumers resume from checkpoint in secondary region
- Run Athena validation → compare record counts pre/post failover
-- Post-failover validation: check for data gaps in your analytics layer SELECT DATE_TRUNC('minute', event_timestamp) AS minute, COUNT(*) AS record_count FROM analytics_lakehouse.cdc_events WHERE event_timestamp BETWEEN TIMESTAMP '2026-08-01 00:00:00' - INTERVAL '1' HOUR AND TIMESTAMP '2026-08-01 00:00:00' + INTERVAL '1' HOUR GROUP BY 1 ORDER BY 1; -- Look for gaps (minutes with 0 records) during the failover window
Step 9: Integrate with ARC Region Switch (Optional)
If you're using Amazon ARC Region Switch for automated failover, add a readiness gate — don't allow automated failover when lag exceeds your RPO:
- Create a CloudWatch alarm that stays in
OKstate only when lag < your RPO threshold - In your ARC Region Switch workflow, add a pre-check step that validates this alarm is in
OKstate - If the alarm is in
ALARMstate (lag too high), the workflow pauses — preventing a failover that would violate your RPO commitment
This ensures automated recovery only fires when the secondary is actually ready to serve without unacceptable data loss.
Key Takeaways
- Replication lag = your actual RPO. Your stated RPO is meaningless if replication lag routinely exceeds it during peak hours.
- Use end-to-end lag (
AuroraGlobalDBProgressLag+AuroraReplicaLag), not justAuroraGlobalDBReplicationLagalone. - Analytics lag compounds database lag. If Aurora feeds Kinesis/MSK/Redshift via CDC or zero-ETL, monitor the full pipeline — your analytics RPO is only as good as the slowest consumer.
- Anomaly detection catches drift that static thresholds miss — a gradual increase from 200ms to 800ms over 3 months won't trigger a 1,000ms alarm but signals degrading readiness.
- Measure compliance percentage, not just point-in-time values. "We're within RPO 99.2% of the time" is a more meaningful statement than "lag was 450ms just now."
- Use FIS to prove recovery works. Static lag monitoring tells you the current state; FIS experiments prove your failover actually recovers both the database AND downstream analytics pipelines.
- Gate your failover automation on readiness. An automated failover during a lag spike can be worse than no failover — you lose data AND shift traffic to an out-of-date secondary.
References
- Amazon CloudWatch metrics for Aurora — Global Database metrics
- Cross-Region resiliency for Global Database secondary clusters
- Monitor Aurora Global Database replication at scale using CloudWatch Metrics Insights
- Using switchover or failover in Aurora Global Database
- CloudWatch Anomaly Detection
- Perform resilience testing with AWS FIS — Kinesis Data Streams
- Amazon Kinesis Data Streams FIS actions for API errors
- What is AWS Fault Injection Service?
- Aurora zero-ETL integration with Amazon Redshift
- Language
- English
The command in step1 does not work, looks like 'p99' is not a valid value.
An error occurred (InvalidParameterValue) when calling the GetMetricStatistics operation: The parameter Statistics.member.3.<list element> must be a value in the set [SampleCount, Average, Sum, Minimum, Maximum].
replied 6 days ago
Relevant content
asked 4 years ago
- Accepted Answer
asked 3 years ago
AWS OFFICIALUpdated 2 years ago