Caching Does Not Always Make an Application Faster

Caching is not a turbo button. Bad invalidation, stale data, and cache stampedes can make a system slower and less trustworthy.

· · 8 min read

I used to treat caching like a turbo button. Slow query? Add Redis. Busy endpoint? Cache the response. The dashboard felt faster until someone saw old data and asked the uncomfortable question: “Is this number correct, or merely quick?”

A cache is a copy stored closer to the reader. The application is betting that the copy is still trustworthy.

A cache hit is not the whole story

A 95% hit rate sounds impressive. If the remaining five percent contains the heaviest queries and arrives at the same time, the database still suffers. Redis also has serialization, network, and connection costs.

Measure the actual bottleneck first: query time, network time, rendering time, and the acceptable staleness window. If a query takes 20 ms and the cache path takes 15 ms while adding invalidation complexity, that is not an optimization. It is technical debt with a fashionable name.

Invalidation is the difficult part

Writing set(key, value, 60) is easy. Knowing when to remove the key after a profile, article, or price changes is the real design problem.

Use a simple TTL for data that may be stale. For important data, use events and versioned keys. For popular keys, protect the origin with stale-while-revalidate or a short lock to prevent a cache stampede.

My rule is simple: caching is a consistency decision before it is a performance decision. Add it when the data, bottleneck, and staleness budget are measurable—not because every backend tutorial mentions Redis.

---

When caching makes things slower

Three patterns appear repeatedly. First, a cache is placed in front of a slow query whose real problem is a missing or incorrect index. The application looks fine at low traffic, but a cache miss makes many requests wait for the same query. Fix the query and measure again before adding another layer.

Second, a cache is used for data that must be consistent: balances, payment status, or permissions. A TTL alone is not enough. A user can see an old balance while a transaction is being processed. Cache non-critical data, or use a read-after-write strategy so readers see the new value after a change.

Third, cache keys have no clear owner. One service writes them, another deletes them, and nobody owns the naming contract. Months later, the team is afraid to change the schema because an old key may still be read.

I usually start with the simplest cache: HTTP caching for public responses, a properly indexed query, and a short TTL for data that may be stale. Only then do I decide whether Redis is justified. A small cache that can be explained is healthier than a cache in every layer.

Before merging, answer: what is the source of truth, how stale may the data be, who invalidates the key, what happens when the cache is down, and which hit/miss/latency metrics exist? If the application dies when the cache dies, the cache is no longer an optimization.

Sources

• https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching
• https://aws.amazon.com/caching/
• https://martinfowler.com/bliki/TwoHardThings.html