Everyone loves to tune Redis. I get it—the eviction policies are seductive, and the promise of a 95% hit rate is irresistible. But here's the contrarian truth: you're probably polishing the wrong cache. Before you dive into allkeys-lru vs. volatile-ttl, step back and look at your HTTP cache headers. That's where the real performance gains are hiding.
The Cache You're Ignoring Is the One That Matters
Think about the layers in a typical architecture: browser cache, CDN edge, load balancer, application cache, distributed cache, and finally the database (AWS). Most of us obsess over the distributed cache—Redis, Memcached—because it's tangible and we control it. But the browser cache and the CDN edge sit far closer to the user. A single correct Cache-Control header can keep a response out of your origin entirely, saving you the round trip and the Redis lookup. That's not just a nice-to-have; it's the difference between a 50 ms response and a 5 ms one.
Yet so many teams treat HTTP caching as an afterthought. They set max-age=60 on everything and call it a day. That's lazy, and it's costing you.
Your Cache-Control Header Is a Load Balancer
Here's a perspective shift: Cache-Control is not just a performance nicety; it's a load-balancing mechanism. When you let a CDN or a browser serve a cached copy, you're effectively routing traffic away from your origin servers. That's the cheapest load balancing you'll ever buy. The CDN can absorb 70% or more of your origin requests (AWS), and typical hit ratios run 80% to 95% (AWS). That's not a rounding error—that's a whole fleet of servers you don't need to provision.
So why do we ignore it? Because it's invisible. We can't see the hits in our Redis stats, so we assume it's not working. But it is working, silently, and often poorly because we've given it no direction.
What the Headers Actually Do (and What They Don't)
Let's get concrete. Cache-Control is a rich header with many directives (MDN). For a shared cache, s-maxage takes precedence over max-age (RFC 9111). That means you can give your CDN a long TTL while telling the browser to revalidate sooner. That's powerful. And stale-while-revalidate (RFC 5861) lets you serve stale content while fetching fresh data in the background—a killer for perceived performance.
But the biggest mistake I see is using no-store on everything out of fear. Yes, no-store forbids any cache from storing the response (MDN), and that's appropriate for sensitive data. But for public, static-ish resources, it's a performance killer. Instead, use public and max-age to let caches store the response (MDN). And don't forget validators: an ETag lets the server send a 304 Not Modified instead of the full body, saving bandwidth (MDN). That's a free win.
The Counter-Argument: "But Our Data Changes Constantly"
I hear you: "Our API returns dynamic data; we can't cache it." That's the strongest objection, and it's usually wrong. The answer isn't no-store; it's Vary. The Vary header tells caches to store a separate version for each value of a request header (MDN). If your response depends on Accept-Language or Authorization, you can still cache it—just vary on those headers. Yes, it fragments your cache, but you still get hits for users who share the same language or auth scope. And for truly personalized data, you can use private to limit caching to the browser's cache (MDN), which still saves you from regenerating the response on every single request.
What about invalidations? Yes, caches must invalidate on unsafe methods like POST, PUT, DELETE (RFC 9111). But you can also purge the CDN by single file when you know something changed (Cloudflare). It's not perfect, but it's far better than throwing away the cache entirely.
Where Redis Still Fits In
I'm not saying Redis is useless. Far from it. For sessions, rate limiting, and truly dynamic data, Redis is a workhorse. It has typical hit rates of 90-95% and 1-5 ms latency (AWS). But if you're using Redis to serve content that could be cached at the edge, you're wasting it. Use Redis for what it's good at: fast, atomic operations like INCR for counters (Redis) or SETEX for short-lived tokens (Redis). And when you do use Redis, choose allkeys-lru eviction because a cache is not storage (AWS). But don't tune Redis to compensate for missing HTTP headers. That's like buying a sports car to avoid fixing a pothole.
Comparison: Where to Focus Your Tuning Effort
| Layer | Typical Hit Rate | Latency Saved | Control | Best For |
|---|---|---|---|---|
| Browser Cache | Varies | Zero network | High (headers) | Static assets, images |
| CDN Edge | 80-95% (AWS) | 10-100 ms | Medium (headers, purge) | Public content, APIs |
| Redis/Memcached | 90-95% (AWS) | 1-5 ms (AWS) | High (code) | Dynamic, personalized data |
The table says it: Redis gives you milliseconds, but a CDN gives you tens of milliseconds and offloads your origin. Which one would you rather tune?
Bottom Line
Stop tuning Redis eviction policies first. Audit your Cache-Control headers. Use public, s-maxage, and stale-while-revalidate to let the edge cache work for you. Add Vary for dynamic responses, and use ETag for revalidation. Only after you've squeezed the edge should you touch Redis's maxmemory-policy. That's the single best move for performance tuning: fix the headers, and the cache will follow.
Sources
- AWS Caching Overview - https://aws.amazon.com/caching/
- MDN Cache-Control - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
- RFC 9111 HTTP Caching - https://httpwg.org/specs/rfc9111.html
- RFC 5861 stale-while-revalidate - https://www.rfc-editor.org/rfc/rfc5861.txt
- MDN Vary - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary
- Cloudflare Purge by Single-File - https://developers.cloudflare.com/cache/how-to/purge-cache/purge-by-single-file/
Comments (0)
Please sign in to post a comment.
Don't have an account? Create one
No comments yet. Be the first to comment!