Skip to content

How Do I Analyze Aurora Global Database Replication Lag to Predict Failover Readiness Across Transactional and Analytics Workloads?

12 minute read
Content level: Advanced
0

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):

MetricWhat It MeasuresUnit
AuroraGlobalDBReplicationLagHow far the secondary cluster's storage volume is behind the primary — for both user and system transactionsMilliseconds
AuroraGlobalDBProgressLagLag between primary and secondary cluster volumesMilliseconds
AuroraReplicaLagLag 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 WindowTypical p50 LagTypical p99 LagMax Observed
Off-peak (00:00–06:00 UTC)<50ms<200ms500ms
Business hours (14:00–22:00 UTC)<100ms<500ms2,000ms
Batch window (02:00–04:00 UTC)<200ms<2,000ms5,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 StateLag ThresholdMeaningAction
🟢 READY< 1,000ms (1s)Failover will lose < 1 second of dataNo action needed
🟡 DEGRADED1,000ms – 5,000msFailover possible but RPO exceeds targetInvestigate cause
🔴 NOT READY> 5,000ms (5s)Failover would cause unacceptable data lossCritical — 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

SymptomLikely CauseFix
Lag spikes during batch windowsLarge write bursts exceeding replication throughputThrottle batch writes or schedule during low-traffic periods
Gradual lag increase over weeksGrowing dataset with same secondary instance sizeScale up secondary cluster instances
Sudden sustained lagDDL operations (schema changes, index builds)Schedule DDL during maintenance windows
Intermittent spikes >5sCross-region network congestionMonitor NetworkTransmitThroughput; consider regions with lower inter-region latency
Lag after failover/switchoverSecondary promoted but old primary rejoining as new secondaryAllow 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:

LayerMetricWhat It Tells You
Aurora replicationAuroraGlobalDBReplicationLagDB-level lag between regions
Kinesis consumerGetRecords.IteratorAgeMillisecondsHow far behind your streaming consumer is
MSK consumerMaxOffsetLag (consumer group)Kafka consumer lag in records
Redshift (zero-ETL)IntegrationLatencyDelay 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 AuroraGlobalDBReplicationLag spike?
  • 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 ProvisionedThroughputExceededException with 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:

  1. Inject FIS network disruption → Aurora replication lag spikes → readiness alarm fires
  2. Trigger ARC Region Switch → secondary promoted, traffic shifts
  3. Validate analytics pipeline → Kinesis consumers resume from checkpoint in secondary region
  4. 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:

  1. Create a CloudWatch alarm that stays in OK state only when lag < your RPO threshold
  2. In your ARC Region Switch workflow, add a pre-check step that validates this alarm is in OK state
  3. If the alarm is in ALARM state (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

  1. Replication lag = your actual RPO. Your stated RPO is meaningless if replication lag routinely exceeds it during peak hours.
  2. Use end-to-end lag (AuroraGlobalDBProgressLag + AuroraReplicaLag), not just AuroraGlobalDBReplicationLag alone.
  3. 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.
  4. 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.
  5. 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."
  6. 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.
  7. 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

1 Comment

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 3 days ago