IConfiguration vs IOptions NET
Synchronous and Asynchronous in .NET Core
Model Binding and Validation in ASP.NET Core
ControllerBase vs Controller in ASP.NET Core
ConfigureServices and Configure methods
IHostedService interface in .NET Core
ASP.NET Core request processing
| Unit Testing in .NET core | ViewData, ViewBag, TempData & Session | |
Thundering Herd Problem |
The Thundering Herd Problem occurs when a large number of processes or threads, which are all waiting for the same event, are woken up simultaneously when that event occurs. Only one of them can ultimately perform the task, while the rest are left to compete for resources, leading to a cascade of wasted effort, performance degradation, and potential system crashes.
This problem is not confined to a specific component. It can manifest in various parts of a system, from operating systems and databases to distributed systems and web applications.
Analogy: The Ice Cream Truck
Imagine an ice cream truck in a park announces free ice cream for the first person in line. If 100 people all hear the announcement and rush to the truck at the exact same time, you have a "thundering herd".
The winner: Only one person gets the ice cream.
The losers: The other 99 people wasted their energy and contributed to the chaos, possibly overwhelming the ice cream vendor and line.
The result: Chaos, wasted effort, and an overwhelmed vendor.
The thundering herd problem occurs when a large number of processes, threads, or clients simultaneously attempt to access a shared resource or respond to the same event—often overwhelming the system.
Imagine hundreds or thousands of clients waiting for a cache to expire or a server to become available. The moment that happens, they all rush in at once, like a stampede. Only one of them may succeed, but the rest still consume CPU, memory, or network resources trying.
Common causes in software systems
Cache expiration or cold cache: When a highly-accessed cache entry expires, many concurrent requests will find the cache empty. They all rush to the backend (e.g., a database) to fetch the data simultaneously, overwhelming it with traffic.
Service restarts or outages: After a service goes down for maintenance or crashes, all the dependent clients will attempt to reconnect and flood the newly restored service with requests at the same time.
Synchronized batch jobs or scheduled tasks: If multiple servers or processes are running the same job (e.g., a cron job) that triggers at the exact same moment (e.g., on the hour), they will all hit a shared resource simultaneously.
Mass client retries: If many clients or microservices use the same, fixed-interval retry policy after a network failure, they can end up retrying their requests at the same moment, creating repeated traffic spikes.
Mass-market events: High-demand events like concert ticket sales or product drops can cause millions of users to hit a system at the same instant, replicating the thundering herd problem at a massive scale.
Consequences of the problem
System overload and degradation: The sudden flood of requests can overwhelm the system's processing capacity, leading to high latency and dropped requests.
Resource contention: All competing processes or threads fight for shared resources like CPU time, memory, or database connections, causing a major bottleneck.
Wasted resources: The effort spent waking and scheduling dozens or hundreds of processes, even if only one can succeed, is a significant waste of CPU cycles and memory.
System instability and cascading failures: An overwhelmed server can crash, causing all its dependent services to fail as well. As those services attempt to recover and retry, the cascading failures can spread throughout the entire system.
Solutions and mitigation strategies
Cache locking or request coalescing: Implement a mechanism to ensure that when a cache entry expires, only one request is allowed to go to the backend to refresh it. All other requests for that same data must wait for the result and then get it from the newly updated cache.
Staggered cache expiration: Instead of having all cached items expire at a fixed interval, add a small, random amount of "jitter" to their Time-to-Live (TTL). This spreads out the refresh requests over time and prevents a large-scale cache expiration event.
Exponential backoff with jitter: For client retries, use an exponential backoff algorithm that progressively increases the waiting time between retries after each failure. Critically, add a random value (jitter) to the backoff time to ensure retries don't synchronize and cause repeated herd effects.
Rate limiting: Apply rate limiting to your API endpoints to control the frequency of requests and protect backend services from being overwhelmed. This can be done at an API gateway or within the application itself.
Message queuing and queuing theory: Place a queue (e.g., RabbitMQ, Kafka) in front of the resource. This allows requests to be buffered and processed at a stable, controlled rate, smoothing out sudden bursts of traffic.
Circuit breaker pattern: Implement a circuit breaker to prevent requests from flooding an already overwhelmed service. The breaker will "trip" and fail requests immediately when a service is under stress, providing a fallback response and protecting the downstream service from collapsing.
Use advanced OS APIs: Modern operating systems offer more efficient APIs for handling asynchronous I/O and waking processes. For example, Linux's epoll with the EPOLLEXCLUSIVE flag or Windows' I/O completion ports (IOCP) can be configured to wake only a single process or thread for an event.
To mitigate the impact of the thundering herd problem in a distributed system—where many clients simultaneously request the same resource, overwhelming the system—several strategies can be employed:
🛠️ Effective Mitigation Strategies
Request Coalescing
Combine multiple identical requests into a single backend call.
Once the result is available, share it with all waiting clients.
Caching with Locking (or "Cache Stampede" Protection)
Use a lock or semaphore when a cache miss occurs so only one client regenerates the data.
Others wait for the cache to be repopulated.
Randomized Backoff or Jitter
Introduce random delays before retrying failed requests.
Prevents synchronized retries that worsen the load.
Rate Limiting
Limit the number of requests per client or per endpoint.
Helps control traffic spikes and smooth out demand.
Staggered Scheduling
Spread out scheduled tasks or refreshes to avoid simultaneous execution.
Useful for cron jobs or periodic polling.
Load Shedding
Drop excess requests when the system is under heavy load.
Prioritize critical traffic to maintain system stability.
Queueing and Throttling
Use queues to buffer incoming requests and process them gradually.
Throttle request rates to prevent overload.
Push-Based Updates
Instead of clients polling for updates, push changes from the server.
Reduces redundant traffic and improves efficiency.
These techniques can be combined depending on the architecture and nature of your distributed system. For example, caching with locking is often paired with request coalescing in high-read environments like CDN-backed APIs
| Unit Testing in .NET core | ViewData, ViewBag, TempData & Session | |