Read After Write: When Your Own Update Vanishes on Refresh

April 4, 2026


The write succeeded. The next read lied.

This is the single most common surprise in any system with read replicas. The user posts an update. The primary commits it and returns 200. The client immediately refreshes. That refresh lands on a follower that has not received the change yet. The user sees the old value.

From the user's perspective it looks like the write failed. From the database's perspective it did not. The replica was simply behind.

Two consistency properties matter here. Read-your-writes says a client must see the effect of writes it issued itself. Monotonic reads says successive reads from the same client must never go backwards in time. Most apps need both, even if the README never names them.

There are three practical ways to enforce read-your-writes:

  1. Route reads to the primary for a short window after each write. The client tracks "I just wrote at T" and sends reads to the leader until T plus the worst-case lag.
  2. Carry a causal token. The write returns an LSN, version, or timestamp. The client sends it on the next read, and the router waits for a replica that has caught up to that point.
  3. Pin the session to a specific replica that the client has already observed at a known position. Cheap when sessions are short, painful when they roam.

The production failure I have actually seen: a profile-edit screen. User submits a new avatar in eu-west-1 where the write region lives. The mobile app is sticky to us-east-1 reads because the CDN edge routed there. Refresh shows the old avatar. User assumes the submit broke, hits save again with a different file. Replication finally catches up and the upsert path lands two rows in profile_versions with overlapping valid_from ranges. Now the profile API has to pick one, and on a bad day it picks the older write because LWW on the modified timestamp went the wrong way across NTP skew.

The deeper bug was not lag. It was a mobile client pinned to one region while the write went to another, with no causal token tying the two together. Lag inside a single region was 50 ms. Lag across regions, depending on the link, was several seconds. The app treated both topologies as if they were the same.

There is a small UX trick worth knowing. While the causal token is still in flight, show a "saving" or "syncing" state on the affected widget instead of optimistically painting the new value as confirmed. The user reads ambiguity correctly and does not retry.

If you use replicas, name the consistency you need. Then pick the route, the token, or the pin that makes it true. Anything else is a UI that occasionally gaslights your users.

Key takeaway

Read replicas improve scale, not freshness. If a user can read their own write, you have to route, pin, or wait. Otherwise the UI lies and the user retries.

Originally posted on LinkedIn. View original.


All Rights Reserved.