Herkese açık her API'nin bir hız sınırlayıcıya ihtiyacı vardır ve çoğu ekip bunu aynı şekilde keşfeder: yeniden deneme döngüsündeki tek bir istemci ya da tek bir kazıyıcı servisi herkes için çökertir. Sınırlayıcı, tek bir anahtarın, kullanıcının ya da IP'nin ne kadar istek yapabileceğini kısıtlar; böylece kötü niyetli tek bir aktör size bir kesintiye değil, bir 429'a mal olur.
Tasarım soruları hep aynıdır: hangi algoritma, sınırlayıcı nerede durur, sayaçlar nerede yaşar ve sayaç deposu çöktüğünde ne olur. Bu yazı her birini yanıtlıyor ve bir API'nin önündeki dağıtık sınırlayıcının mimari diyagramıyla bitiyor.
Sınırlayıcının yapması gerekenler
- Her istekte, bir isteğe izin verilip verilmeyeceğine birkaç milisaniyede karar vermek.
- Farklı çağıranlara farklı kurallar uygulamak: API anahtarı, kullanıcı, IP ve rota başına.
- İsteği hangi sunucu örneği işlerse işlesin aynı davranmak.
- İstemcilere ne zaman sınırlandıklarını ve ne zaman yeniden deneyeceklerini açıkça söylemek.
- Kendi deposunda sorun olduğunda çalışmaya devam etmek ya da öngörülebilir biçimde başarısız olmak.
Bunu bir dağıtık sistemler problemine çeviren üçüncü maddedir. On ağ geçidi örneğinin her biri kendi 100'ünü sayıyorsa, dakikada 100 isteklik bir limitin hiçbir anlamı yoktur.
Beş algoritma
| Algoritma | Nasıl sayar | Güçlü yanı | Zayıf yanı |
|---|---|---|---|
| Token bucket | Kova sabit hızla dolar; her istek bir token alır | Kova boyutuna kadar kısa patlamalara izin verir | Ayarlanacak iki parametre |
| Leaky bucket | İstekler bir kuyruktan sabit hızla akar | Düzgün çıkış hızı | Patlamalar kuyrukta bekler ve gecikme ekler |
| Sabit pencere sayacı | Anahtar başına dakikada bir sayaç | En ucuzu: tek bir artırma | Pencere sınırında limitin iki katı geçebilir |
| Kayan pencere logu | Her istek için bir zaman damgası saklar | Kesin | Bellek trafikle büyür |
| Kayan pencere sayacı | Önceki pencerenin sayısını örtüşmeye göre ağırlıklandırır | Kesine yakın, anahtar başına iki sayaç | Bir yaklaşık hesap |
Sabit pencere sorunu kolay görülür: bir istemci 12:00:59'da 100, 12:01:00'da 100 istek daha gönderir ve iki pencere de bunları kabul eder. Dakikada 100 limitine karşı iki saniyede 200 istek demektir.
API'ler için genel varsayılan token bucket'tır, çünkü patlamalar normal istemci davranışıdır. "Dakikada 100 istek, 20'lik patlama" gibi bir limit doğrudan bir dolum hızına ve bir kova boyutuna karşılık gelir. Stripe, hız sınırlayıcılarla bir API'yi ölçeklemek üzerine yazısında Redis üzerinde token bucket sınırlayıcılar çalıştırdığını ve bunun yanında eşzamanlı istekler için ayrı bir sınırlayıcı kullandığını anlattı.
Pencere başına katı bir limit tercih ettiğinizde, sınır sorununu ucuza ortadan kaldırmanın yolu kayan pencere sayaçlarıdır.
Sınırlayıcı nerede durur
Limitleri üç yerde uygulayabilirsiniz ve birçok sistem birden fazlasını kullanır:
- Uçta (CDN ya da WAF). Size ulaşmadan selleri emmek için IP başına kaba limitler.
- API ağ geçidinde. Çağıranın kim olduğunu bilmesi gereken anahtar ve rota başına limitler. Ürün limitlerinin çoğu burada yaşar.
- Bir servisin içinde. "Hesap başına iki eşzamanlı dışa aktarma" gibi iş verisine bağlı limitler.
Bir ağ geçidi yerel (her örneğin kendi sayaçları) ya da global (tüm örnekler bir depoyu paylaşır) sayabilir. Yerel limitler bedavadır ve kaba koruma için yeterlidir; limiti örnek sayısına bölün ve hatayı kabul edin. Müşterilerin parasını ödediği ürün limitleri global bir depo ister. Örneğin Envoy ikisini de destekler: yerel bir hız sınırı filtresi ve Redis destekli harici bir hız sınırı servisini çağıran global bir mod.
Redis'te sayaçlar
Global sayaçlar için yaygın seçim Redis'tir: hızlıdır, sayaçları bellekte tutar ve anahtarların süresini kendisi doldurur. Tuzak, bir sayacı okumakla geri yazmak arasındaki yarıştır. İki ağ geçidi 99 okur, ikisi de isteğe izin verir, ikisi de 100 yazar.
Her kararı atomik tutun. Sabit pencere için yeni bir anahtarda INCR ve ardından EXPIRE yeterlidir; Redis INCR belgeleri bunun üzerine kurulu hız sınırlama kalıplarını gösterir. Token bucket için kovayı okuyan, geçen süreye göre dolduran, bir token alan ve tek adımda geri yazan küçük bir Lua betiği çalıştırın. Redis bir betiği araya başka komut sokmadan çalıştırır; böylece eşzamanlı istekler tutarlı bir kova görür.
-- KEYS[1] = bucket key, ARGV = rate per second, capacity, now (ms), cost
local b = redis.call('HMGET', KEYS[1], 'tokens', 'ts')
local rate, cap, now, cost = tonumber(ARGV[1]), tonumber(ARGV[2]), tonumber(ARGV[3]), tonumber(ARGV[4])
local tokens = tonumber(b[1]) or cap
local ts = tonumber(b[2]) or now
tokens = math.min(cap, tokens + (now - ts) / 1000 * rate)
local allowed = tokens >= cost
if allowed then tokens = tokens - cost end
redis.call('HSET', KEYS[1], 'tokens', tokens, 'ts', now)
redis.call('PEXPIRE', KEYS[1], math.ceil(cap / rate * 1000))
return allowed and 1 or 0
Zamanı çağırandan geçirin ya da Redis'in TIME komutunu kullanın; birçok ağ geçidinin saatlerini karıştırmak tuhaf sonuçların yaygın bir kaynağıdır. Aynı Redis önbellek olarak da kullanılıyorsa tahliye politikasına dikkat edin: tahliye edilen bir kova, sıfırlanmış bir limit demektir. Önbellek stratejileri yazısı tahliye ayarlarını anlatır.
İstemciye söylemek
Sınırlanan bir isteğe RFC 6585 içinde tanımlanan 429 Too Many Requests ile yanıt verin ve beklenecek saniye sayısını içeren bir Retry-After başlığı ekleyin. İyi istemciler geri çekilir; diğerleri en azından net bir sinyal alır. Bir IETF taslağı (RateLimit header fields for HTTP), istemcilere limite takılmadan önce kotalarını ve kalan bütçelerini söyleyen standart başlıklar öneriyor; bu yazının yazıldığı tarihte (Ekim 2026) hâlâ taslak, bu yüzden oradaki başlık adlarını geçici kabul edin.
Limitleri belgeleyin. İstemcilerin engellenerek keşfettiği bir limit destek talebine dönüşür.
Redis çöktüğünde
Bunu olmadan önce kararlaştırın:
- Açık kalarak başarısız olmak (fail open). Sınırlayıcı karar veremediğinde isteklere izin verin. API ayakta kalır, o süre boyunca korumayı kaybedersiniz. Ürün limitleri için olağan seçim budur.
- Kapanarak başarısız olmak (fail closed). İstekleri reddedin. Giriş denemeleri ya da SMS gönderimi gibi pahalı veya kötüye kullanıma açık işlemler için doğrusu budur.
- Yerel limitlere düşmek. Depo geri gelene kadar her ağ geçidi kaba bir yerel limit uygular.
Hangisini seçerseniz seçin, bunun için uyarı kurun. Bir hafta boyunca sessizce açık kalan bir sınırlayıcı, sınırlayıcı değildir.
Mimari diyagram
Diyagram içeriği
- API clients
- Load balancer
- API gateway
- Rate limit service
- Redis: counters
- Under the limit?
- Orders API
- 429 + Retry-After
- Metrics
- key + route
- atomic Lua script
- yes
- no
- allowed vs rejected
Nasıl okunur:
- Soldan sağa istek yoludur: istemciler, yük dengeleyici, API ağ geçidi ve hız sınırı servisi.
- Hız sınırı servisi kuralların sahibidir ve istek başına bir karar verir. Sayaçları Redis'te tutar ve atomik bir Lua betiğiyle günceller.
- Karar düğümü izin verilen istekleri Orders API'ye gönderir. Geri kalanlar Retry-After ile bir 429 alır ve backend'e hiç ulaşmaz.
- Metrikler asenkron raporlandığı için kesiklidir. Bir müşteri engellendiğini söylediğinde ilk bakılacak şey kural başına reddedilen sayılardır.
- Markdown bloğu kuralları, hata modu dahil, kontrol edildikleri sırayla listeler.
Diyagrama neler koymalı
- Kurallar ve anahtarları. "100/dk", "API anahtarı başına" ya da "IP başına" olmadan anlamsızdır.
- Yerel ya da global sayım. Limitlerin örnekler arasında geçerli olup olmayacağını bu fark belirler.
- Hata modu. Açık ya da kapalı başarısız olma yalnızca kodda değil, diyagramda da yer almalı.
- Ret yolu. 429'un nerede üretildiğini gösterin ki kimse backend'in o istekleri gördüğünü sanmasın.
Ağ geçidinin kendisi için API ağ geçidi mimari diyagramı yönlendirmeyi, kimlik doğrulamayı ve sınırlayıcının yanında çalışan diğer filtreleri anlatır. Sistem tasarımı diyagramı örneği bir sınırlayıcının daha büyük bir tasarıma nasıl oturduğunu gösterir.
Archboard'da oluşturun
- Google ya da GitHub ile giriş yapın ve bir diyagram oluşturun.
- İstemciler için C4 Modeli paketinden bir Kişi, ardından Bulut paketinden bir Bulut yük dengeleyici ve bir API ağ geçidi ekleyin.
- C4 Modeli paketinden iki Konteyner düğümü ekleyin: hız sınırı servisi ve Orders API. Hız sınırı servisinin altına Veri paketinden Redis ekleyin.
- İzin ver ya da reddet dalı için Akış Şeması paketinden bir Karar ve bir Başla / Bitir, DevOps paketinden de Metrikler ekleyin.
- Kenarları etiketleyin, metrik kenarını kesikli çizin ve kuralları içeren, Temel paketinden bir Markdown bloğu ekleyin.
Örnek 10 diyagram düğümü kullanıyor ve Free planın sınırları içinde kalıyor.
SSS
Hangi hız sınırlama algoritmasını kullanmalıyım? Çoğu API için token bucket, çünkü gerçek istemcilerin ürettiği kısa patlamalara izin verir. Tam bir istek logunun maliyeti olmadan pencere başına katı bir limite ihtiyacınız varsa kayan pencere sayacı kullanın.
Hız sınırlayıcı ayrı bir servis mi olmalı? Şart değil. Paylaşılan bir Redis'le çalışan bir ağ geçidi filtresi birçok sistem için yeterlidir. Birçok ağ geçidi ve servis aynı kuralları paylaştığında ayrı bir servis işe yarar.
Sınırlanan bir istek hangi durum kodunu almalı? Retry-After başlığıyla birlikte 429 Too Many Requests. 503 kullanmayın; istemcilere çok fazla istekte bulunduklarını değil, servisinizin çöktüğünü söyler.
İstekler birçok sunucudan geçerken kullanıcı başına nasıl sınırlarım? Sayaçları kullanıcıya ya da API anahtarına göre anahtarlanmış biçimde Redis gibi paylaşılan bir depoda tutun ve atomik olarak güncelleyin. Sunucu başına yerel sayaçlar ancak kaba bir üst sınır olarak işe yarar.
Hız sınırlayıcının kendisi başarısız olursa ne olur? Önceden seçin. Normal ürün limitleri için açık kalarak, pahalı ya da kötüye kullanıma açık işlemler için kapanarak başarısız olun ve iki durumda da uyarı kurun.