Skip to main content
Caching Strategies

Caching Myths That Are Killing Your Hit Ratio (And What to Do)

Stop treating your cache like a database. Learn why cache-aside wins, why Redis isn't a storage tier, and how to set TTLs that don't wreck your CDN hit rate.

You've just deployed Redis, configured nginx, and signed up for a CDN. Everything is humming. Then you notice your database CPU is still high, your CDN hit ratio is stuck at 60%, and your "cache" is serving stale data that makes your users angry. You start tweaking eviction policies, adding more servers, and blaming the load balancer. Sound familiar?

Here's the blunt truth: most caching setups fail because of a few stubborn misconceptions. You're treating a cache like a database, ignoring HTTP headers, or overthinking algorithms that don't matter. Let's bust those myths and get you a cache that actually works.

Isn't a cache just a faster database?

The single biggest mistake I see is people using Redis or Memcached as if it were a durable store. It's not. Redis is an in-memory key-value cache — period. Typical hit rates are 90% to 95% with 1 to 5 ms latency, and adding it often reduces database CPU by 70% to 90% (AWS). Those numbers are great, but they only hold if you're caching, not storing. Redis offers persistence options like RDB snapshots and AOF logs, but the docs themselves say persistence can be disabled entirely when you're using it as a cache (Redis). That's a hint: don't put your only copy of critical data there. If you lose the cache, you should be able to rebuild it from the database. If you can't, you're not caching — you're storing, and you're doing it wrong.

Should I use cache-aside or write-through?

Cache-aside, also called lazy loading, is the most common pattern for a reason: check the cache first, and on a miss, fetch from the database and write the result back to the cache (AWS). It's simple, it works, and it's what you should start with. Write-through sounds appealing — update the cache on every write so it's never stale — but it doubles your write latency and fills your cache with data nobody reads. The blunt advice: use cache-aside. Let the cache be a reflection of what your users actually request, not a mirror of your entire database. You'll get a far better hit ratio with far less memory wasted on cold data.

Which eviction policy should I use in Redis?

If you're using Redis as a cache, the answer is almost always allkeys-lru. AWS explicitly recommends it because "a cache is not storage." Let me explain why. Redis offers a zoo of policies: noeviction, allkeys-lru, allkeys-lfu, volatile-lru, volatile-ttl, and so on (Redis). The volatile-* policies only evict keys that have an expiration set — which means if you forget to set TTLs on some keys, they'll never be evicted, and you'll hit noeviction errors when memory fills up. That's a silent killer. allkeys-lru treats all keys equally and evicts the least recently used ones, which is exactly what you want for a cache. Yes, allkeys-lfu (least frequently used) can give a better hit ratio in some cases (Redis), but LRU is simpler and plenty good for most workloads. Don't overthink it. Set maxmemory-policy allkeys-lru and move on.

Do I need sticky sessions for my load balancer?

If you're using caching, the answer is usually no — and sticky sessions can actually hurt. Sticky sessions, or session persistence, route a user to the same backend server every time. That's useful for in-memory sessions, but it destroys the ability of any cache in front of those servers to be shared. The system-design-primer is blunt: use round robin for short, stateless connections; use least connections or least response time for long connections; and use hashing or cookies only when you need session persistence. My advice: design your app to be stateless and let your load balancer spread traffic evenly. If you absolutely need sessions, store them in a shared cache like Redis, not in server memory. That way you can use round robin or least connections without fear, and you'll get far better utilization across your fleet.

Should I cache dynamic content at the CDN?

Yes — but only if you set the right headers. Many people think CDNs are only for static files like images and CSS. That's a myth. CDNs can cache dynamic HTML too, as long as you tell them how. The key is the Cache-Control header. If you set Cache-Control: max-age=604800, a response stays fresh for 7 days (MDN). That's fine for a static asset, but for a user-specific page you need private or no-cache. And if you have content that varies by user or device, use the Vary header to create separate cache keys (MDN). A concrete example: I once saw a news site that served the same homepage to everyone but didn't set Vary: Accept-Encoding. Their CDN stored separate copies for gzip and non-gzip requests, halving the hit ratio. Adding that one header fixed it. So don't shy away from caching dynamic content — just be deliberate about the headers. If you're worried about stale data, use stale-while-revalidate to serve stale content while fetching fresh in the background (RFC 5861).

Is a bigger TTL always better for CDN hit ratio?

No, and this is a classic trap. Longer TTLs do improve hit ratios, but they also increase the chance of serving stale content. The trick is to pick TTLs that match how often your content changes. For truly static assets like versioned CSS or JS, a long TTL like max-age=604800 (7 days) is perfect (MDN). For dynamic pages, you might want a TTL of 60 seconds or even less. And here's where CDN defaults can bite you: Cloudflare, for example, caches certain status codes for fixed durations when no cache-control headers are present — 200/206/301 for 120 minutes, 302/303 for 20 minutes, 404/410 for 3 minutes (Cloudflare). That means a 404 page could be cached for 3 minutes even if you thought it wasn't cacheable. If you don't set explicit Cache-Control, you're at the mercy of those defaults. So set TTLs deliberately, and use no-cache for anything that must always be revalidated. Remember, no-cache doesn't mean "don't store" — it means "store but revalidate before every use." That's a powerful tool for dynamic content.

Should I purge the entire CDN cache when I update a file?

No! Purge by single-file, not everything. When you deploy a new version of your site, you don't need to nuke your entire CDN cache. You just need to invalidate the specific URLs that changed. Cloudflare's purge by single-file instantly removes a cached resource across all data centers, so the next request for that URL fetches the latest version (Cloudflare). That's a surgical approach. Purging everything forces your CDN to fetch every asset from your origin again, which defeats the purpose of having a cache and can spike your origin load. Instead, use versioned filenames: styles.v2.css instead of styles.css. When the file changes, the new URL is a cache miss and gets fetched naturally. That way you never need to purge at all. And for API responses, use ETag or Last-Modified validators so clients can do conditional requests — the server only sends a 304 Not Modified if nothing changed, saving bandwidth (MDN).

How do I know if my cache is actually working?

Track your hit ratio, but don't obsess over it. A common myth is that a 100% hit ratio is the goal. It's not. If you're at 100%, you're probably caching too much — including data that changes too often or is user-specific. A healthy CDN hit ratio is usually 80% to 95% (AWS). If you're below that, look at your cache keys and TTLs. If you're above that, check if you're serving stale data. And for Redis, if your hit ratio drops, it might be an eviction problem — but if you're using allkeys-lru, that's less likely. Use the INFO command to see how many keys are being evicted. If you see a lot, your cache is too small or your TTLs are too long. The most important thing is to monitor what happens when the cache misses: if your origin can handle the load, you're fine. If not, you need a bigger cache or better TTLs.

Sources

  • AWS - https://aws.amazon.com/caching/
  • MDN (Cache-Control) - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
  • MDN (Vary) - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary
  • Cloudflare - https://developers.cloudflare.com/cache/concepts/default-cache-behavior/
  • Redis - https://redis.io/docs/latest/develop/reference/eviction/
  • RFC 5861 - https://www.rfc-editor.org/rfc/rfc5861.txt

Share this article:

Comments (0)

No comments yet. Be the first to comment!