Stop Routing Reads to Primary: Building Causal Consistency in Java with Postgres LSN Tokens
Falling back to your primary database for reads right after a mutation is a lazy architectural crutch that kills your write capacity at scale. In 2026, if you aren't using causal session tokens across your edge and read replicas, you're needlessly setting money on fire with oversized primary instances.
Want to go deeper? javalld.com — machine coding interview problems with working Java code and full execution traces.
Why Most Developers Get This Wrong
- Arbitrary TTL pinning: Routing all session reads to the primary for "5 seconds after a write" blindsides your master node during traffic spikes and wastes up to 40% of replica capacity.
-
Ignoring database watermarks: Engineers treat replication lag as an unpredictable mystery instead of querying exact write-ahead positions (
pg_current_wal_lsn()in Postgres or GTID in MySQL). - Over-engineering with distributed locks: Reaching for heavy coordination layers like Raft or Redis locks when monotonic causal consistency (Read-Your-Own-Writes) is all the end-user requires.
The Right Way
Capture the commit log sequence number on writes, return it to the client as an HTTP response header, and enforce replica watermarks on subsequent reads.
- Extract the engine's log sequence number (e.g., Postgres LSN) immediately within your mutation transaction.
- Echo the token back to the client via an HTTP header (e.g.,
X-Causal-Token: 0/16B3748). - Pass the token back in subsequent
GETrequests and route to a replica pool managed by a SpringAbstractRoutingDataSource. - Check replica state against the token using a low-latency cached watermark; fall back to the primary only when lag hedging bounds (e.g., 25ms) are exceeded.
Show Me The Code
public class CausalReadDataSourceRouter extends AbstractRoutingDataSource {
private final ReplicaWatermarkTracker watermarkTracker;
@Override
protected Object determineCurrentLookupKey() {
String requiredLsn = CausalContextHolder.getRequiredLsn();
if (requiredLsn == null) {
return DataSourceType.REPLICA; // Cold reads bypass lag verification
}
// Fast in-memory check against background-polled pg_last_wal_replay_lsn()
boolean replicaCaughtUp = watermarkTracker.isReplayed(requiredLsn);
return replicaCaughtUp ? DataSourceType.REPLICA : DataSourceType.PRIMARY;
}
}
Key Takeaways
- Replicas are for reads: Pinning entire user sessions to the primary defeats the purpose of horizontal scaling.
- LSN tokens enable true causal consistency: The client tracks their own timeline without imposing global linearizability bottlenecks on your persistence tier.
- Hedging protects latency: Always implement a fast fallback threshold so replication spikes degrade to primary reads rather than surfacing connection timeouts.
Top comments (0)