Cache stampedes happen when many requests miss the same cache entry at the same time and all of them recompute or reload the value. That can turn a cache miss into a database spike, an API quota problem, or a slow dependency incident.
Use HybridCache for shared expensive lookups where stampede protection matters. It gives you one cache abstraction for local memory and optional distributed storage. Even without Redis or another IDistributedCache, it still gives you in-process caching and coordinates concurrent callers for the same key.
That makes it a better default than raw IMemoryCache when many requests can miss the same expensive value at once:
app.MapGet("/api/tenant/{id}/settings", async (
string id,
HybridCache cache,
TenantDb db,
CancellationToken ct) =>
{
return await cache.GetOrCreateAsync(
$"tenant-settings-{id}",
async cancel => await db.GetSettingsAsync(id, cancel),
options: new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(5)
},
tags: ["tenant-settings"],
cancellationToken: ct);
});
Good candidates are configuration snapshots, reference data, expensive read models, model metadata, tenant settings, and external API results that can tolerate short-lived staleness.
Do not hide business correctness behind a cache. Make the key explicit, set an intentional expiration, and keep invalidation or refresh behavior visible. If related entries need to be refreshed together, use tags and invalidate them with RemoveByTagAsync. Treat that as part of the write path, not as a background cleanup detail.
A cache should reduce pressure, not make state changes impossible to reason about.