DEV Community

Huzaifa Awan
Huzaifa Awan

Posted on • Originally published at huzaifaawan.com

How I Hit a 95% Redis Cache Hit Rate Under Real Load

On a multi-tenant SaaS dashboard I work on, response times climbed as concurrency grew. The fix was not a bigger server. It was a Redis caching strategy layered onto a .NET Core API. The result: a 95% cache hit rate and sub-50ms responses under 100+ concurrent users. Here is the actual process, not the highlight reel.

Measure before you cache

Caching the wrong thing hides problems and creates new ones. I started by profiling the API under realistic load to find the true hotspots: the endpoints that were both frequently hit and expensive to compute. Almost all the pain came from a handful of dashboard reads that ran heavy SQL Server aggregations on every request.

  • Frequent + expensive = cache it.
  • Frequent + cheap = probably leave it.
  • Rare + expensive = optimize the query, do not cache.

Cache-key design is the whole game

A cache is only as good as its keys. For a multi-tenant app, every key has to be scoped to the tenant and to the exact parameters that change the result: date range, filters, locale. Get this wrong and you either leak data across tenants or get a hit rate near zero because no two keys ever match.

dashboard:{tenantId}:kpis:{from}:{to}:{locale}
dashboard:{tenantId}:queue:live
catalog:{tenantId}:lowstock
Enter fullscreen mode Exit fullscreen mode

Flat, predictable, and self-documenting. When a key names exactly what it holds, invalidation becomes obvious instead of guesswork.

Invalidation without the footguns

The famous hard problem. I leaned on two techniques: short, tuned TTLs for data that is allowed to be a few seconds stale (KPI roll-ups), and event-driven invalidation for data that must be fresh: when a write happens, the specific keys it affects are evicted immediately. Mixing the two keeps the hit rate high without ever showing a user stale-but-important data.

Verify the hit rate is real

A 95% hit rate only means something if you measure it under production-like concurrency, not a single-user test. I tracked hits, misses, and p95 latency while ramping concurrent users, and watched the cache warm and hold. That is also how you catch a key that looks cached but never actually matches.

The takeaways

  1. Profile first. Cache the endpoints that are frequent and expensive, nothing else.
  2. Scope keys to the tenant and every parameter that changes the result.
  3. Combine tuned TTLs with event-driven eviction so fresh data stays fresh.
  4. Prove the hit rate under real concurrency, not on your laptop.

Performance work like this, turning a slow app that scales poorly into a fast one, is a big part of what I do. See more of my work or reach out if your app is starting to feel the load.


Originally published at huzaifaawan.com. I am Muhammad Huzaifa Awan, a Senior Full Stack Developer building SaaS, AI products, and MCP servers. More writing and my portfolio live at huzaifaawan.com.

Top comments (0)