API Rate Limiting ve Throttling Best Practices

API'leri aşırı kullanımdan korumak için rate limiting ve throttling stratejileri; Token Bucket, Leaky Bucket, Fixed/Sliding Window algoritmaları, .NET 7+ ile yerleşik RateLimiter kullanımı, dağıtık sistemlerde Redis ile state yönetimi ve en iyi pratikler ele alınır.

API Rate Limiting ve Throttling Best Practices

API Rate Limiting ve Throttling Best Practices

API'lerinizi aşırı kullanımdan korumak ve hizmet kalitesini (QoS) garanti altına almak için Rate Limiting (Hız Sınırlama) ve Throttling (Boğma), olmazsa olmaz iki savunma mekanizmasıdır. Bu yazıda, en yaygın kullanılan algoritmaları, .NET'teki uygulama yöntemlerini ve sağlam bir strateji için dikkat edilmesi gereken en iyi pratikleri inceleyeceğiz.


1. Temel Rate Limiting Algoritmaları

Doğru algoritmayı seçmek, uygulamanızın trafik desenine ve ihtiyaçlarına bağlıdır:

Algoritma Çalışma Mantığı Avantajları Dezavantajları Kullanım Alanı
Token Bucket Sabit kapasiteli bir kovaya belirli hızda token eklenir; her istek bir token tüketir. Ani trafik patlamalarına (burst) izin verir. Dağıtık sistemlerde token durumunu paylaşmak ek maliyet getirir. Genel API limitleri, kullanıcı başına istek sınırı.
Leaky Bucket Gelen istekler sabit kapasiteli kovaya eklenir ve sabit hızla işlenir (sızdırır). Trafik akışını düzgünleştirir (smoothing). Ani patlamaları desteklemez; kuyruk yönetimi karmaşıktır. Veritabanı yazma, batch işlemler.
Fixed Window Zaman sabit aralıklara bölünür ve her aralık için bir sayaç tutulur. Çok basit ve hafiftir. Pencere sınırlarında limitin iki katı istek gönderilebilir (sınır sorunu). Basit, düşük trafikli API'ler.
Sliding Window Zamanı daha hassas parçalara bölerek veya isteklerin zaman damgalarını tutarak çalışır. Sınır sorununu ortadan kaldırır, daha adildir. Uygulaması daha zordur ve daha fazla bellek tüketir. Yüksek trafik, hassas limit ihtiyacı.

2. .NET'te Rate Limiting Uygulama Yöntemleri

.NET 7 ve sonrası, System.Threading.RateLimiting namespace'i ile yerleşik ve güçlü bir rate limiting desteği sunmaktadır.

A. Yerleşik Rate Limiter'ları Kullanmak (.NET 7+)

En temel ve önerilen yöntem, bu yerleşik limiter'ları kullanmaktır:

csharp

// Program.cs
using System.Threading.RateLimiting;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRateLimiter(options =>
{
    // 1. Fixed Window Limiter: Dakikada 100 istek
    options.AddFixedWindowLimiter("Fixed", opt =>
    {
        opt.PermitLimit = 100;
        opt.Window = TimeSpan.FromMinutes(1);
        opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
        opt.QueueLimit = 0; // Kuyruk yok
    });

    // 2. Sliding Window Limiter: Dakikada 100 istek, 10 segment
    options.AddSlidingWindowLimiter("Sliding", opt =>
    {
        opt.PermitLimit = 100;
        opt.Window = TimeSpan.FromMinutes(1);
        opt.SegmentsPerWindow = 10;
    });

    // 3. Token Bucket Limiter
    options.AddTokenBucketLimiter("TokenBucket", opt =>
    {
        opt.TokenLimit = 100;
        opt.QueueLimit = 10;
        opt.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
        opt.TokensPerPeriod = 20;
    });

    // 4. Global veya Endpoint bazında uygulama
    options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(
        httpContext => RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: httpContext.User.Identity?.Name ?? httpContext.Connection.RemoteIpAddress?.ToString(),
            factory: partition => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 100,
                Window = TimeSpan.FromMinutes(1)
            }
        ));
});

app.UseRateLimiter(); // Middleware'i ekle

app.MapGet("/api/data", () => "Hello, World!")
   .RequireRateLimiting("Fixed"); // Belirli bir policy kullan

Bu yaklaşım, yukarıdaki algoritmaların çoğunu zaten implemente ettiği için hızlı ve güvenilir bir çözüm sunar.

B. Özel Middleware ile İnce Ayar (Önceki Sürümler veya Özel İhtiyaçlar)

Eğer .NET 7 öncesi bir sürüm kullanıyorsanız veya çok özel bir mantık gerekiyorsa, özel bir middleware oluşturabilirsiniz:

csharp

