<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/cache-stampede-when-our-cache-turned-against-us-4vfw3uolu" -->

---
title: Cache stampede: when our cache turned against us | daily.dev
description: A cache stampede occurs when many concurrent requests all miss the cache simultaneously and hammer the backend. Using ASP.NET Core's IMemoryCache without...
canonical: https://daily.dev/posts/cache-stampede-when-our-cache-turned-against-us-4vfw3uolu
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Cache stampede: when our cache turned against us | daily.dev
og:description: A cache stampede occurs when many concurrent requests all miss the cache simultaneously and hammer the backend. Using ASP.NET Core's IMemoryCache without...
og:url: https://daily.dev/posts/cache-stampede-when-our-cache-turned-against-us-4vfw3uolu
og:image: https://api.daily.dev/og/posts/4VFW3uolu.png
og:image:alt: Cache stampede: when our cache turned against us
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Cache stampede: when our cache turned against us

**[The Art of Simplicity](https://daily.dev/sources/bartwullems)** · 5 min read · 2 upvotes · 0 comments

## Summary

A cache stampede occurs when many concurrent requests all miss the cache simultaneously and hammer the backend. Using ASP.NET Core's IMemoryCache without locking leaves this problem unaddressed. Three fixes are presented: (1) a per-key SemaphoreSlim with double-checked locking to serialize concurrent rebuilds for the same key, (2) Microsoft's HybridCache which provides built-in stampede protection and a two-level cache out of the box, and (3) adding TTL jitter to prevent many different keys from expiring at the same instant. HybridCache is recommended for broader use, while SemaphoreSlim works well for a small number of hot keys.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://bartwullems.blogspot.com/2026/07/cache-stampede-when-our-cache-turned.html>

## Questions this post answers

### What causes a cache stampede in an ASP.NET Core app using IMemoryCache.GetOrCreate?

IMemoryCache.GetOrCreate and its async equivalent do not add any locking, so when the cache entry expires and many concurrent requests miss at once, every one of them independently calls the expensive rebuild function in parallel, overwhelming the database or downstream API. This happens with distributed caches like Redis too, since the cache itself has no awareness of concurrent callers for the same key.

_Developers debugging sudden database load spikes can find more caching deep dives like this on daily.dev._

### How does Microsoft.Extensions.Caching.Hybrid.HybridCache prevent cache stampedes in .NET?

HybridCache's GetOrCreateAsync method guarantees only one factory call runs per key at a time; every other concurrent caller for that key awaits the same in-flight task instead of starting a new one. This replicates the behavior of a hand-rolled SemaphoreSlim per-key lock without the manual bookkeeping, and ships without requiring an extra NuGet dependency. Its protection applies only at the local, in-process layer, not across cluster nodes.

_Teams evaluating built-in versus custom locking for caching can weigh trade-offs like these on daily.dev._

### How do you add jitter to a cache TTL in C# to avoid synchronized cache expirations?

Add a random extra duration to the base expiration, for example TimeSpan.FromSeconds(60) plus TimeSpan.FromSeconds(Random.Shared.Next(0, 10)), when calling cache.Set. This spreads out expirations for many different keys that would otherwise expire at the same instant, such as a batch of items warmed at startup, turning one large load spike into several smaller ones.

_Developers hardening cache warm-up logic against synchronized expirations can browse related caching patterns on daily.dev._

## Similar posts on daily.dev

- [Cache Me If You Can: A .NET Caching Survival Guide](https://daily.dev/posts/cache-me-if-you-can-a-net-caching-survival-guide-sydnkz4ko) · ITNEXT · 2 upvotes · 0 comments
- [How to Build a High-Performance Cache Without External Libraries](https://daily.dev/posts/how-to-build-a-high-performance-cache-without-external-libraries-phts4izvp) · Milan Jovanović · 6 upvotes · 0 comments
- [Taming the ClickHouse Cache Stampede](https://daily.dev/posts/taming-the-clickhouse-cache-stampede-2akcfcl1c) · BigData Boutique blog · 4 upvotes · 0 comments
- [How to tame the thundering herd problem](https://daily.dev/posts/how-to-tame-the-thundering-herd-problem-t1eucwlba) · Redis · 1 upvotes · 0 comments
- [Designing Systems That Don’t Break When It Matters Most](https://daily.dev/posts/designing-systems-that-don-t-break-when-it-matters-most-ihqyerzu0) · SD Times · 1 upvotes · 0 comments

---

Tags: [#aspnet](https://daily.dev/tags/aspnet)

[View this post on daily.dev](https://daily.dev/posts/cache-stampede-when-our-cache-turned-against-us-4vfw3uolu)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Cache stampede: when our cache turned against us","url":"https://daily.dev/posts/cache-stampede-when-our-cache-turned-against-us-4vfw3uolu","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/cache-stampede-when-our-cache-turned-against-us-4vfw3uolu"},"datePublished":"2026-07-13T05:45:04.687Z","dateModified":"2026-09-14T06:37:34.696Z","description":"A cache stampede occurs when many concurrent requests all miss the cache simultaneously and hammer the backend. Using ASP.NET Core's IMemoryCache without...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/3919e50809df9ed8adcfb2079fa066d2?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/3919e50809df9ed8adcfb2079fa066d2?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"The Art of Simplicity","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"The Art of Simplicity","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/52b68c70636d44f0901ae7cd107c9438","url":"https://daily.dev/sources/bartwullems"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/cache-stampede-when-our-cache-turned-against-us-4vfw3uolu","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":2},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"aspnet","timeRequired":"PT5M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"The Art of Simplicity","item":"https://daily.dev/sources/bartwullems"},{"@type":"ListItem","position":3,"name":"Cache stampede: when our cache turned against us"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/cache-stampede-when-our-cache-turned-against-us-4vfw3uolu#faq","mainEntity":[{"@type":"Question","name":"What causes a cache stampede in an ASP.NET Core app using IMemoryCache.GetOrCreate?","acceptedAnswer":{"@type":"Answer","text":"IMemoryCache.GetOrCreate and its async equivalent do not add any locking, so when the cache entry expires and many concurrent requests miss at once, every one of them independently calls the expensive rebuild function in parallel, overwhelming the database or downstream API. This happens with distributed caches like Redis too, since the cache itself has no awareness of concurrent callers for the same key. Developers debugging sudden database load spikes can find more caching deep dives like this on daily.dev."}},{"@type":"Question","name":"How does Microsoft.Extensions.Caching.Hybrid.HybridCache prevent cache stampedes in .NET?","acceptedAnswer":{"@type":"Answer","text":"HybridCache's GetOrCreateAsync method guarantees only one factory call runs per key at a time; every other concurrent caller for that key awaits the same in-flight task instead of starting a new one. This replicates the behavior of a hand-rolled SemaphoreSlim per-key lock without the manual bookkeeping, and ships without requiring an extra NuGet dependency. Its protection applies only at the local, in-process layer, not across cluster nodes. Teams evaluating built-in versus custom locking for caching can weigh trade-offs like these on daily.dev."}},{"@type":"Question","name":"How do you add jitter to a cache TTL in C# to avoid synchronized cache expirations?","acceptedAnswer":{"@type":"Answer","text":"Add a random extra duration to the base expiration, for example TimeSpan.FromSeconds(60) plus TimeSpan.FromSeconds(Random.Shared.Next(0, 10)), when calling cache.Set. This spreads out expirations for many different keys that would otherwise expire at the same instant, such as a batch of items warmed at startup, turning one large load spike into several smaller ones. Developers hardening cache warm-up logic against synchronized expirations can browse related caching patterns on daily.dev."}}]}
```

