Building a Read-Replica Lag-Aware Routing Proxy for Aurora MySQL with Lambda and PI

User avatar placeholder
Written by Tamzid Ahmed

June 20, 2026

Designing a Read-Replica Lag-Aware Routing Proxy

When scaling read-heavy workloads on Amazon Aurora MySQL, replication lag can silently degrade user experience by serving stale data. A read-replica lag-aware routing proxy dynamically directs queries to the replica with the lowest delay, balancing load while preserving consistency. This guide shows how to build such a proxy using AWS Lambda and RDS Performance Insights.

Why Lag Matters in Aurora

Amazon Aurora replicates data from the writer instance to up to 15 readable replicas using a shared storage layer. Although the storage is synchronous, the apply lag on each replica can vary due to network pressure, CPU contention, or long-running transactions. Monitoring this lag is essential because reads served from a stale replica may return outdated information, violating application SLAs.

Traditional round‑robin or least‑connection routers ignore lag, which can cause a burst of stale reads during a spike. By contrast, a lag‑aware router evaluates near‑real‑time delay metrics and prefers replicas whose apply lag falls below a defined threshold.

Architecture of the Proxy

The proxy sits between the application and the Aurora cluster endpoint. For each incoming read request, it:

  1. Retrieves the latest lag metrics for every replica.
  2. Filters out replicas exceeding the lag threshold.
  3. Selects the replica with the smallest lag (or lowest load if ties).
  4. Forwards the query to the chosen replica and returns the result.

To keep latency low, the proxy caches lag metrics for a short window (e.g., 10 seconds) and refreshes them via a background process.

Implementing the Proxy with AWS Lambda

AWS Lambda provides a lightweight, scalable compute layer that can be invoked via Amazon API Gateway or an Application Load Balancer. The Lambda function implements the routing logic described above.

Lambda Function Code Skeleton (Python 3.11)


import os, json, boto3, time

rds = boto3.client('rds')
LagThresholdMs = int(os.getenv('LAG_THRESHOLD_MS', '500'))
CacheTTL = int(os.getenv('METRIC_CACHE_TTL', '10'))

def get_lag_metrics():
    # fetch replica lag from Performance Insights
    # (implementation in next section)
    pass

def lambda_handler(event, context):
    lag_data = get_lag_metrics()
    eligible = {rid: lag for rid, lag in lag_data.items() if lag <= LagThresholdMs}
    if not eligible:
        # fallback to writer or raise error
        target = os.getenv('WRITER_ENDPOINT')
    else:
        target = min(eligible, key=eligible.get)  # replica with min lag
    # forward request (simplified)
    return {
        'statusCode': 200,
        'body': json.dumps({'target_endpoint': target})
    }

The function reads environment variables for the lag threshold and cache TTL, calls a helper to retrieve lag metrics, filters replicas, and returns the endpoint with the smallest lag.

Leveraging RDS Performance Insights for Lag Metrics

Amazon RDS Performance Insights exposes the metric db.replica_lag for each Aurora replica. By querying the PI API, the Lambda can obtain sub‑second lag values.

Fetching Lag via the PI API


pi = boto3.client('pi')
def get_lag_metrics():
    now = int(time.time())
    start = now - CacheTTL
    resp = pi.get_resource_metrics(
        ServiceType='RDS',
        Identifier=os.getenv('CLUSTER_RESOURCE_ID'),
        MetricQueries=[
            {
                'Metric': 'db.replica_lag.max',
                'GroupBy': [{'Group': 'DB_INSTANCE'}]
            }
        ],
        StartTime=start,
        EndTime=now,
        PeriodInSeconds=CacheTTL
    )
    metrics = {}
    for result in resp['MetricList']:
        for key, values in result['DataPoints'].items():
            # values is a list of [timestamp, value]
            lag_ms = values[-1][1] * 1000  # convert seconds to ms
            metrics[key] = lag_ms
    return metrics

The helper caches the result for the duration of CacheTTL to reduce API calls and latency.

Tradeoffs and Operational Considerations

  • Consistency vs. Freshness: Setting a low lag threshold improves data freshness but may increase load on fewer replicas, risking overload.
  • Lambda Cold Starts: Keep the function warm with provisioned concurrency or schedule a periodic ping to maintain sub‑50 ms response times.
  • Error Handling: If all replicas exceed the threshold, fallback to the writer instance or return a 503 with a retry‑after header.
  • Monitoring: Export Lambda invocation duration, lag metric age, and fallback rate to CloudWatch Alarms.

Conclusion

Building a read-replica lag-aware routing proxy for MySQL on Amazon Aurora using AWS Lambda and RDS Performance Insights lets you serve fresher reads without sacrificing scalability. By continuously measuring db.replica_lag and routing to the replica with the smallest delay, you balance load and consistency effectively. Start small: deploy a Lambda with the code above, set a 500 ms lag threshold, and monitor the fallback rate before tightening the SLA.

Leave a Comment