Tek bir istemci tüm API'yi yavaşlatıyor: hız sınırlama (rate limiting) ve kota tasarımı
Bir istemci API'nizi yavaşlattığında sebep genelde saldırı değil, döngüye girmiş dürüst bir entegrasyondur. Hız sınırlama (rate limiting) bu işi tek bir ayarla çözmez, dört ayrı karar ister: neye göre sayacaksınız (IP, API anahtarı, hesap, uç nokta), hangi yöntemle sayacaksınız (sabit pencere, kayan pencere, token kovası), sınır aşıldığında ne yapacaksınız (reddetme, yavaşlatma, kuyruğa alma) ve istemciye bunu nasıl anlatacaksınız (429, Retry-After, kalan kota başlıkları). Üstüne birbirine karıştırılan iki sınır daha geliyor: eşzamanlılık sınırı ve ticari kota. Bu altısını ayrı ayrı tasarlamazsanız elinizde ya meşru müşteriyi kapıda tutan ya da hiçbir şeyi gerçekten durdurmayan bir kural kalır.
Sınır koymanın üç farklı gerekçesi var
Birincisi kötüye kullanım: veri kazıma, hesap deneme, toplu kayıt açma. İkincisi kaza: yanlış yapılandırılmış bir zamanlanmış görev, sayfalama döngüsünden çıkamayan bir istemci, kesintiden sonra hepsi aynı saniyede geri dönen yeniden deneme yığını. Üçüncüsü para: her isteğin arkasında ölçülen bir maliyet varsa (dil modeli çağrısı, SMS, kargo sorgusu, veri çıkışı) sınırsız uç nokta doğrudan fatura riskidir.
İlk gerekçenin ağırlığı artıyor. Imperva'nın 2026 bot raporuna göre 2025'te web trafiğinin %53'ü otomatik trafikti, insan trafiği %47'ye indi ve bot saldırılarının %27'si arayüzü hiç kullanmadan doğrudan API'leri hedefledi. İkinci ve üçüncü gerekçe ise çoğu ekipte daha sık gerçekleşiyor: gördüğümüz vakaların büyük kısmında sınırı dolduran, sözleşmesi olan bir müşterinin kendi kodudur.
Neye göre sayıyorsunuz? Anahtar seçimi kararın yarısı
IP adresi en kolay anahtar ve en yanıltıcı olanı. Tek bir ofisin tamamı NAT arkasında tek adresle çıkar, mobil operatörlerde binlerce abone aynı adresi paylaşır, yani IP bazlı sıkı bir sınır meşru bir müşterinin tüm çalışanlarını birlikte cezalandırır. Ters tarafı da var: IPv6'da tek bir aboneye genelde /64 ya da daha geniş bir blok verilir, dolayısıyla tek adres saymak hiçbir şey saymamaktır, prefiks bazında saymanız gerekir.
Bir de önünüzdeki katman sorunu. Uygulamanız bir CDN ya da yük dengeleyici arkasındaysa gördüğü IP o katmanın IP'sidir, gerçek istemci X-Forwarded-For başlığındadır ve bu başlığı yalnızca kendi vekil sunucunuza güvenerek okumanız gerekir. Herkesten gelen başlığa güvenirseniz saldırgan başlığı uydurup sınırı tamamen atlar. Vekil sunucu başlıklarının neden yerelde hiç görünmediğini ortam farkı yazısında anlattık.
Kimlik doğrulanmış trafikte doğru birim IP değil, hesap ya da API anahtarıdır. Çok kiracılı bir kurulumda kiracı bazlı sınır, bir müşterinin diğerlerini yavaşlatmasına karşı ilk ve en ucuz araçtır (çok kiracılı SaaS mimarisi).
Giriş uç noktası ayrı bir vaka ve iki anahtarı birlikte ister. Yalnızca hesap bazında sayarsanız saldırgan kasten sınırı doldurup gerçek kullanıcıyı kapıda bırakır, yani sınır bir hizmet engelleme aracına dönüşür. Yalnızca IP bazında sayarsanız binlerce adrese yayılmış kimlik bilgisi doldurma (credential stuffing) hiç görünmez. İkisini birlikte sayın ve kilitleme politikasını kimlik doğrulama yazısındaki çerçeveyle kurun.
Hız, eşzamanlılık ve kota aynı şey değil
Stripe'ın yayımladığı sınırlar bu ayrımın iyi bir örneği. Canlı modda hesap başına global sınır saniyede 100 istek, tek tek uç noktalar için varsayılan saniyede 25 istek, sandbox ortamında global sınır 25. Bunların yanında ayrı bir eşzamanlılık sınırı var ve o saniyedeki hızı değil, o anda işlenmekte olan istek sayısını sayıyor. 429 yanıtında dönen Stripe-Rate-Limited-Reason başlığı hangi sınırın dolduğunu söylüyor: global-rate, endpoint-rate, global-concurrency, endpoint-concurrency ya da resource-specific.
Üçüncü tür nesne bazlı: bir PaymentIntent için saatte en fazla 1000 güncelleme, bir abonelik için dakikada 10 yeni fatura. Dördüncüsü tamamen ticari: okuma isteklerinde yuvarlanan 30 günde işlem başına ortalama 500 istek tahsisi, her hesap için de aylık en az 10.000 okuma.
Aynı ürünün içinde dört farklı sınır mantığı çalışıyor ve hiçbiri diğerinin yerini tutmuyor. Sizin tasarımınızda da bunları ayırmanız gerekiyor, çünkü "saniyede kaç istek" sorusu tek başına ne uzun süren ağır sorguları ne de ay sonundaki ticari hakkı kapsıyor.
Sabit pencere sizi yanıltır
En sık yapılan kurulum, dakika başına sıfırlanan bir sayaç. Sorun sınırda ortaya çıkıyor: dakikada 100 istek sınırı varsa istemci 00:59'da 100 istek, 01:00'da 100 istek daha gönderebilir, yani iki saniye içinde 200. Arka tarafta koruduğunuz veritabanı için bu, sınır hiç yokmuş gibidir. İkinci sorun sürü etkisi: pencere dakika başında sıfırlandığı için tüm istemciler saat gibi aynı anda vurur.
Kayan pencere (son 60 saniyeyi gerçekten geriye doğru sayan yöntem) bu iki sorunu çözer ama daha fazla durum tutar. Pratikte yaygın olan orta yol, iki komşu pencerenin sayaçlarını ağırlıklandırarak yaklaşık bir kayan pencere hesaplamak.
Üçüncü ve genelde en iyi seçenek kova modelleri. Token kovası (token bucket) kova kapasitesi kadar patlamaya izin verir, uzun vadede ortalamayı doldurma hızına oturtur. AWS API Gateway istekleri bu algoritmayla kısıtlıyor ve ayarı iki parçadan oluşuyor: hız (kovaya saniyede eklenen token) ve patlama (kovanın kapasitesi). Sızan kova (leaky bucket) da aynı ailenin üyesi. Nginx'in limit_req modülü bunu uygular:
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req zone=api burst=20 nodelay;
Shopify da tüm API'lerinde sızan kova kullanıyor, kova boyu ve doldurma hızı API'ye ve müşterinin planına göre değişiyor. Bu modelin pratik değeri şu: gerçek istemciler trafiği tekdüze göndermez, patlamalı gönderir. Patlama payını birkaç saniyelik hıza denk ayarlarsanız meşru istemciyi engellemeden ortalamayı korursunuz.
Her istek aynı maliyette değil
İstek saymak, bir GET ile on tabloyu tarayan bir rapor sorgusunu eşit kabul etmek demektir. GitHub bu yüzden puan sistemine geçmiş. REST tarafında GET, HEAD ve OPTIONS 1 puan, POST, PATCH, PUT ve DELETE 5 puan ve uç nokta başına dakikada en fazla 900 puan harcanabiliyor. Birincil sınır kimlik doğrulanmış kullanıcı için saatte 5.000 istek, kimlik doğrulanmamış istekler için IP başına saatte 60.
İkincil sınırlar listesi daha öğretici: aynı anda en fazla 100 eşzamanlı istek, 60 saniyelik gerçek zamanda en fazla 90 saniye CPU süresi, dakikada 80 ve saatte 500 içerik üreten istek. GraphQL tarafında sayım tamamen maliyete dayanıyor, saatte 5.000 puan ve tek sorguda en fazla 500.000 düğüm; maliyet, sorgudaki sayfa boyutlarından hesaplanıyor.
Buradan çıkan kural basit. Uç noktalarınızın maliyeti arasında yüz kat fark varsa istek sayısı yanlış ölçüdür. İki çözüm var: ya isteklere puan ağırlığı verin, ya da pahalı uç noktalara (arama, rapor, dışa aktarma, toplu içe aktarma) kendi ayrı ve düşük sınırlarını koyun. Hangi sorgunun neden pahalı olduğunu bulmak için veritabanı darboğazları ve site içi arama yazılarına bakabilirsiniz.
Reddetmek son seçenek olsun
429 dönmek en kolay tepki ama tek tepki değil. Üç alternatif genelde daha iyi sonuç veriyor.
Yavaşlatma: nginx'te nodelay yazmazsanız patlama payına düşen istekler reddedilmez, hıza uyacak şekilde kuyruklanıp geciktirilir. İstemci biraz bekler ve işi yapılır. Bu, dakikada bir çalışan bir entegrasyon için 429 almaktan çok daha iyidir.
Kuyruğa alma: isteğin işi gerçekten ağırsa senkron yapmayın. İşi kuyruğa atıp 202 dönün, sonucu ayrı bir uç noktadan ya da bir webhook ile verin. Kuyruk tarafının nasıl kurulacağı arka plan işleri yazısında duruyor.
Yük atma ve önceliklendirme: Stripe'ın mühendislik blogunda anlattığı dört sınırlayıcıdan ikisi tam bu işi yapıyor. Filo kullanımına bakan katman kritik istekler için kapasitenin bir kısmını sürekli ayrı tutuyor, işçi kullanımına bakan ikinci katman ise yoğunlukta düşük öncelikli ve test modundaki trafiği önce atıyor. Sizin karşılığınız şudur: ödeme ve giriş her koşulda geçsin, rapor ve dışa aktarma yoğunlukta ilk kısılan olsun. Aynı düşünce yük testi ve kapasite planlaması tarafında da geçerli.
İstemciye ne söylüyorsunuz?
Doğru durum kodu 429 Too Many Requests ve 2012'de RFC 6585 ile tanımlandı. Yanına Retry-After koyun; RFC 9110'un 10.2.3 bölümüne göre bu başlık ya saniye sayısı ya da bir HTTP tarihi taşır, ikisi de geçerlidir.
Küçük ama sık atlanan bir ayrıntı: nginx limit_req sınırı aşıldığında varsayılan olarak 503 döner. limit_req_status 429; yazmazsanız istemci kütüphaneleri bunu bir hız sınırı değil, genel bir sunucu arızası olarak yorumlar ve geri çekilme davranışını buna göre seçemez.
Kalan kota başlıklarında fiilî standart, GitHub'ın da kullandığı biçim: x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-used, x-ratelimit-reset, x-ratelimit-resource. IETF'in bunu standartlaştırma çalışması hâlâ taslak aşamasında; draft-ietf-httpapi-ratelimit-headers Mayıs 2026'da 11. sürümde ve henüz RFC olmadı. Taslak iki alan tanımlıyor, biri politikayı biri anlık durumu bildiriyor:
RateLimit-Policy: "burst";q=100;w=60,"daily";q=1000;w=86400
RateLimit: "default";r=50;t=30
Taslağın kendi uyarısı da tasarımınıza girmeli: istemci bu değeri bir hizmet taahhüdü saymamalı, sunucu doygunluk anında bildirdiği kotayı düşürebilir.
Son olarak hata gövdesi hangi sınırın dolduğunu söylesin. Stripe'ın Stripe-Rate-Limited-Reason başlığı iyi bir örnek, çünkü "global mi, uç nokta mı, eşzamanlılık mı" bilgisi entegratörün doğru düzeltmeyi yapmasını sağlıyor. Bunu vermezseniz destek kuyruğunuz teşhis edilemeyen "429 alıyoruz" mesajlarıyla dolar. Sınırları dokümanınıza yazın ve bir sınırı sıkmanın entegratör için kırıcı bir değişiklik olduğunu unutmayın (sürümleme ve geriye dönük uyumluluk).
429'u alan taraf sizseniz
Kargo, e-fatura, ödeme ve dil modeli servislerinin hepsinin sınırı var, yani bu yazının diğer yarısı sizin istemci kodunuz.
Varsa Retry-After'a uyun, yoksa üstel geri çekilme uygulayın ve araya rastgelelik (jitter) ekleyin. Stripe da dokümantasyonunda bunu öneriyor ve gerekçesini açıkça yazıyor: rastgelelik olmadan tüm istemciler aynı anda geri döner ve sürü etkisi oluşur. Sabit bir saniyeyle üç kez denemek, sorunu çözmek yerine aynı yığını üç kez göndermektir.
İdempotent olmayan bir yazma isteğini körlemesine tekrar denemeyin. Ödeme gibi akışlarda idempotency anahtarı kullanın, yoksa "yeniden dene" mantığı çift kayıt üretir (ödeme entegrasyonu ve mutabakat).
İşçi havuzunuzun eşzamanlılığını da karşı tarafın sınırına göre kapatın. Elli işçinin paralel çalıştığı bir kuyruk, saniyede 10 istek kabul eden bir servise dakikalar içinde 429 yığını üretir ve yeniden denemeler bunun üstüne binince kuyruk temizlenmek yerine büyür. Geçici hata ile kalıcı hatayı ayırmak ve devre kesici koymak bu noktada gerekiyor (dış servis kesintisine dayanıklı uygulama).
En etkili önlem ise sınıra hiç çarpmamak: kendi tarafınızda bir token kovası kurup giden trafiği baştan kısın. Stripe'ın önerdiği yöntem de bu.
Üç sunucu, üç ayrı sayaç
Sınırı her örneğin kendi belleğinde tutuyorsanız etkin sınır örnek sayısıyla çarpılır. Üç kopya ve saniyede 100 istek sınırı, gerçekte saniyede 300 demektir. Otomatik ölçekleme varsa durum daha kötü: trafik arttıkça örnek sayısı artar, örnek sayısı arttıkça sınır gevşer, yani sınır tam ihtiyaç duyulduğu anda ortadan kalkar.
Ortak sayaç için yaygın çözüm Redis. Basit hâli atomik artırma ve süre sonu, daha iyisi tek bir betikle çalışan token kovası. Stripe'ın sınırlayıcıları da durumu Redis'te tutuyor.
Bu yolun iki bedeli var. Birincisi gecikme: sayaç artık her isteğin yolunda duran bir ağ çağrısıdır. İkincisi arıza davranışı: sayaç düşerse ne olacak? Stripe'ın kuralı hata durumunda açık kalmak (fail open), yani sınırlayıcıdaki bir arıza isteklerin reddedilmesine yol açmasın. Buna bir de yerel üst sınır ekleyin; aksi hâlde Redis kesintisi tüm korumayı aynı anda kaldırır.
Hacimli trafiği ise uygulamaya hiç getirmemek en iyisi. Cloudflare'ın hız sınırlama kurallarında plan farkı belirgin: ücretsiz planda tek kural, yalnızca IP'ye göre sayım ve 10 saniyelik pencere; Business planında özel sayma ifadeleri ve 10 dakikaya kadar pencere; Enterprise'da yanıt durum koduna veya başlığına göre sayma ve çok daha uzun pencereler. Kenar katmanı hacmi keser, iş mantığını uygulama katmanı korur ve ikisi birbirinin yerine geçmez (DDoS koruması).
Eşiği tahminle değil ölçümle koyun
Sınır sayısını toplantıda seçmek, ya hiç devreye girmeyen ya da ilk gün müşteriyi durduran bir değerle sonuçlanıyor. Daha güvenilir yol, bir hafta boyunca anahtar başına istek hızının dağılımını çıkarmak: en yoğun meşru istemcinin tepe değerini bulun, eşiği onun birkaç katına koyun ve pahalı uç noktalar için ayrı bir sayı belirleyin.
İlk yayına almayı gözlem modunda yapın. Nginx'te bunun için hazır bir anahtar var, limit_req_dry_run on; sınırı uygulamaz ama aşımları paylaşımlı bellekte saymaya devam eder. Kural yazıp bir hafta kimin çarpacağını izlemek, çarpanlarla konuşmak ve sonra uygulamaya almak, sürpriz kesinti riskini büyük ölçüde bitirir.
Sonrasında ölçümü panoya taşıyın: anahtar başına 429 sayısı, hangi uç noktada, hangi müşteride. Daha önce hiç çarpmayan bir anahtarın çarpmaya başlaması ya kötüye kullanım ya da büyüme sinyalidir ve iki durumda da haberi müşteriden önce sizin almanız gerekir. Eşik ve uyarı kurma tarafı SLO ve hata bütçesi yazısında.
Kota teknik değil ürün kararıdır
Saniyelik hız sınırı sistemi korur, kota ise ne sattığınızı tanımlar. Plan başına aylık hak, yumuşak sınır (uyar ama geçir) ile sert sınır (durdur) ayrımı, aşım ücreti olup olmadığı: bunların hepsi fiyatlandırma kararıdır ve mühendislik ekibinin tek başına vereceği kararlar değil.
Kalıbı görmek için AWS API Gateway'in kullanım planı modeline bakmak yeterli: API anahtarı başına hız ve patlama ayarı, üstüne gün, hafta veya ay bazında kota ve sınırların uygulanma sırası (önce istemci ve metot bazlı, sonra hesap bazlı, en sonda bölge bazlı). Aynı kurgu kendi ürününüzde de işe yarar.
İki pratik not. Kullanıcı kendi kullanımını panelde görebilsin; görmüyorsa kotayı öğrendiği ilk yer sizin destek kuyruğunuz olur. Ve arkasında ölçülen bir maliyet olan her özelliğe (dil modeli çağrısı, SMS, veri çıkışı) kullanıcı başına bir üst sınır koyun, çünkü bu sınır teknik bir koruma değil fatura korumasıdır (bulut maliyeti, ürüne yapay zeka özelliği eklemek).
Bu haftaya sığan üç adım
Bir: en pahalı beş uç noktanızı yazın (arama, rapor, dışa aktarma, toplu içe aktarma, giriş) ve anahtar başına mevcut trafiği ölçün. Çoğu ekipte bu liste çıkınca sınıra en yakın müşterinin kim olduğu da ortaya çıkıyor.
İki: bu beş uç noktaya yüksek eşikli bir sınırı gözlem modunda koyun, bir hafta bekleyin, kimin çarptığına bakın, sonra eşiği gerçek sayıya indirin.
Üç: 429 yanıtını standartlaştırın. Retry-After, kalan kota başlıkları ve hangi sınırın dolduğunu söyleyen bir hata gövdesi. Aynı gün kendi dışa giden istemcilerinizde geri çekilme ve rastgelelik ayarını da düzeltin, çünkü 429 veren taraf olduğunuz kadar 429 alan taraf da sizsiniz.
Wedevit olarak API'lerinizin maliyet haritasını çıkarıyor, hız sınırlama ve kota katmanını kenar ile uygulama arasında paylaştırıyor, 429 sözleşmesini dokümante ediyor ve giden entegrasyonlarınızdaki yeniden deneme davranışını düzeltiyoruz. Somut ilk adım: bugün bir müşteri döngüye girse onu durduran bir katman var mı, yoksa bunu ilk olarak veritabanı yükünden mi öğreniyorsunuz?
Bu konuda yardıma mı ihtiyacınız var?