Database replication lag becomes visible to customers when an application accepts a write and then reads from a replica that has not replayed it yet. The database may be healthy, the write may be durable, and the interface may still show the old value.
That mismatch is more than a monitoring detail. It can make a saved address appear unsaved, a payment status seem to move backward or a newly created record vanish until the next refresh. The same product discipline applies to API pagination across moving datasets: carry a version boundary instead of assuming every read sees one instant.
Source review: Amazon Aurora and Amazon RDS documentation, plus PostgreSQL 16 monitoring documentation, were reviewed on October 30, 2026. Product-specific metrics and behavior are identified below; the design pattern applies more broadly to asynchronously replicated systems.
Start with the customer promise
A replica does not need to be perfectly current for every read. A product does need to decide where stale data is acceptable.
The most important question is not “How many milliseconds behind is the replica?” It is “Which user action must be followed by a read that includes that action?”
Examples include:
- updating a delivery address and immediately reopening the profile;
- creating an invoice and landing on its detail page;
- changing an access rule and checking the permissions screen;
- initiating a payment and viewing its status;
- cancelling a job and returning to the queue.
In each case, the interface has made a promise. It accepted an action and now needs to show a state consistent with that acceptance.
Why the old value appears
A typical read-scaling design sends writes to a primary database and some reads to replicas. The primary commits a transaction and returns success. Replication then transfers and replays the change on each replica.
With asynchronous replication, those steps do not complete at the same instant.
Amazon’s Aurora PostgreSQL write-forwarding documentation says read replicas receive updates asynchronously and that lag can differ between replicas. It also states that reads requiring read-after-write consistency must run on the writer unless the application uses a mechanism that provides that consistency.
A load balancer can make the symptom look random. One refresh reaches a caught-up replica and shows the new value. The next reaches a slower replica and shows the old one. The customer sees time moving backward even though replication is moving forward independently on each node.
Define the consistency boundary
“Eventually consistent” is too broad for a product requirement. Write a rule for each interaction.
| Interaction | Acceptable read behavior |
|---|---|
| Public product list | A short delay may be acceptable |
| Account setting after save | The saving user should see the new value |
| Payment confirmation | The status must not regress in the same journey |
| Analytics dashboard | A stated freshness delay may be acceptable |
| Authorization change | Security-sensitive reads should use an authoritative path |
| Newly created object | The creator should be able to open it immediately |
The boundary can be scoped to one session, one object or one workflow. It does not require every read in the application to use the primary.
A useful contract says how long the guarantee lasts. For example: after a successful profile update, reads of that profile in the same session use an authoritative source for 30 seconds or until a replica confirms the commit position.
Route the critical read deliberately
The simplest read-after-write strategy is to send the immediate follow-up read to the primary.
That works well when:
- the write path already knows which object changed;
- only a small share of total reads follow writes;
- the primary has capacity for those reads;
- the consistency window is short.
Do not route the whole application to the primary because one screen needs fresh data. Carry a bounded marker through the request path: the object identifier, a recent-write flag or the commit position returned by the database.
Amazon Aurora’s local write forwarding is a product-specific alternative. It can forward writes from a replica connection to the writer and offers session consistency controls. That does not remove the need to define the product boundary. It gives an application another mechanism for implementing it.
Wait for a known replication position
Time-based delays are easy to add and hard to trust. Sleeping for 200 milliseconds assumes the replica will always catch up within 200 milliseconds.
A stronger pattern uses a commit position, log sequence number or equivalent token:
- the write commits on the primary;
- the application records the commit position;
- the follow-up read chooses a replica only after that replica has replayed at least that position;
- otherwise it waits within a budget or falls back to the primary.
This turns “probably fresh” into a testable condition.
The exact token and wait operation depend on the database and managed service. Do not invent a portable abstraction that hides whether the replica can actually prove its replay position.
Show an acknowledged state while the read model catches up
Some systems intentionally separate the write model from the read model. A command may succeed before a search index, cache or materialized view is updated.
The interface can preserve continuity by keeping the acknowledged result from the write response:
- add the new item optimistically with a pending marker;
- show the saved value from the response;
- keep the prior screen state and attach the confirmed change;
- prevent a background refresh from overwriting newer client state with an older snapshot.
This is not permission to fabricate success. The write response must carry an authoritative outcome. If the write is still pending, say so. If it failed, remove the optimistic state and show the failure.
Include a version or timestamp with the response when possible. The client can then reject a read model response that is older than the version it already holds.
Do not hide lag with a cache
A cache can create the same symptom as a replica, or make it last longer.
After a write, check every layer that can serve the old value:
- application memory;
- distributed cache;
- database replica;
- search index;
- API gateway or CDN;
- browser cache;
- client state.
Invalidating a cache does not guarantee that the next read reaches a caught-up replica. Updating the cache directly after a write can help, but only when the update order, failure handling and version checks are explicit.
Treat the customer-visible path as one consistency chain. Measuring only the database misses stale data served later in the chain.
Measure lag in more than seconds
Amazon RDS exposes a ReplicaLag metric. Its meaning depends on the database engine. For PostgreSQL, RDS calculates reader lag using the time since the last replayed transaction. For MySQL and MariaDB, the metric follows the engine’s replica-status value. RDS also notes that some unavailable or inactive states use a sentinel value rather than an ordinary lag.
PostgreSQL’s pg_stat_replication documentation provides write, flush and replay lag measurements. It warns that these values measure recent commit visibility delays; they are not a prediction of how long a standby will take to catch up. On an idle, caught-up system, lag columns can keep a recent value briefly and then become null.
That distinction matters for alerts. A single “lag seconds” chart can confuse four different conditions:
- replication is caught up;
- replication is idle and the metric is null;
- replication is delayed;
- replication is stopped or indeterminate.
Monitor the state as well as the number.
Connect infrastructure metrics to product symptoms
A useful dashboard pairs replication health with interface evidence:
| Infrastructure signal | Product signal |
|---|---|
| Replay lag by replica | Read-after-write mismatch rate |
| WAL or log backlog | Customer refresh loops |
| Replica error state | Reads routed to fallback |
| Primary read fallback rate | Latency and primary capacity |
| Cache age | Stale object responses |
| Commit-position wait time | Time until the object becomes readable |
Instrument the application so a response records which data source served it and which data version it returned. The goal is not to expose internal server names to customers. It is to let an operator reconstruct why a particular request saw an old value.
Design the failure path
A consistency strategy needs a budget.
If a replica has not reached the required position, the application can:
- wait briefly;
- read from the primary;
- return the acknowledged write response;
- show an explicit pending state;
- fail with a retryable error when correctness matters more than availability.
Choose the order per workflow. An analytics screen may accept stale data with a freshness label. An authorization check should not quietly use an old permission set. A payment screen should distinguish “accepted for processing” from “settled” instead of inventing a final state.
Put a circuit breaker around primary fallback. If every read moves to the primary during an incident, the recovery path can overload the writer and turn lag into a wider outage.
Test the experience under lag
A unit test with one database connection will not reveal this problem. Add tests that deliberately delay replica visibility.
Useful cases include:
- save, redirect and immediate read;
- two quick writes to the same object;
- a refresh routed to different replicas;
- a background poll returning an older version;
- cache invalidation before replica replay;
- replica unavailable during primary fallback;
- retry after the interface already showed success.
The assertion should describe the user-visible rule. “The replica caught up in 500 ms” is an implementation observation. “The user never sees the address revert after save” is the product invariant.
A practical decision sequence
For each write path:
- name the object or workflow whose state changed;
- decide whether immediate fresh reading is required;
- define how long that guarantee lasts;
- choose primary routing, a replay-position check or acknowledged client state;
- set a wait and fallback budget;
- instrument the source and version of every response;
- test delayed and failed replication;
- monitor the customer-visible mismatch.
This keeps replication architecture tied to a real promise. It also prevents teams from paying for strong consistency on reads that do not need it.





