Herkese açık her API, bir gün aynı soruyla karşılaşır: bu isteği kaçıncı kez sorabilirsin? Rate limiting bu sorunun cevabıdır; ama tek bir cevap değil, dört farklı desen vardır. Her desen, adil paylaşım ile hız arasındaki dengeyi farklı bir yerde kurar ve farklı bir hata türünü getiriştir.

Sınırlamanın amacı da tartışılmayı hak eder: yalnızca kötücül trafiği kesmek mi, yoksa tüm kullanıcıların hizmetten adil pay almasını mı sağlamak? Birincisi için kabaca bir duvar yeterliyken, ikincisi için hesabı doğru tutan bir desen gerekir. Desen seçimi, işte bu amaca göre yapılmalıdır. Ayrıca sınırın ötesinde kalan isteklere nasıl davranılacağını da erken kararlaştırın: reddetmek, bekletmek ya da daha yavaş işlemek — üçü arasında seçim, deseni seçmek kadar önemlidir.

Sabit ve Kayan Pencere

En basit desen, sabit pencerelere bölmektir: dakikada 60 istek izni, dakika başında sıfırlanır. Uygulaması bir sayaç kadar kolaydır; ama pencere sınırında patlar: biri dakikanın son saniyesinde 60, öteki yeni dakikanın ilk saniyesinde 60 istek atarsa, iki saniyede 120 istek geçmiş olur.

const sayac = await redis.incr(`limit:${ip}:${dakika}`);
if (sayac > SINIR) return new Response('429 Too Many Requests', { status: 429 });

Kayan pencere, bu sınır etkisini yumuşatır: son bir dakikalık dilime bakar, sabit bir döneme değil. Ama tam olarak esneklik sağlamak için ayrıntılı zaman damgası saklamak gerekir; tahmini sürümleri, bu maliyeti azaltmak için ağırlıklı ortalama kullanır.

Token Sepeti: Patlamaya İzin Veren Denge

Token sepeti (token bucket) desende farklı bir felsefe vardır: istekler sabit hızla sepete damlayan jetonlarla harcanır. Sepet kapasitesi, izin verilen kısa süreli patlamayı (burst) belirler; damlama hızı ise sürekli tüketilebilecek ortalama yükü. Bir istemci normalde sakin davranıp ara sıra hızlanmak istiyorsa, bu desen onu cezalandırmaz. Örneğin bir e-posta gönderim uç noktası, dakikada on ile sınırlıyken beş dakikalık sessizlik sonrası kısa bir seri gönderime izin verebilir: jetonlar sessizlikte birikmiştir.

const sepet = await sepetiGetir(ip);
sepet.jeton = Math.min(sepet.kapasite, sepet.jeton + damlaHizi * gecenSure);
if (sepet.jeton >= 1) {
  sepet.jeton -= 1;
  await sepetiKaydet(ip, sepet);
  return devam();
}
return new Response('429 Too Many Requests', { status: 429 });

Kaygan kova (leaky bucket) ise tersinden çalışır: istekleri bir kuyruğa alır ve sabit hızda boşaltır; girişten bağımsız olarak çıkış hızı sabittir. Ani yükü yumuşatmak isteyen, örneğin dış servise vereceği isteklerde bu desen mükemmeldir.

İki desen arasındaki farkı tek cümleyle özetlemek gerekirse: token sepeti girişi düzenler, kaygan kova çıkışı. Hangisinin sizin sorununuz olduğuna karar vermeden önce, düzenlemeniz gereken tarafın giriş mi çıkış mı olduğunu netleştirin.

Hangi Desen Nerede?

Sabit pencere, yönetim paneli gibi düşük riskli uçlarda hâlâ yeterlidir. Kayan pencere, istemci adına adalet vaat eden genel API'lerde mantıklıdır. Token sepeti, kullanıcı patlamalarını hoş gören etkileşimli ürünlerde; kaygan kova ise dışa doğru veri çeken entegrasyonlardadır.

Bir de sınırı kimin üzerinde tutacağına karar vermek gerekir: IP başına mı, kullanıcı başına mı, uç nokta başına mı? IP tabanlı sayaç, aynı ofisi paylaşan on dürüst kullanıcıyı tek müşteri gibi cezalandırır; kullanıcı tabanlı sayaç ise anonim keşif aşamasında işe yaramaz. Çoğu uygulamada ikisinin bileşimi doğru cevaptır.

Doğru desen yok, doğru denge vardır: patlamayı hoş görmek ile ortalama yükü sınırlamak arasında tercih yapın. Ve istemcinize 429'la birlikte ne zaman tekrar deneyebileceğini söyleyin; iyi bir sınır, cezalandıran değil yönlendirendir.