TurboMediator
Patterns

Rate Limiting

Control request throughput with rate limiting and bulkhead isolation

The rate limiting package provides two complementary behaviors: Rate Limiting to control request throughput, and Bulkhead Isolation to limit concurrent executions.

Installation

dotnet add package TurboMediator.RateLimiting

Rate Limiting

Using the Attribute

[RateLimit(maxRequests: 10, windowSeconds: 60)]
public record SendSmsCommand(string Phone, string Message) : ICommand;

[RateLimit(maxRequests: 100, windowSeconds: 60, PerUser = true)]
public record SearchQuery(string Term) : IQuery<SearchResult>;

[RateLimit(maxRequests: 50, windowSeconds: 60, PerTenant = true)]
public record ApiCallCommand(string Endpoint) : ICommand<ApiResponse>;

Using Configuration

builder.Services.AddTurboMediator(m =>
{
    m.WithRateLimiting<SendSmsCommand, Unit>(options =>
    {
        options.MaxRequests = 10;
        options.WindowSeconds = 60;
        options.Algorithm = RateLimiterAlgorithm.SlidingWindow;
        options.PerUser = true;
        options.ThrowOnRateLimitExceeded = true;
    });

    // Or apply globally
    m.WithGlobalRateLimiting(options =>
    {
        options.MaxRequests = 1000;
        options.WindowSeconds = 60;
    });
});

RateLimitOptions

OptionDefaultDescription
MaxRequests100Max requests per window
WindowSeconds60Time window in seconds
PerUserfalsePer-user rate limiting
PerTenantfalsePer-tenant rate limiting
PerIpAddressfalsePer-IP rate limiting
AlgorithmRateLimiterAlgorithm.FixedWindowAlgorithm: FixedWindow, SlidingWindow, TokenBucket, Concurrency
TokensPerPeriod10Tokens per period (TokenBucket algorithm)
SegmentsPerWindow4Segments per window (SlidingWindow algorithm)
QueueProcessingOrderOldestFirstOrder for processing queued requests
PolicyNamenullNamed policy identifier
QueueExceededRequestsfalseQueue instead of reject
MaxQueueSize0Maximum queue size
ThrowOnRateLimitExceededtrueThrow vs return default
UserIdProvidernullCustom user ID provider
TenantIdProvidernullCustom tenant ID provider
IpAddressProvidernullCustom IP provider
CustomRateLimiterFactorynullCustom rate limiter

Algorithms

AlgorithmDescription
FixedWindowFixed time windows (e.g., 100 req/min)
SlidingWindowRolling time window for smoother limiting
TokenBucketToken-based with steady refill rate
ConcurrencyLimits concurrent executions

Convenience Methods

m.WithSlidingWindowRateLimit<ApiCallCommand, ApiResponse>(
    maxRequests: 50, windowSeconds: 60, segmentsPerWindow: 4);

m.WithTokenBucketRateLimit<ApiCallCommand, ApiResponse>(
    bucketSize: 100, tokensPerPeriod: 10, replenishmentPeriodSeconds: 60);

m.WithPerUserRateLimit<SearchQuery, SearchResult>(
    maxRequestsPerUser: 10, windowSeconds: 60, userIdProvider: () => GetCurrentUserId());

RateLimitExceededException

try
{
    await mediator.Send(new SendSmsCommand("555-1234", "Hello"));
}
catch (RateLimitExceededException ex)
{
    // ex.MessageType — what was rate limited
    // ex.PartitionKey — the partition that exceeded limits
    // ex.RetryAfter — suggested retry delay
    return Results.StatusCode(429);
}

Bulkhead Isolation

Limits concurrent execution of handlers to prevent resource exhaustion.

Using the Attribute

[Bulkhead(maxConcurrent: 5, maxQueue: 10)]
public record ProcessVideoCommand(string VideoUrl) : ICommand<ProcessResult>;

Using Configuration

builder.Services.AddTurboMediator(m =>
{
    m.WithBulkhead<ProcessVideoCommand, ProcessResult>(options =>
    {
        options.MaxConcurrent = 5;
        options.MaxQueue = 10;
        options.QueueTimeout = TimeSpan.FromSeconds(30);
        options.ThrowOnBulkheadFull = true;
        options.TrackMetrics = true;
        options.OnRejection = (info) =>
        {
            Console.WriteLine($"Bulkhead full for {info.MessageType}: {info.Reason}");
        };
    });

    // Or global bulkhead
    m.WithGlobalBulkhead(options =>
    {
        options.MaxConcurrent = 50;
    });
});

BulkheadOptions

OptionDefaultDescription
MaxConcurrent10Maximum concurrent executions
MaxQueue100Maximum queued requests
QueueTimeoutnullTimeout waiting in queue
ThrowOnBulkheadFulltrueThrow BulkheadFullException
TrackMetricsfalseTrack concurrency metrics
PerPartitionfalseSeparate bulkheads per partition
PartitionKeyProvidernullCustom partition key
OnRejectionnullCallback when rejected

Combined Rate Limiting + Bulkhead

builder.Services.AddTurboMediator(m =>
{
    // Apply both at once
    m.WithThrottling<ProcessVideoCommand, ProcessResult>(
        rateLimitOptions =>
        {
            rateLimitOptions.MaxRequests = 100;
            rateLimitOptions.WindowSeconds = 60;
        },
        bulkheadOptions =>
        {
            bulkheadOptions.MaxConcurrent = 5;
        });
});

Rate limiting controls throughput (requests per time window), while bulkhead isolation controls concurrency (simultaneous executions). Use both together for comprehensive resource protection.

On this page