Course contentsShow
← C#
Lesson 1784 of 1,90816. Performance and DiagnosticsFree lesson

Implementing Throttling for Load Balancing

Learners will build custom request limiters to protect against cascading performance failure.

In this lesson, you will learn how to implement custom request limiters to protect your services from cascading performance failures by enforcing back-pressure at the application level.

The Core Problem: Cascading Failure

When a downstream service or database becomes saturated, your application can suffer from a "death spiral." As requests pile up, the thread pool starves and memory consumption spikes due to queued tasks, leading to higher latency for every user. Throttling acts as an circuit breaker, shedding load early to ensure that the portion of traffic you do accept has a chance to succeed.

Implementing a Token Bucket Limiter

To prevent cascading failure, we often implement a "Token Bucket" algorithm. Think of this as a bucket that fills with a limited number of "tokens" per second. Every incoming request must consume a token; if the bucket is empty, the request is immediately rejected with a 429 Too Many Requests status, protecting your system's remaining resources.

Here is a lightweight implementation using a SemaphoreSlim to track available concurrency:

public class RequestThrottler
{
    private readonly SemaphoreSlim _semaphore;

    public RequestThrottler(int maxConcurrentRequests)
    {
        _semaphore = new SemaphoreSlim(maxConcurrentRequests);
    }

    public async Task<bool> TryExecuteAsync(Func<Task> action)
    {
        if (await _semaphore.WaitAsync(0)) // Non-blocking check
        {
            try
            {
                await action();
                return true;
            }
            finally
            {
                _semaphore.Release();
            }
        }
        return false; // Load shed: service is at capacity
    }
}

Why This Protects Performance

By rejecting requests at the entry point, you prevent the thread pool from becoming saturated with work that cannot be completed. This preserves the responsiveness of the threads already executing critical operations. Unlike standard request queues, which store pending requests in memory (increasing GC pressure), this approach explicitly drops excess load. When integrated into your middleware pipeline, it ensures that your P99 latency remains stable even when the total demand exceeds the capacity of your infrastructure.

Key Takeaway: Throttling is a deliberate strategy to trade away immediate availability for a subset of users to prevent a total system-wide collapse during periods of extreme load.