Eventual Consistency: What It Actually Feels Like in Production
A user updates their profile photo. The profile page immediately shows the new photo - it reads from the primary database. The activity feed still shows the old photo - it reads from a read replica that’s two seconds behind. The user refreshes the feed. Still old photo. They open a support ticket. “Your product is broken.”
The lag was two seconds. Nothing was broken. But trust eroded faster than the propagation delay required.
This is eventual consistency in production: not a theoretical property, but a specific class of user-visible bug that appears whenever reads and writes don’t use the same data source.
What the Model Actually Says
The CAP theorem states that a distributed system can guarantee at most two of three properties: consistency (every read sees the most recent write), availability (every request gets a response), and partition tolerance (the system keeps working when network partitions occur). In any real distributed system, network partitions happen - a cable fails, a switch gets overloaded, a cloud availability zone has issues. So you’re choosing between consistency and availability.
Eventual consistency is the availability choice. You accept that different parts of the system may see different versions of the truth for a short window, in exchange for the ability to keep serving requests even when replicas are momentarily out of sync.
The “eventual” part is real: given enough time without new writes, all replicas converge to the same value. In well-configured systems, the window is milliseconds to a few seconds. In degraded conditions - network issues, replica lag - it can be longer.
The Three Bugs It Causes
Read-your-own-writes violation. The user writes something, then immediately reads it back - but the read hits a replica that hasn’t caught up. They see the old version. From their perspective, the update didn’t work.
This is the most common eventual consistency bug in production. The fix isn’t to make all reads consistent - it’s to make reads for the current user consistent for a short window after their writes.
One approach: track the replication position of the last write per user, and route reads to the primary (or a replica that’s caught up enough) for that user for the next few seconds:
def get_user_profile(user_id, requesting_user_id):
if requesting_user_id == user_id:
# User reading their own profile - might have just updated it
last_write_pos = cache.get(f'last_write:{user_id}')
if last_write_pos:
# Find a replica at least this far ahead, or use primary
return primary_db.get_user(user_id)
return replica_db.get_user(user_id)
A simpler version: just read from the primary for a fixed window (30 seconds) after any write from that user.
Causality violations. User A posts a message. User A sends User B a link to the message. User B opens the link before the message has replicated to their nearest replica. The message “exists” from User A’s perspective but returns 404 from User B’s.
This is harder to fix generically. Options: short retry on 404 for recently-created resources, or having User A’s link include a version hint that lets the server route to a consistent replica.
Conflicting concurrent writes. Two users update the same inventory count simultaneously. Both read 1 item remaining. Both write their purchase. The last writer wins - the count goes to 0 even though two purchases went through.
Eventual consistency doesn’t cause this bug on its own - it’s a missing lock or transaction at the write layer. But eventual consistency makes it more likely, because different parts of the system can read stale state that appears valid.
Where to Accept It and Where Not To
The mistake is applying the same consistency level everywhere. Not all data has the same consistency requirements.
Accepting eventual consistency is correct for:
- User profile data after writes (names, avatars, bio)
- Follower/like/view counts
- Search indexes
- Recommendation feeds
- Analytics dashboards
Slight staleness in these contexts is invisible or harmless to users.
Eventual consistency will cause correctness bugs in:
- Payment and financial operations
- Inventory that can go negative
- Access control (a revoked permission still appearing as granted)
- Any operation where the current read determines whether a write is valid
For these, you need to route through the primary or use a distributed transaction. The performance cost is real, but so is the correctness cost of getting it wrong.
Caches Make It Worse
A cache in front of an eventually consistent read replica means you have two sources of lag: replication lag and cache TTL. A user who updates their profile might not see the change for cache TTL + replication lag time.
When you add a cache, you’re not just accepting eventual consistency - you’re accepting a specific, configurable amount of it. Be deliberate about the TTL. “We can show data up to 60 seconds stale” is a product decision that should be made explicitly, not implicitly through cache configuration.
Cache invalidation on write is the right answer when staleness is unacceptable: when a user writes, invalidate or update the cache entry immediately. This couples the write and cache paths, but it’s better than users seeing stale data.
Designing the UI for It
The worst user experience is a UI that looks consistent but isn’t. The user updates their name, the save succeeds, and the page still shows the old name. They wonder if it saved. They hit save again.
Optimistic UI updates are the right pattern: update the UI immediately on write, before the server confirms, then reconcile when the server responds. If the server confirms, nothing changes. If the server rejects, revert.
// Update UI immediately, confirm asynchronously
function updateDisplayName(newName) {
// Immediate local update - user sees change right away
setDisplayName(newName);
api.patch('/profile', { display_name: newName })
.then(() => {
// Server confirmed - nothing to do
})
.catch(() => {
// Server rejected - revert
setDisplayName(previousName);
showError('Update failed');
});
}
If optimistic updates aren’t viable, the fallback is explicit acknowledgment: “Your changes have been saved and will appear shortly.” This sets the right expectation instead of making the user wonder if their write worked.
The rule: eventual consistency is a performance and availability optimization. It’s acceptable if users understand the experience. It’s a bug if users experience it as data loss or inconsistency without explanation.