public class RateLimitingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IMemoryCache _cache;

    public async Task InvokeAsync(HttpContext context)
    {
        var clientId = context.Connection.RemoteIpAddress.ToString();
        var cacheKey = $"RateLimit_{clientId}";

        // 1. Cache'ten mevcut sayaç değerini al
        if (_cache.TryGetValue(cacheKey, out int requestCount))
        {
            if (requestCount >= 100) // Limit aşıldıysa
            {
                context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
                await context.Response.WriteAsync("Rate limit exceeded. Please try again later.");
                return;
            }
            // Limit aşılmadıysa sayacı artır
            _cache.Set(cacheKey, requestCount + 1, TimeSpan.FromMinutes(1));
        }
        else
        {
            // İlk istek, sayacı oluştur
            _cache.Set(cacheKey, 1, TimeSpan.FromMinutes(1));
        }

        await _next(context);
    }
}

3. Dağıtık Sistemlerde Rate Limiting (Redis ile)

Birden fazla sunucu (Web Farm, Kubernetes) varsa, istemciler farklı sunuculara istek gönderebilir. Bu durumda, rate limiter state'ini dağıtık bir cache'de (Redis) saklamak gerekir.

  • Redis Sorted Set: Her isteği bir sorted set'e ekleyip penceredeki istek sayısını hesaplamak.

  • Redis Lua Script: Atomik işlem için Lua script kullanarak hem okuma hem yazma yapmak (race condition önleme).

Örnek (Redis Sorted Set ile Sliding Window):

csharp

public async Task<bool> IsAllowedAsync(string key, int limit, TimeSpan window)
{
    var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
    var windowStart = now - (long)window.TotalSeconds;

    var redis = GetRedisDatabase(); // StackExchange.Redis
    var sortedSetKey = $"ratelimit:{key}";

    // 1. Pencere dışındaki eski istekleri temizle
    await redis.SortedSetRemoveRangeByScoreAsync(sortedSetKey, 0, windowStart);

    // 2. Mevcut penceredeki istek sayısını al
    var count = await redis.SortedSetLengthAsync(sortedSetKey);

    // 3. Limit kontrolü
    if (count < limit)
    {
        // 4. Yeni isteği ekle (score = timestamp)
        await redis.SortedSetAddAsync(sortedSetKey, Guid.NewGuid().ToString(), now);
        await redis.KeyExpireAsync(sortedSetKey, window); // TTL
        return true;
    }
    return false;
}

Dağıtık Rate Limiting Kütüphaneleri: AspNetCoreRateLimit (Redis desteği), Polly.RateLimit gibi hazır çözümler de mevcuttur.


4. Rate Limiting En İyi Pratikler (Best Practices)

  1. Doğru Algoritmayı Seçin: Trafik deseninize göre karar verin. Çoğu genel senaryo için Token Bucket iyi bir dengedir.

  2. Limitleri Dinamik Yapın: Kullanıcı abonelik seviyesine göre limitler değişebilir. Partition key'i doğru seçin (ör. API Key) ve limitleri bir konfigürasyon dosyasından veya veritabanından okuyun.

  3. Kullanıcı Deneyimini Ön Planda Tutun: İstek reddedildiğinde (429 Too Many Requests) yanıt başlığına Retry-After bilgisini ekleyerek istemciye ne zaman tekrar deneyeceğini bildirin.

  4. Rate Limiting'i Sadece Gateway'de Değil, Servis Katmanında da Uygulayın: Bazı servisler doğrudan gateway dışından çağrılabilir. Bu nedenle, rate limiting'i hem gateway'de hem de servis katmanında (opsiyonel) uygulayın.

  5. Token Bucket'ta Replenishment Period'u İyi Ayarlayın: Token'ları saniyede 10 eklemek yerine, toplu olarak (ör. 60 saniyede 600 token) eklemek, sürekli yenileme maliyetini azaltır.

  6. İzleme ve Uyarı (Monitoring & Alerting) Kurun: Rate limiting ile ilgili metrikleri (toplam istek, reddedilen istek, limit aşımı) izleyin ve anormal durumlar için uyarılar oluşturun.

Sonuç

Rate limiting, API'larınızın güvenliğini, kararlılığını ve adil kullanımını sağlamak için kritik bir araçtır. .NET 7+ ile gelen yerleşik RateLimiter desteği, bu mekanizmayı uygulamayı her zamankinden daha kolay hale getirmiştir. Doğru algoritmayı seçmek, dağıtık sistemlerde Redis gibi araçlarla state yönetimini sağlamak ve yukarıdaki en iyi pratikleri takip etmek, başarılı bir rate limiting stratejisinin anahtarıdır. Unutmayın, hızlı ve adil bir API, mutlu kullanıcılar demektir.

Tüm yazılar