Bir API/SaaS altyapısına gelen her isteğin; kullanıcı, kurum, uygulama, servis, endpoint ve API anahtarı boyutlarında tek merkezden denetlendiği — limit veya kota aşıldığında isteğin downstream servise hiç ulaşmadan kesildiği uçtan uca bir platform.
Paylaşılan altyapıda istek trafiği sınırsız, dağınık ve ölçülemez. Her servis kendi limitini ayrı uygular; tek müşteri kapasitenin tamamını tüketebilir, satılan kullanım hakkı teknik tarafta sayılamaz, suistimal kapıdan geçer.
İstek yolundaki tek karar noktası: altı boyutta eşleşen kural tek atomik adımda değerlendirilir; istek ya geçer ya da kapıda 429 ile kesilir — sıcak yolda p99 7,1 ms.
Sorun — Paylaşılan bir API altyapısında istek trafiği tek elden yönetilmiyor: her servis kendi limitini ayrı uygular, kurum ve kullanıcı bazında merkezî bir denetim ve ölçüm yoktur. Çözüm — İsteği downstream servise ulaşmadan tek noktada ve altı boyutta değerlendiren merkezî bir Rate Limit & Kota Yönetimi Middleware'i araştırdık, tasarladık ve çalışan bir uygulamayla ölçerek doğruladık.
Binlerce müşterinin aynı altyapıyı paylaştığı bir API/SaaS sağlayıcısında istek trafiği sınırlanmadığında dört şey aynı anda bozulur: kapasite adaleti, gelir ölçümü, güvenlik ve servis kararlılığı. Soldaki iki şerit korumasız ile korumalı durumun farkını canlı gösterir; sağdaki eşleme her sorunun hangi mekanizmayla çözüldüğünü verir.
Altı durakta ilerleyeceğiz. Her durak, bir öncekinin üzerine inşa edilir.
Vakayı somutlaştıralım. Her servis kendi limitini ayrı ayrı, birbirinden habersiz uyguluyordu. Bu dağınıklık, üretimde altı somut problem doğuruyordu — her biri ayrı bir olayın kök nedeni.
Her ekip kendi çözümünü yazıyor; hata gövdeleri ve header'lar standart değil.
In-memory limit pod sayısıyla çarpılıyor: "dakikada 100" fiilen "1000" oluyor.
Tek kurumun toplu işi paylaşılan kaynağı tüketip herkesi etkiliyor.
Satış "aylık 1M çağrı" satıyor; teknik tarafta ölçüp kesen mekanizma yok.
"X kurumu bu ay ne tüketti?" sorusunun cevabı yok; kapasite varsayımla.
Bir limiti değiştirmek kod + PR + deploy: ortalama 2 gün. Olay anında imkânsız.
Bir kural tek bir soruya cevap verir: kime · nerede · ne kadar sürede · ne kadar? Bu beş kavramı somut örnekleriyle oturtun — sunumun geri kalanı bunların üstüne kurulu.
kime?Limitin bağlandığı kimlik. Aynı istek birden çok subject'e aynı anda uyabilir — hem kurum, hem o kurumdaki kullanıcı ayrı ayrı sayılır.
nerede?Kuralın vurduğu hedefin genişliği: tüm sistem, tek servis ya da tek endpoint. Çakışırsa en özgül eşleşen kural kazanır.
ne kadar sürede?Sayacın sıfırlanma ritmi. Hız limitinde saniye/dakika; kotada gün/ay ya da cüzdanda süresiz birikir.
ne kadar?Bir isteğin kaç birim yediği. Hafif okuma 1, ağır dışa-aktarım 20 sayar — pahalı işler hakkı/kotayı çok daha hızlı eritir.
sayaç kimliğiSayacın neyi ölçtüğünün kimliği — kuralın değil. Plan/fiyat değişse, hatta kural silinip yeniden yazılsa bile birikmiş tüketim aynı anahtarda korunur.
En kritik kavram: hız limiti ve kota birbirine benzer ama tamamen farklıdır. Aşağıdaki iki canlı sayaç aynı anda ilerliyor — farkı kendi gözünüzle izleyin.
Karar veren yol (data plane) hızlı ve DB'siz; kural yöneten yol (control plane) ayrı; analitik yol asenkron. Hiçbiri diğerini yavaşlatmaz — bu ayrım tasarımın temelidir.
Reaktif filtre, kimlik doğrulamadan sonra ama yönlendirmeden önce çalışır. Amaç: istek downstream servise ulaşmadan kararı vermek.
tenant · user · endpoint · maliyet
bellek içi indeks · kilitsiz
tek atomik Lua çağrısı
200 · veya 429/503/403
Kota takvim sıfırlaması ister; hız limiti pürüzsüz burst kontrolü ister — tek algoritma ikisini birden iyi yapamaz. Beşini canlı mini-görsellerle kıyaslayalım, sonra altta gerçekte hangisini, neden seçtiğimizi görelim.
Sabit pencere sayacı; süre dolunca sıfırdan başlar.
Ağırlıklı kayan pencere; son 1 dakikayı sürekli değerlendirir (önceki dilim × kalan oran + mevcut).
Kova sabit hızda dolar; her istek 1 jeton harcar.
Token Bucket'ın tek-değerli hali: teorik varış zamanı (TAT).
Özel kota cüzdanı; bakiye pozitifken geçer, bir kez eksiye düşebilir.
Kova sabit hızda jetonla dolar; kapasitesi burst ile sınırlıdır. Her istek bir jeton harcar. Jeton varsa geçer, yoksa reddedilir. Bu, kısa patlamalara izin verirken sürekli aşırı yükü keser.
"Dakikada 100" için her dakikayı sıfırlayan bir sayaç basit görünür. Ama pencere sınırında tehlikeli bir durum vardır. Aşağıdan kendiniz istek gönderin — tuzağın canlı oluştuğunu görün.
İki ayrı istek — aynı isteğin tekrarı değil, iki farklı istemciden (ör. aynı hesabı kullanan iki cihaz) gelen iki bağımsız istek — tam olarak aynı milisaniyede aynı sayaca vurursa ne olur? Yolu kendiniz seçin, A ve B'yi aynı anda tetikleyin, farkı canlı görün.
Sayaçlar Redis'te yaşar. Anahtarın her parçasının bir görevi var. Bir örnek seçin, sonra parçaların üstüne gelin — her parçanın ne işe yaradığını okuyun.
Cluster'da aynı öznenin tüm anahtarlarını aynı slot'a düşürür → tek atomik script hepsine erişebilir.
Sayaç "ne ölçtüğünü" söyler. Müşteri plan değiştirse bile tüketim korunur — sayaç sıfırlanmaz.
Takvim pencerelerinde (aylık) anahtara gömülür: 202607. Cüzdanda ve GCRA'da yoktur — süresizdir.
Her istek Redis'te tek atomik Lua çağrısıyla, milisaniyenin altında karara dönüşür. Neden PostgreSQL değil? Aynı özneye eş zamanlı UPDATE'ler serileşir (~10k tps tavan). Redis'te ölçtük: EVALSHA ~19µs. Aşağıda bir isteğin karara dönüşünü izleyin.
Lua script tek komut gibi çalışır; ortada başka istek araya giremez → yarış koşulu yok. İstek yolu bloklanmaz.
Cüzdan bakiyesi para karşılığıdır. appendonly yes ile restart'ta bakiye korunur (test: 850 → restart → 850).
{t:acme} öznenin tüm anahtarlarını aynı slot'a düşürür; tek Lua script cluster'da hepsine dokunabilir.
Admin bir kota yüklediğinde bu değişiklik gateway'e nasıl kaybetmeden ulaşır? Zincir boyunca paketi ve biriken gecikmeyi izleyin — uçtan uca ~35ms.
kota yükle
UPDATE + outbox
(tek transaction)
WAL okur
rules.v1
(compacted)
bellek indeksi
~35ms
Kural yazılırken aynı transaction'da bir outbox satırı düşer. Debezium PostgreSQL WAL'ini okuyup bu satırı Kafka'ya taşır.
"DB'ye yaz + Kafka'ya yaz" iki ayrı işlem atomik olamaz — biri düşer, tutarsızlık doğar. Outbox tek transaction ile bunu imkânsız kılar → kayıpsız.
rules.v1 compacted topic; her gateway kendi group'uyla tüm kuralları alır → yerel bellek indeksi, karar anında ağ turu yok.
Bu platformda Kafka üç işi aynı anda yapar: kuralları tüm gateway'lere yayar, her isteğin izini analitiğe akıtır ve kota eşikleri aşılınca canlı olay üretir. Üretici olayı yazar, tüketiciler kendi hızında okur — kimse kimseyi beklemez.
usage.v1 6 partition'a bölünür. Bölme = paralellik: aynı anahtar tek partition'a düşer, farklı anahtarlar farklı partition'lara → 6 tüketici eşzamanlı çalışır.
Her gateway kendi rastgele UUID group'u → herkes TÜM kuralları alır. Eşik izleyici sabit group → iş partition'lara paylaştırılır, olay bir kez işlenir.
Her group kaldığı offset'i saklar; çöküp dönünce kaldığı yerden okur — olay kaybolmaz. Aynı anahtar hep aynı partition → sıralama garanti.
rules/overrides compact: her anahtarın son değeri kalıcı → yeni gateway state'i baştan kurar. usage delete: 7 gün sonra silinir.
Servisler arası dayanıklı olay omurgası — üretici olayı diske yazar, tüketiciler kendi hızında okur.
Dayanıklı & tekrar-oynatılabilir · çok-tüketicili (tek olay → ES + izleyici + downstream) · gevşek bağlı (üretici tüketiciyi bilmez) · asenkron → istek yolu yavaşlamaz.
Çözer: ikili-yazım tutarsızlığı, senkron bağımlılık, kayıp olay. Kota %80/%100 → quota-events.v1 → otomasyon dinler.
Aynı olay gibi görünür ama tek bir karar üç ayrı soruya cevap verir. İzleyin — bir DENY olayı üç depoya farklı amaçlarla dağılıyor.
Ne: Kural, müşteri, fatura, denetim satırı.
Neden: ACID + kalıcı — faturalamanın tek doğru kaynağı.
Ne: Her isteğin tam kararı; kim / ne kadar / hangi endpoint.
Neden: Yüksek kardinalite — tenant × endpoint kırılımı burada aranır.
Ne: Sistem sağlıklı mı, ne kadar hızlı, ne kadar kesiyor.
Neden: Düşük kardinalite — sadece sabit etiketler.
ratelimit.usage.v1 topic'inden gelen her istek bir kullanım olayına dönüşür ve toplu (_bulk) indekslenir. Prometheus'un taşıyamadığı yüksek kardinaliteli soru burada cevaplanır: kim, ne zaman, hangi endpoint'te, neden kesildi?
{ "ts": "2026-07-23T14:32:07.412Z", "tenantId": "globex", "endpoint": "/v1/pay", "method": "POST", "decision": "DENY", "statusCode": 429, "ruleId": "overdraft-pay-01", "algo": "OVERDRAFT_QUOTA", "latencyMs": 12, "apiKeyHash": "sha256:9f2a…c1" }
tenantId:"globex" and statusCode:429 and ts >= now-1hHer kararın tam bağlamı tek belgede: tenant, endpoint, karar (ALLOW/DENY/SHADOW), kural, gecikme, zaman. Günlük index (ratelimit-usage-*), milyonlarca doküman.
Tenant × endpoint = milyonlarca kırılım. Prometheus'ta etiket olsa çöker (ADR-011). ES ters-indeksle tam-metin + terms/date agregasyonuyu saniyede döndürür — serbest sorgu.
Consumer olayları tamponlar, _bulk ile toplu yazar (tek tek değil → yüksek verim). Kibana (:5601) bu index üzerinde pano, filtre ve alarm kurar.
Rolü net: düşük kardinaliteli operasyon metriği. Gateway /actuator/prometheus'u açar, Prometheus scrape eder (:9090), Grafana panolarda gösterir (:3001). Ucuz, sürekli, alarmlı. Ama tek bir kural var: tenantId etiket DEĞİL (ADR-011).
sum(rate(ratelimit_decisions_total{outcome="deny"}[1m])) / sum(rate(ratelimit_decisions_total[1m]))
şimdi 4,0%
histogram_quantile(0.99, rate(ratelimit_decision_latency_seconds_bucket[5m]))rate(ratelimit_redis_fallback_total[5m])1000 birim yüklü, 900 tüketilmiş → bakiye 100. Butonlarla cüzdanı siz oynatın: 200 birimlik istek bakiye pozitifken geçer, sizi -100'e düşürür; sonrası 429. Yükleyince erişim geri açılır.
Tek düğme (kota modu) iki iş modelini de verir. Modu seçin, “Dönemi ilerlet” ile dönemleri atlayın: Ön Ödemeli cüzdan birikir, hiç sıfırlanmaz; Yenilenen kota her dönem başında el değmeden 0'a döner.
Fail-open ve fail-closed ikili bir seçim değildir. Her kural kendi politikasını taşır. Redis'i "düşürüp" üç farklı sonucu izleyelim.
Adil kullanım, genel koruma. Kesinti gürültülü komşudan kötüdür.
Güvenlik (login) ve ücretli kota. Brute-force penceresi açılmamalı.
Global limiti pod sayısına böl, yerel kova uygula. Kritik downstream'i korur.
Redis para değil hız tutar. Tüketim her 30 sn PostgreSQL'e checkpoint'lenir. Redis tüm veriyi kaybetse bile müşteri bedava kota kazanamaz — sayaç checkpoint'ten geri kurulur. Senaryoyu aşağıda siz yürütün.
Geri yükleme birden çok yoldan (gösterim + zamanlı iş) ve eş zamanlı tetiklenebilir. INCRBY olsaydı her çağrı değeri şişirirdi: 6 çağrı → 180. Bunun yerine atomik taban (floor) yazılır: max(mevcut, checkpoint). Kaç kez çalışırsa çalışsın sonuç 30. Çökme sonrası gelen yeni trafik değeri büyütmüşse o korunur — hiçbir tüketim kaybolmaz.
Dört açık kapatıldı; hepsi Redis öncelikli — sıcak yol veritabanına yük bindirmez. İlk ikisini elinizle deneyin: para tam bir kez yazılır, IDOR saldırısı imzada reddedilir.
Kota yükleme / fatura · SET NX Redis kilidi · DB'ye sıfır yük
Parametre yok sayılır · imzalı token · doğrulama sıfır DB/Redis
{ "customer": "acme", "exp": 1790000000 }gönderilen a91f…7c2e = hesaplanan a91f…7c2eKural yönetimi Bearer token ile korunur (admin / readonly rolleri). Karşılaştırma MessageDigest.isEqual ile sabit zamanlı — token'ı harf harf tahmin ettiren zamanlama sızıntısı yok. Denetim aktörü artık istemciden gelmez, token'dan türetilir: dev modda bile sahtecilik imkânsız.
Müşteri listesi eskiden her müşteri için ayrı kural sorgusu yapıyordu: 1 + N. Deterministik dönem-dilimli anahtar sayesinde kural DB'den yüklenmeden Redis'ten okunur. Artık 1 SQL + 1 Redis MGET: 1.000 müşteri için 1.001 sorgu → 2 gidiş. Sıcak yol veritabanını hiç görmez.
Test rehberinin ilk kanıtı: aynı istek iki farklı porta. Butona basın — kova (burst 10) boşalınca 429 başlar. Gerçek kuralın simülasyonu: GCRA 30/dk, burst 10. Aşağıdaki her tıklama gerçek sistemdeki bir curl isteğine denktir.
İstek göndermek için soldaki butona basın.
Gerçek uygulama davranışını adım adım yürütün. Üstten bir senaryo seçin; "Sonraki adım" ile cüzdan bakiyesini canlı izleyin. Her adım ne olduğunu ve neden olduğunu anlatır — overdraft, haftalık yenilenme, çoklu servis maliyeti, idempotent yükleme ve başarısız çağrı iadesi.
Hepsi çalışan sistem üzerinde, tek dizüstünde (Docker 8 CPU / 11 GB) ölçüldü.
İlk yük testinde isteklerin %19'u 503 aldı. Redis sağlıklıydı (23µs) ama gateway JVM'inin GC duraklaması 231ms'ye çıkıyordu → timeout → devre kesici → fail-closed 503. Hata JVM ayarındaydı, sistemde değil.
Aşağıdaki her satır bu sistemde gerçekten koşturuldu; slaytlar ekran görüntüsü değil, ölçüm. Testleri çalıştır'a basın — satırlar sırayla yeşile dönsün, her biri kendi ölçüm rozetini göstersin.
Testler rastgele değil: her biri gerçek bir başarısızlık modunu hedefler. Bir başlığa tıklayın — ne yaptık · neden bu mekanizma · neyi çözdü, ölçülen kanıt ve mekanizmanın canlı mini şeması açılsın.
Her parça tek bir işi en iyi yaptığı için burada. Bir teknolojiye tıklayın — ne olduğunu, neden seçtiğimizi, neyi çözdüğünü ve bu uygulamada tam olarak nerede çalıştığını canlı bir mini animasyonla açalım.
Ürünü bir yana bırakıp yolculuğa kavram düzeyinde bakalım: bir problemden yola çıkıp, algoritmadan atomikliğe, dağıtık mimariden ürün davranışına altı durakta hangi teorik konuları işledik.
Neden hız sınırlama ve kotaya ihtiyaç var; birbirine benzeyip aslında ayrışan iki eksen ve bir kuralın anatomisi.
Sayaç ailesi ve her birinin doğru kullanım anı — klasik sınır tuzağından pürüzsüz kadansa.
Yarış koşulunu tek atomik adımda çözmek ve sayacın kimliğini —kuralın değil— tasarlamak.
Her bileşene tek bir sorumluluk: karar, kaynak doğruluk, kayıpsız yayılım, analitik ve metrik.
Kotanın ticari yüzü ve arıza anındaki duruşu — güven, tutarlılık ve doğru anda haber verme.
Kavramların lafta kalmadığı yer: gerçek testler, canlı ölçümler ve elle denenebilir demolarla doğrulama.