A read-heavy endpoint is hammering the database. How do you add caching safely?

ASP.NET Core Scenario Advanced 3–5 Yrs5+ Yrs Scenario 2 min read

Quick Answer

Confirm the data tolerates staleness and by how much. Pick the layer — output caching for whole responses, a distributed cache (Redis) for data shared across instances, in-memory only if one instance. Set a sensible TTL, invalidate on write (tag or key eviction), guard against a cache stampede, and measure the hit rate.

First question: how stale can this data be, and is it the same for everyone or per-user? That decides everything. Then the layer — output caching if I can cache the whole response and evict it by tag when the underlying data changes; Redis if the cached data is shared across multiple app instances; in-memory only if there's one instance. The cache key has to include everything that varies the result, like the tenant. The write path is responsible for invalidating the right keys — and if I can't invalidate cleanly, I use a short TTL and live with a small staleness window. I also protect against a stampede when a hot key expires by using a single-flight lock so only one request refills it. Then I check the hit rate and the database load to confirm it actually helped.

  1. Is it cacheable? How stale can it be — seconds, minutes? Is it the same for every user or per-user/per-tenant (that affects the key)?
  2. Layer:
    • Output caching (.NET 7+) if you can cache the whole response and evict by tag on writes.
    • IDistributedCache / Redis for cached data shared across app instances.
    • IMemoryCache only for a single instance or truly static reference data.
  3. Key — include everything that varies the result (route, query, tenant, maybe user).
  4. Invalidation — the write path evicts the relevant keys/tags. If you can't invalidate reliably, use a short TTL and accept the staleness window.
  5. Stampede — when a hot key expires, many requests miss at once and all hit the DB. Use a lock / GetOrCreateAsync with a single-flight, or stale-while-revalidate.
  6. Verify — log hit/miss ratio and DB load before and after.