Geçen yılın Kasım ayında, araç takip API'mizi kullanan bir entegrasyon ortağının sunucusunda unutulmuş bir retry döngüsü, bir gecede 3,2 milyon istek üretti. Sabah altıda uyandığımda veritabanı bağlantı havuzu dolmuş, aynı API'yi kullanan diğer on iki müşteri de yavaşlamadan nasibini almıştı. İşin acı tarafı şu: rate limiting altyapımız vardı. Ama limiti "kimse bu kadar istek atmaz canım" mantığıyla, dakikada on bin gibi kağıt üzerinde makul görünen bir değere kurmuştuk. O gece öğrendik ki rate limiting bir açma-kapama düğmesi değil, katman katman düşünülmesi gereken bir tasarım problemi.
Bu yazıda o geceden sonra oturttuğumuz yaklaşımı anlatacağım. Teori değil; şu an üretimde, günde ortalama 40 milyon isteği karşılayan sistemlerde çalışan hâliyle.
Sabit pencere neden bizi yanılttı
İlk kurulumumuz klasik fixed window sayacıydı: her kullanıcı için dakikalık bir sayaç, limit aşılınca 429. Basit, Redis'te tek INCR ile çözülüyor, anlaması kolay. Sorun şu ki pencere sınırlarında çirkin bir açık var: bir istemci 10.000 isteği dakikanın son saniyesinde, 10.000 isteği de yeni dakikanın ilk saniyesinde atarsa, iki saniyede 20.000 istekle karşılaşıyorsunuz ve teknik olarak hiçbir limit ihlal edilmemiş oluyor. Bizim o gece yaşadığımız yükün önemli kısmı tam bu desenden geldi; retry döngüsü tesadüfen pencere geçişleriyle senkronize olmuştu.
Token bucket'a geçişimiz bu yüzden oldu. Mantığı seviyorum çünkü gerçek dünyadaki kullanım desenine saygılı: her istemcinin bir kovası var, kova sabit hızda dolar (mesela saniyede 50 jeton), her istek bir jeton harcar. İstemci kısa bir patlama yapabilir — kovada birikmiş jeton kadar — ama sürekli yüksek tempoda gidemez. Mobil uygulamalar için bu kritik; kullanıcı uygulamayı açtığında ekran birkaç isteği aynı anda atar, sonra dakikalarca sessizlik olur. Sabit pencere bu deseni cezalandırır, token bucket hoş görür.
Tek limit yetmiyor: kota piramidi
Asıl olgunlaşma, tek bir limitin hiçbir zaman yeterli olmadığını kabul etmekle geldi. Bugün API'lerimizde limitleri piramit gibi diziyoruz:
- IP başına saniyelik limit: botlara ve yanlış yapılandırılmış istemcilere karşı ilk savunma hattı, uygulama sunucusuna hiç değmeden edge'de çalışıyor.
- API anahtarı başına token bucket: asıl iş yükü kontrolü burada; patlamaya izin veriyor, sürekli istismarı kesiyor.
- Kullanıcı/tenant başına günlük ve aylık kota: ticari sözleşmenin teknik karşılığı; "ayda 1 milyon istek" planı buradan yönetiliyor.
- Endpoint bazlı ağırlık: rapor üreten bir endpoint ile basit bir durum sorgusu aynı jetonu harcamamalı; pahalı endpoint'ler 5-10 jeton yiyor.
Endpoint ağırlığı konusu genelde atlanıyor ama bizim için oyun değiştirici oldu. Filo raporu üreten bir sorgu, veritabanında tek araç konumu sorgusunun yaklaşık iki yüz katı iş yapıyor. İkisini aynı limitte tutmak, ucuz isteklere haksızlık, pahalı isteklere davetiye.
429 dönmek yetmez, doğru dönmek gerekir
Limitin kendisi kadar önemli olan şey, limite takılan istemciye ne söylediğiniz. Sadece 429 status dönüp susmak, karşı taraftaki geliştiriciyi köreltir; ne zaman tekrar deneyeceğini bilmediği için ya agresif retry yapar (sorunu büyütür) ya da entegrasyonu bırakır (müşteriyi kaybedersiniz).
Bizim standardımız: her yanıtta kalan jeton sayısı, limit ve sıfırlanma zamanı header'ları; 429 yanıtında ise mutlaka Retry-After. Karşı taraf bu header'a saygı duyduğunda retry fırtınaları kendiliğinden sönümleniyor. O Kasım gecesinden sonra entegrasyon ortağımızla oturduk, retry mantığına exponential backoff ve jitter ekletttik; aynı senaryoyu staging'de tekrar oynattığımızda 3,2 milyon istek 40 bine düştü. Kod tarafında değişen şey topu topu yirmi satırdı.
Bir ayrıntı daha: 429 gövdesine insan diliyle bir açıklama koyun. "Rate limit exceeded" yazan bir gövde ile "Dakikalık limitinize ulaştınız; 42 saniye sonra tekrar deneyebilir veya panelden planınızı yükseltebilirsiniz" yazan bir gövde arasındaki fark, destek kutunuza düşen ticket sayısında ölçülüyor. Biz ikinci formata geçtikten sonra "API'niz bozuk, hata veriyor" başlıklı e-postalar neredeyse kayboldu; geliştiriciler hatayı sorun olarak değil, sistemin öngörülebilir bir cevabı olarak okumaya başladı.
Bir de şu var: limit aşımını istemciye söylemeden önce kendinize söyleyin. Rate limit ihlalleri bizim için birinci sınıf metrik; hangi anahtar, hangi endpoint, hangi saatte. Çünkü bu grafik çoğu zaman bir saldırıyı değil, bir müşterinin büyüdüğünü gösteriyor — ve büyüyen müşteriye limit hatası göstermeden önce paket yükseltme teklifi götürmek her iki taraf için de daha tatlı bir konuşma.
Dağıtık sistemde sayaç tutmanın sancısı
Tek sunucuda rate limiting öğrenci ödevi kadar kolay. Üç bölgede, on beş instance'ta çalışan bir API'de ise "kullanıcı kaç istek attı" sorusunun cevabı felsefi bir mesele hâline geliyor. Merkezi Redis ile tam tutarlılık alırsınız ama her isteğe bir ağ turu eklersiniz; yerel sayaçlarla hız alırsınız ama limitler instance sayısı kadar şişer.
Bizim dengemiz karma: saniyelik limitler her instance'ta yerel ve yaklaşık (biraz taşarsa dünyanın sonu değil), günlük ve aylık kotalar ise Redis'te kesin. Yaklaşık sayaçların taşma payını limitin yüzde onu olarak kabul ediyoruz ve bunu müşteri sözleşmelerine de dürüstçe böyle yansıtıyoruz. Mükemmel tutarlılık uğruna her isteğe 2 ms eklemek, korumaya çalıştığınız performansı limitin kendisiyle öldürmek demek.
Bu arada 2026'nın yeni gerçeği: API tüketicilerinizin giderek artan kısmı artık insan yazılımı değil, ajan. MCP sunucumuz üzerinden API'mize bağlanan yapay zeka ajanları, klasik istemcilerden çok daha değişken desenlerle geliyor; bir ajan bir görev için on saniyede yüz istek atıp sonra saatlerce kaybolabiliyor. Token bucket bu dünyada iyice değerlendi, çünkü patlamalı kullanım artık istisna değil norm. Ajan trafiği için ayrı anahtar sınıfı ve ayrı kova boyutları tanımladık; insan kullanıcıyla ajanı aynı kefeye koymak iki tarafı da mutsuz ediyor.
Son bir uyarı da kendi iç servisleriniz için. Rate limiting'i sadece dış dünyaya karşı bir kalkan sanmak yaygın bir hata; bizim yaşadığımız en pahalı iki kesintiden biri, kendi mobil uygulamamızın hatalı bir sürümünün API'yi döverek çökertmesiydi. İç istemcilere de anahtar veriyoruz, onların da kovası var. Kendi kodunuza güvenmek güzel; ama bir yıl önce yayınlanmış ve mağazadan silemediğiniz eski bir uygulama sürümü de "kendi kodunuz" ve o sürümün ne yaptığını artık kontrol edemiyorsunuz.
Rate limiting'i sonradan eklemeye çalışmak, kalabalık bir binaya sonradan yangın merdiveni yapmak gibi; mümkün ama pahalı ve çirkin. Yeni bir API tasarlıyorsanız ilk sprint'ten itibaren kotayı düşünün. Mevcut API'niz için nereden başlayacağınızı bilmiyorsanız bir çay içimi konuşalım; hangi katmanın sizin trafiğinize uyduğunu yarım günlük bir analizle kabaca çıkarabiliyoruz.
O gece uyandıran telefondan bu yana API'lerimizde bir tane bile kotasız endpoint yok. Bir tane bile.