Skip to main content
Cache Write Strategieslesson 4 of 5 · 2 min read

Cache Consistency

The window where both are wrong

Update the database, delete the key, and for a moment in between every reader gets the old answer.

Measure that moment honestly. It is usually a millisecond or two, it is never zero, and no ordering of those two operations makes it zero.

Decide who is allowed to see it, because the answer differs by reader. A stranger seeing a price two milliseconds out of date has seen nothing wrong.

The person who just changed that price and is staring at the confirmation screen has, and they will report it as a bug. That is reading your own writes, and it is the one consistency property people notice directly.

Handle that case by not caching for the writer. After somebody updates their own profile, serve them from the database for a few seconds. Or write their new value into the cache instead of deleting it, so their next read finds it.

Leave everybody else on the cheap path.

Two caches, not one

Watch out for the two-cache version of this problem. Once you have a browser cache in front of a shared cache in front of your database, an invalidation has to reach all three. The one you cannot reach is the browser.

Read a short expiry on the client as something other than a performance decision. It is a bound on how long your invalidations are allowed to be wrong.

Accept the window where you can, because the alternatives cost more than people expect. Making a cache strictly consistent with its database means every write blocks on the cache and every failure becomes a distributed transaction problem. Your cache stops being the thing that made reads cheap.

Almost every system appearing to solve this has instead chosen data where a few milliseconds of staleness simply does not matter.

the shape of it
Writersets price 24,999Database24,999Cachestill 27,499Readersees the old onecommittedduring the gapdelete, moments on
step 1 of 3
The window between the commit and the delete is small, real, and impossible to remove entirely.
letting the writer see their own change
Java
void updateProfile(long userId, Profile p) {
  db.save(p);
  redis.del("profile:" + userId);

  // The person who just made the change is the one reader who
  // will notice the gap, so take them off the cached path for
  // a few seconds rather than trying to close it.
  redis.setex("skipcache:" + userId, 5, "1");
}

Profile read(long userId, long viewerId) {
  boolean isAuthor = viewerId == userId
      && redis.get("skipcache:" + userId) != null;

  if (isAuthor) return db.load(userId);   // fresh, for them only
  return cached(userId);                  // cheap, for everyone else
}

Worked example

Arjun's support queue kept receiving the same complaint from sellers: they updated a product, saw the old price on the page, updated it again, and sometimes ended up with the wrong one saved twice.

The gap was about 200 milliseconds, which was long enough because a seller clicks straight from save to view. Nobody else in the world would have noticed it.

The five second skip flag ended the complaints entirely. Sellers see their own change immediately, buyers keep getting cached pages, and the cache hit rate moved from 96 percent to 95.8, which is the whole cost of the fix.