How to Route Reads Between Primary and Replicas After a Write
A user changes their profile name from Alex to Alexander.
The primary database immediately has Alexander.
But the next request is routed to a read replica that is still showing Alex.
Routing that read to the primary solves the immediate problem.
But production systems have a second problem:
How does the application know when it can safely stop using the primary and return that read to a replica?
The practical answer requires more than a timer.
It requires a way to connect the successful write with the replication progress of the replica.
The Basic Flow
A simplified architecture looks like this:
Application
↓
Write → Primary
Read → Router → Replica / Primary
After a write, the application needs to establish a consistency requirement for subsequent reads.
Conceptually:
Write succeeds
↓
Capture a consistency point
↓
Carry that point to the read path
↓
Check replica progress
↓
Replica has reached the required point?
Yes → Read from replica
No → Read from primary
The exact implementation of the consistency point depends on the database and replication mechanism.
This is the part that cannot safely be generalized across all databases.
Step 1: The Write Produces a Consistency Point
Suppose a database's replication mechanism exposes some form of monotonically advancing position.
For illustration, imagine the primary commits a user's profile update at position:
5000
The application now has a useful fact:
The read must not be served from a replica that has not reached position 5000.
This is more useful than simply saying:
“The user wrote something recently.”
A time-based statement does not tell the application whether a particular replica has actually applied the write.
A replication position can, where supported by the database, provide a more meaningful condition.
The actual marker could be a replication log position, transaction identifier, commit sequence, or another database-specific value.
The terminology and semantics differ between database technologies.
Step 2: Carry the Consistency Requirement Forward
The read request needs access to the consistency requirement.
There are several ways an application can accomplish this.
For example, the value can be associated with:
• A server-side session
• Request context
• A signed client token
• A cookie
• Application-level state
• A service-to-service request context
The choice depends on the application architecture and security model.
The important concept is that the read path needs to know:
“This user has a recent write, and reads must not go behind this point.”
For a single request immediately following the write, the consistency marker can remain in request context.
For subsequent independent HTTP requests, some form of state needs to cross the request boundary if the application promises the same behavior across those requests.
Step 3: The Read Router Evaluates the Request
Now suppose the user requests their profile.
The read router receives:
Required consistency point: 5000
It has several replicas available.
For example:
Node Replication Progress
Replica A 4982
Replica B 5000
Replica C 4960
Replica A is behind.
Replica C is behind.
Replica B has reached the required point.
If the database's consistency semantics allow this comparison, Replica B can satisfy the requirement.
The router can therefore select Replica B.
There is no need to send the read to the primary simply because a write occurred.
The important practical distinction:
A recent write does not automatically mean primary routing is required forever.
The routing decision depends on whether an eligible replica can satisfy the required consistency condition.
Step 4: What If Every Replica Is Behind?
Suppose the replicas now look like this:
Node Replication Progress
Replica A 4982
Replica B 4990
Replica C 4975
The required position is still 5000.
No replica currently satisfies the requirement.
If the application requires read-after-write behavior, the router can send the request to the primary.
This provides a simple fallback:
Replica cannot satisfy freshness requirement → Primary
The application should also have a defined behavior if the primary is unavailable.
That behavior depends on the application's consistency requirements.
Some applications may prefer an error rather than returning known-stale data. Others may explicitly permit degraded consistency.
There is no universal answer.
Step 5: When Does the Application Switch Back?
This is the key part.
The application does not need to wait for an arbitrary amount of time if it has a reliable database-specific signal.
The condition can be:
Replica progress >= required consistency point
Once that condition is satisfied, the replica becomes eligible for the read.
For example:
Required: 5000
Replica A: 4982 → Not eligible
Later:
Replica A: 4997 → Not eligible
Later:
Replica A: 5000 → Eligible
At this point, the read can return to replica routing.
This is why replication-aware routing is more precise than:
“After five seconds, use a replica.”
Why a Fixed Timeout Is Not the Same Thing
Consider a system that uses this rule:
After a write → primary for 5 seconds → replica
It appears reasonable, but it does not actually measure replication progress.
During normal operation:
Replication lag = 10 ms
The five-second primary window creates unnecessary primary traffic.
During an incident:
Replication lag = 15 seconds
The five-second window expires while the replica is still behind.
The application can then return stale data.
The timeout is therefore only a heuristic.
It can still be useful as part of a broader design, but it should not automatically be treated as proof that a replica has caught up.
Where Should This Logic Be Implemented?
There is no single mandatory location.
Application Data-Access Layer
The application can expose an abstraction such as:
readNormally()
and
readWithConsistencyRequirement(marker)
The data-access layer then decides which database node is eligible.
This gives the application direct control over consistency requirements.
Database Proxy
A database proxy can perform some routing and health-based decisions.
However, a generic proxy that only knows:
writes → primary
reads → replicas
does not automatically understand that a particular user's read must observe a previous write.
The consistency requirement must still be represented and supported by the routing mechanism.
Middleware
Request middleware can attach the consistency marker to the request context before the request reaches the data-access layer.
This can be useful when multiple endpoints need the same behavior.
The architectural goal is to avoid scattering database-node decisions throughout business logic.
What About Sticky Reads?
Another approach is sticky reads.
After a write, the application can temporarily associate the user or session with a particular database node.
This can simplify routing, but there is an important limitation:
Sticky does not automatically mean fresh.
If the selected replica is behind, keeping the user on that replica can continue returning stale data.
Sticky routing is therefore an implementation technique, not by itself a complete read-after-write guarantee.
If strong freshness is required, the application still needs a way to know whether the selected node has reached the required state.
One Common Mistake
Replica health ≠ Replica freshness
A replica can be healthy and available while still being behind the specific write the user just made.
The Trade-off
If replicas stay behind for too long, routing reads to the primary can increase its load.
Production systems therefore also need safeguards for:
Replica lag
Primary load
Timeouts
Failover
Degraded consistency
Takeaway
The real flow is:
Write → Capture consistency point → Carry it forward → Check replica progress → Primary if behind → Replica when caught up
That's how read-after-write consistency becomes an actual routing mechanism.
How would you implement this in a production database? Share your technical perspective below. 👇
