API'ler günümüzde uygulamalar arasındaki iletişimin omurgasını oluşturuyor. Bir mobil uygulama, bir web sitesi ya da başka bir servis sizin API'nize istek gönderdiğinde, bu isteklerin sayısı ve sıklığı kontrol altında tutulmazsa sistem kısa sürede zor duruma düşebilir. İşte tam bu noktada devreye "rate limiting" yani istek sınırlama giriyor. Bu yazıda, rate limiting'in neden gerekli olduğunu ve yaygın kullanılan algoritmaların mantığını pratik bir bakış açısıyla ele alacağız.
Bir bakışta
- Rate limiting, API'lere gelen istek sayısını sınırlayarak sunucu kaynaklarını korur ve adil kullanım sağlar.
- Fixed Window Counter basittir ama pencere sınırlarında istek patlaması sorunu yaşanabilir.
- Token Bucket ve Leaky Bucket algoritmaları trafiği esnek veya düzenli akışla yönetmeyi sağlar.
API Rate Limiting Nedir ve Neden Gereklidir?
Rate limiting, belirli bir zaman aralığında bir istemcinin (kullanıcı, uygulama veya IP adresi) API'nize gönderebileceği istek sayısını sınırlayan bir kontrol mekanizmasıdır. Basitçe söylemek gerekirse, "bu istemci dakikada en fazla şu kadar istek atabilir" kuralını uygulamaktır.
Bunun gerekli olmasının birkaç temel nedeni var. İlk olarak kaynak koruma söz konusu: sunucunuzun işlemci gücü, belleği, veritabanı bağlantı havuzu ve bant genişliği sınırsız değildir. Kontrolsüz bir istek akını, bu kaynakların tükenmesine ve sistemin tamamen yanıt veremez hale gelmesine yol açabilir.
İkinci neden adil kullanımdır. Birden fazla istemci aynı API'yi kullanıyorsa, tek bir istemcinin aşırı istek göndererek diğerlerinin hizmet kalitesini düşürmesi istenmeyen bir durumdur. Rate limiting, kaynakların istemciler arasında daha dengeli paylaşılmasını sağlar.
Üçüncüsü, kötüye kullanım ve saldırıların önlenmesi. Brute force saldırıları (örneğin şifre tahmin etmeye çalışan botlar), aşırı veri kazıma (scraping) girişimleri veya kasıtlı hizmet dışı bırakma (DoS) denemeleri rate limiting sayesinde büyük ölçüde engellenebilir ya da en azından zorlaştırılabilir.
Rate limiting uygulanmadığında ortaya çıkabilecek riskler arasında sunucu çökmeleri, yavaşlayan yanıt süreleri, artan altyapı maliyetleri ve güvenlik açıkları sayılabilir. Bu nedenle rate limiting, özellikle herkese açık veya yoğun kullanılan API'ler için neredeyse zorunlu bir tasarım bileşenidir.
Fixed Window Counter: En Basit Yaklaşım
Fixed Window Counter (sabit pencere sayacı), rate limiting'in en kolay anlaşılan ve uygulanan yöntemidir. Mantığı şu şekildedir: zaman, sabit uzunlukta pencerelere bölünür (örneğin her dakika bir pencere) ve her pencere içinde gelen istek sayısı bir sayaçla takip edilir. Sayaç belirlenen limite ulaştığında, o pencere bitene kadar yeni istekler reddedilir.
Bu yöntemin en büyük avantajı uygulama kolaylığıdır. Basit bir sayaç ve zaman damgası ile gerçekleştirilebilir, ek bellek veya hesaplama yükü getirmez.
Ancak önemli bir sınırı vardır: pencere sınırlarında ortaya çıkan patlama (burst) problemi. Örneğin limit dakikada 100 istek ise, bir istemci dakikanın son saniyesinde 100 istek, bir sonraki dakikanın ilk saniyesinde de 100 istek daha gönderebilir. Bu durumda iki saniyelik bir aralıkta aslında 200 istek işlenmiş olur ki bu, sistemin beklediği dengeden sapmadır.
Buna rağmen Fixed Window Counter, trafiğin çok düzensiz olmadığı, hassas doğruluk gerektirmeyen ve basitliğin öncelikli olduğu senaryolarda yeterli bir çözüm olabilir.
Sliding Window Log ve Sliding Window Counter
Fixed Window'un pencere sınırı sorununu çözmek için geliştirilen yaklaşımlardan biri Sliding Window Log'dur. Bu yöntemde, her isteğin tam zaman damgası ayrı ayrı kaydedilir. Yeni bir istek geldiğinde, geçerli zamandan geriye doğru belirlenen pencere süresi kadar (örneğin son 60 saniye) kayıtlar incelenir ve bu aralıktaki istek sayısı limitle karşılaştırılır.
Bu yaklaşım çok hassas ve doğru sonuçlar verir çünkü pencere, isteğin geldiği ana göre kayarak hesaplanır; sabit sınırlarda yaşanan patlama sorunu ortadan kalkar. Ancak dezavantajı, her istemci için tüm zaman damgalarının saklanması gerektiğinden bellek ve performans maliyetinin yüksek olmasıdır. Yoğun trafikli sistemlerde bu, ciddi bir kaynak yükü oluşturabilir.
Bu noktada Sliding Window Counter, iki yaklaşım arasında bir denge noktası sunar. Bu yöntem, Fixed Window'un sayaç mantığını korurken, mevcut pencere ile bir önceki pencerenin sayaç değerlerini ağırlıklı olarak birleştirir. Böylece tam log tutmadan, pencere sınırlarındaki ani sıçramaları büyük ölçüde yumuşatan daha doğru bir tahmin elde edilir.
Özetle, Sliding Window Log en yüksek doğruluğu sunar ama kaynak maliyeti fazladır; Sliding Window Counter ise makul bir doğruluk seviyesini çok daha düşük bir maliyetle sağlar. Bu yüzden pratikte çoğu zaman Sliding Window Counter tercih edilir.
Token Bucket Algoritması: Esnek Trafik Yönetimi
Token Bucket (jeton kovası), rate limiting dünyasında en yaygın kullanılan algoritmalardan biridir. Mantığı şöyle işler: hayali bir "kova" belirli bir hızda token ile doldurulur (örneğin saniyede 10 token). Her istek geldiğinde kovadan bir token harcanır. Kovada token varsa istek işlenir; kova boşsa istek reddedilir veya beklemeye alınır.
Bu algoritmanın en önemli özelliği, kovanın bir kapasitesi olması ve token biriktirebilmesidir. Yani istemci bir süre istek göndermezse kovada token birikir ve istemci daha sonra bu birikmiş tokenları kullanarak ani bir istek grubunu (burst) sorunsuzca gönderebilir. Bu, gerçek dünya kullanım örüntülerine daha uygun bir esneklik sağlar; her zaman tam sabit hızda istek gelmesini zorunlu kılmaz.
Token Bucket'ın yaygın tercih edilmesinin nedeni tam olarak bu esneklik ile kontrol dengesidir: hem ortalama hızı sınırlar hem de makul ölçüde ani yoğunluklara izin verir.
Algoritmayı ayarlarken iki temel parametre önemlidir: kova kapasitesi (maksimum biriktirilebilecek token sayısı) ve doldurma hızı (tokenların kovaya eklenme sıklığı). Kapasite büyütülürse daha büyük burst'lere izin verilir; doldurma hızı artırılırsa ortalama kabul edilen istek hızı yükselir.
Leaky Bucket Algoritması: Trafiği Düzenli Akışa Çevirme
Leaky Bucket (delikli kova), Token Bucket'a benzer bir isimle anılsa da amacı biraz farklıdır. Bu yöntemde gelen istekler bir kuyruğa (kovaya) eklenir ve kuyruktaki istekler sabit bir hızla işlenip dışarı çıkarılır. Kovanın altındaki delikten su sızması gibi, istekler de düzenli ve öngörülebilir bir hızda sistemden akar.
Token Bucket ile arasındaki temel fark şudur: Token Bucket giriş tarafında esneklik tanırken (biriken tokenlarla burst'e izin verir), Leaky Bucket çıkış hızını sabit tutar. Yani istekler ne kadar düzensiz gelirse gelsin, sisteme yansıyan işlem hızı her zaman aynıdır.
Bu özellik, Leaky Bucket'ı trafiği düzgünleştirme (smoothing) konusunda güçlü kılar. Arka uç sistemlerin sabit ve öngörülebilir bir yükle çalışmasını istediğiniz senaryolarda, örneğin bir kuyruk işleme sistemi veya sınırlı kapasiteli bir arka servis söz konusu olduğunda tercih edilebilir.
Kuyruk kapasitesi dolduğunda ise yeni gelen istekler reddedilir. Bu, sistemin aşırı yüklenmesini önlerken, kuyruk taşması durumunda bazı isteklerin kaybedilmesi anlamına gelir; bu yüzden kuyruk boyutunun dikkatli belirlenmesi gerekir.
Rate Limiting Nerede Uygulanmalı?
Rate limiting tek bir katmanla sınırlı değildir; farklı seviyelerde uygulanabilir ve her birinin kendine özgü avantajları vardır.
İstemci tarafında öz disiplin yaklaşımı, istemci uygulamanın kendi isteklerini kontrollü bir hızda göndermesi anlamına gelir. Bu, iyi niyetli istemciler için faydalı olsa da kötü niyetli veya hatalı istemcilere karşı tek başına yeterli bir koruma sağlamaz.
API Gateway seviyesinde uygulama, rate limiting'in merkezi ve tutarlı bir şekilde yönetilmesini sağlar. Gateway, tüm istekler sunucuya ulaşmadan önce araya girdiği için, geçersiz veya aşırı istekleri daha erken aşamada eleyebilir. Bu, sunucu kaynaklarının gereksiz yere harcanmasını önler ve farklı servisler arasında tutarlı bir politika uygulamayı kolaylaştırır.
Sunucu tarafında, uygulama mantığına gömülü kontrol de mümkündür. Bu yaklaşım, iş mantığına özgü daha ince ayrıntılı kurallar (örneğin kullanıcı rolüne göre farklı limitler) uygulamak için faydalı olabilir.
Pratikte en sağlam çözüm genellikle katmanlı bir yaklaşımdır: genel ve kaba limitler gateway seviyesinde uygulanırken, daha özel ve iş mantığına bağlı kurallar sunucu tarafında ele alınır. Bu kombinasyon, hem performans hem de esneklik açısından avantaj sağlar.
İstemciye Durumu Bildirme: HTTP Kodları ve Başlıklar
Rate limiting uygulandığında, istemcinin neden reddedildiğini anlaması önemlidir. Bunun standart yolu 429 Too Many Requests HTTP durum kodunu döndürmektir. Bu kod, isteğin sunucu tarafından anlaşıldığını ama istemcinin kısa sürede çok fazla istek gönderdiği için reddedildiğini açıkça belirtir.
Bununla birlikte, istemciye ne zaman tekrar deneyebileceğini bildirmek için Retry-After başlığı kullanılabilir. Bu başlık, istemciye kaç saniye sonra yeniden istek gönderebileceğini belirtir ve gereksiz tekrar denemelerin önüne geçer.
Ayrıca birçok API, istemciye daha fazla şeffaflık sağlamak amacıyla kalan hak sayısı ve limitin sıfırlanma zamanı gibi bilgileri özel başlıklar aracılığıyla iletir. Bu tür bilgilendirici başlıklar, istemci geliştiricilerin kendi uygulamalarını buna göre optimize etmesine yardımcı olur.
İyi tasarlanmış hata yanıtları, geliştirici deneyimini doğrudan etkiler. Sadece "başarısız" demek yerine, neden başarısız olunduğunu ve ne zaman tekrar denenebileceğini net bir şekilde belirtmek, API'nizi kullanan geliştiricilerin işini önemli ölçüde kolaylaştırır.
Dağıtık Sistemlerde Rate Limiting Zorlukları
Tek bir sunucu üzerinde rate limiting uygulamak nispeten basittir; sayaçlar sunucunun belleğinde tutulabilir. Ancak gerçek dünyada çoğu API, yük dengeleyici arkasında birden fazla sunucu üzerinde çalışır. Bu durumda paylaşılan durum yönetimi önemli bir sorun haline gelir: bir istemcinin isteği bazen bir sunucuya, bazen başka bir sunucuya yönlendirilebilir ve her sunucu kendi yerel sayacını tutuyorsa, gerçek istek sayısı doğru şekilde takip edilemez.
Bu sorunu çözmek için genellikle merkezi bir depolama çözümü kullanılır; tüm sunucuların erişebildiği, hızlı okuma-yazma yapabilen paylaşılan bir önbellek sistemi bu amaçla tercih edilir. Böylece tüm sunucular aynı sayaç verisine erişerek tutarlı bir sınırlama uygulayabilir.
Ancak bu da kendi zorluklarını beraberinde getirir. Tutarlılık ile performans arasındaki denge kritik bir konudur: her istek için merkezi depoya gidip gelmek gecikme yaratabilir, ancak bu gecikmeyi azaltmak için yapılan optimizasyonlar (örneğin yerel önbellekleme) doğruluktan ödün verilmesine yol açabilir.
Ayrıca ağ gecikmesi ve senkronizasyon sorunları da göz ardı edilmemelidir. Dağıtık sistemlerde tam zamanlı mükemmel senkronizasyon genellikle pratik değildir; bu nedenle çoğu sistem, kabul edilebilir bir yaklaşık doğruluk seviyesiyle çalışmayı tercih eder.
Doğru Algoritmayı Seçerken Nelere Dikkat Edilmeli?
Hangi rate limiting algoritmasının seçileceği, birkaç önemli faktöre bağlıdır.
Öncelikle trafik örüntüsünü anlamak gerekir: API'nize gelen istekler düzenli bir akış mı izliyor, yoksa zaman zaman ani patlamalar (bursty trafik) mi yaşanıyor? Burst'lere izin vermek istiyorsanız Token Bucket gibi esnek algoritmalar daha uygun olabilir; sabit ve öngörülebilir bir çıkış hızı istiyorsanız Leaky Bucket tercih edilebilir.
İkinci faktör, doğruluk ihtiyacı ile kaynak tüketimi arasındaki ödünleşimdir. Sliding Window Log en yüksek doğruluğu sunar ama bellek maliyeti yüksektir; Fixed Window Counter ise düşük maliyetli ama daha az hassastır. Çoğu gerçek dünya senaryosu için Sliding Window Counter veya Token Bucket makul bir denge noktası sunar.
Üçüncüsü, uygulama karmaşıklığı ve bakım kolaylığı göz önünde bulundurulmalıdır. Basit bir sistem için karmaşık bir algoritma seçmek gereksiz mühendislik yükü yaratabilir.
Dördüncüsü, sistem büyüklüğü ve dağıtık mimari gereksinimleri önemlidir. Tek sunuculu küçük bir sistemle, yüzlerce sunucuya dağıtılmış büyük ölçekli bir sistemin ihtiyaçları çok farklıdır; ikincisi için merkezi durum yönetimi kaçınılmaz hale gelir.
Genel öneri olarak, basit başlayıp ihtiyaca göre geliştirme yaklaşımı mantıklıdır. Küçük veya orta ölçekli bir API için Fixed Window Counter ya da Token Bucket ile başlamak, sistem büyüdükçe ve gereksinimler netleştikçe daha gelişmiş çözümlere geçmek, hem geliştirme hem de işletme açısından daha sürdürülebilir bir yoldur.



