API Rate Limit Aşımında Ne Yapılır: Çözüm Yöntemleri
Üretim ortamında bir sabah 3’te telefon aldığınızı düşünün: “Servisimiz çalışmıyor, müşteriler sipariş veremiyor.” Logları açıyorsunuz ve her yerde aynı hata: 429 Too Many Requests. Evet, API rate limit aşımı. Hem acı veren hem de bir o kadar yaygın olan bu sorunla başa çıkmanın yollarını detaylıca ele alalım.
Rate Limit Nedir ve Neden Önemlidir?
Rate limiting, bir API’nin belirli bir zaman diliminde kaç istek kabul edeceğini sınırlandıran mekanizmadır. Stripe, Twilio, GitHub, Slack gibi popüler servislerin hepsi bu sistemi kullanır. Sunucu kaynaklarını korumak, kötüye kullanımı önlemek ve tüm kullanıcılara adil erişim sağlamak için zorunlu bir önlemdir.
Sorun şu: Çoğu geliştirici ve sistem yöneticisi bu limitleri ancak aşıldığında fark eder. O noktada iş işten geçmiş olur.
Rate limit türlerini bilmek, doğru çözümü üretmenin ilk adımıdır:
- Dakika bazlı limit: 60 istek/dakika gibi kısa aralıklı kısıtlamalar
- Saatlik limit: Bazı servislerin uyguladığı orta vadeli kontroller
- Günlük limit: Ücretsiz katmanlarda sıkça görülen günlük kota
- Concurrent limit: Aynı anda aktif olabilecek bağlantı sayısı
- Endpoint bazlı limit: Her endpoint’in farklı limiti olabilir
Hata Mesajlarını Doğru Okumak
429 aldığınızda paniklemek yerine response header’larına bakın. Çoğu API size ne zaman tekrar deneyebileceğinizi söyler.
# curl ile header'ları görmek
curl -v -X GET "https://api.example.com/v1/users"
-H "Authorization: Bearer YOUR_TOKEN" 2>&1 | grep -i "rate|retry|limit|reset"
Tipik header’lar şunlardır:
- X-RateLimit-Limit: Toplam istek hakkınız
- X-RateLimit-Remaining: Kalan istek sayısı
- X-RateLimit-Reset: Limitin sıfırlanacağı Unix timestamp
- Retry-After: Kaç saniye sonra tekrar denemeniz gerektiği
- X-RateLimit-Retry-After-Seconds: Bazı API’lerin kullandığı alternatif alan
# Header'ları parse edip okunabilir formata çevirme
curl -sI "https://api.github.com/rate_limit"
-H "Authorization: token YOUR_GITHUB_TOKEN" |
awk -F': ' '/x-ratelimit/{
if ($1 == "x-ratelimit-reset")
print $1": "strftime("%Y-%m-%d %H:%M:%S", $2)
else
print $1": "$2
}'
Exponential Backoff: Temel Strateji
Rate limit aşıldığında yapılacak en kötü şey hemen tekrar denemek. Bu hem sorunu çözmez hem de sizi daha uzun süre bloke tutabilir. Exponential backoff (üstel geri çekilme) stratejisi, her başarısız denemeden sonra bekleme süresini katlayarak artırır.
#!/bin/bash
# exponential_backoff.sh - Rate limit için akıllı retry mekanizması
API_URL="https://api.example.com/v1/data"
TOKEN="your_api_token"
MAX_RETRIES=5
BASE_DELAY=1
make_api_request() {
local retry=0
local delay=$BASE_DELAY
while [ $retry -lt $MAX_RETRIES ]; do
response=$(curl -s -w "n%{http_code}"
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
"$API_URL")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | head -n-1)
if [ "$http_code" -eq 200 ]; then
echo "Basarili: $body"
return 0
elif [ "$http_code" -eq 429 ]; then
retry_after=$(curl -sI "$API_URL"
-H "Authorization: Bearer $TOKEN" |
grep -i "retry-after" | awk '{print $2}' | tr -d 'r')
if [ -n "$retry_after" ]; then
delay=$retry_after
fi
echo "Rate limit asimi. $delay saniye bekleniyor... (Deneme: $((retry+1))/$MAX_RETRIES)"
sleep $delay
# Exponential backoff: her denemede sure ikiye katlanir
delay=$((delay * 2))
retry=$((retry + 1))
else
echo "Beklenmeyen hata: HTTP $http_code"
echo "Yanit: $body"
return 1
fi
done
echo "Maksimum deneme sayisina ulasildi!"
return 1
}
make_api_request
Python ile Akıllı Rate Limit Yönetimi
Bash yeterli değilse, Python ile çok daha sofistike bir çözüm üretebilirsiniz. Özellikle yoğun API entegrasyonlarında Python tercih edilir.
#!/usr/bin/env python3
# rate_limit_manager.py
import time
import random
import requests
from functools import wraps
from datetime import datetime
class RateLimitHandler:
def __init__(self, max_retries=5, base_delay=1.0, max_delay=60.0):
self.max_retries = max_retries
self.base_delay = base_delay
self.max_delay = max_delay
self.request_count = 0
self.window_start = time.time()
def with_retry(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(self.max_retries):
try:
response = func(*args, **kwargs)
# Kalan limiti logla
remaining = response.headers.get('X-RateLimit-Remaining', 'N/A')
reset_time = response.headers.get('X-RateLimit-Reset', None)
print(f"[{datetime.now().strftime('%H:%M:%S')}] "
f"Kalan limit: {remaining}")
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After',
self.base_delay * (2 ** attempt)))
# Jitter ekle - ayni anda cok sayida istemci varsa
# hepsinin ayni anda denemesini onle
jitter = random.uniform(0, 1)
wait_time = min(retry_after + jitter, self.max_delay)
print(f"Rate limit! {wait_time:.2f}s bekleniyor "
f"(Deneme {attempt + 1}/{self.max_retries})")
time.sleep(wait_time)
continue
return response
except requests.exceptions.ConnectionError as e:
wait_time = self.base_delay * (2 ** attempt)
print(f"Baglanti hatasi: {e}. {wait_time}s bekleniyor...")
time.sleep(wait_time)
raise Exception(f"{self.max_retries} denemeden sonra basarisiz")
return wrapper
# Kullanim ornegi
handler = RateLimitHandler(max_retries=5, base_delay=2.0)
@handler.with_retry
def fetch_user_data(user_id):
return requests.get(
f"https://api.example.com/users/{user_id}",
headers={"Authorization": "Bearer YOUR_TOKEN"}
)
# Toplu islem - rate limit dostu
def process_users_batch(user_ids, requests_per_minute=30):
delay_between_requests = 60.0 / requests_per_minute
results = []
for i, user_id in enumerate(user_ids):
print(f"Isleniyor: {user_id} ({i+1}/{len(user_ids)})")
response = fetch_user_data(user_id)
results.append(response.json())
# Son istek degilse bekle
if i < len(user_ids) - 1:
time.sleep(delay_between_requests)
return results
Token Bucket Algoritması ile Proaktif Yönetim
Reaktif yaklaşım (429 geldikten sonra beklemek) yerine, proaktif bir yaklaşım olan token bucket algoritmasını kullanabilirsiniz. Bu yöntemde istek göndermeden önce “token” var mı kontrol edersiniz.
#!/usr/bin/env python3
# token_bucket.py
import time
import threading
class TokenBucket:
"""
Proaktif rate limiting icin token bucket implementasyonu.
Ornek: TokenBucket(capacity=100, refill_rate=10)
-> Saniyede 10 token eklenir, max 100 token
"""
def __init__(self, capacity, refill_rate):
self.capacity = capacity
self.refill_rate = refill_rate # saniyede eklenecek token
self.tokens = capacity
self.last_refill = time.time()
self.lock = threading.Lock()
def _refill(self):
now = time.time()
elapsed = now - self.last_refill
tokens_to_add = elapsed * self.refill_rate
self.tokens = min(self.capacity, self.tokens + tokens_to_add)
self.last_refill = now
def consume(self, tokens=1, timeout=None):
"""
Token tuketmeye calis.
timeout: None ise sonsuza kadar bekle
"""
start_time = time.time()
while True:
with self.lock:
self._refill()
if self.tokens >= tokens:
self.tokens -= tokens
return True
# Kac saniye beklemek gerektigini hesapla
tokens_needed = tokens - self.tokens
wait_time = tokens_needed / self.refill_rate
if timeout is not None:
elapsed = time.time() - start_time
if elapsed + wait_time > timeout:
return False
time.sleep(min(wait_time, 0.1)) # Max 100ms kontrol araligi
# Gercek dunya kullanimi: GitHub API ornegi
class GitHubAPIClient:
def __init__(self, token):
self.token = token
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"token {token}",
"Accept": "application/vnd.github.v3+json"
})
# GitHub: saatte 5000 istek = saniyede ~1.38 istek
# Guvenli tarafta kalmak icin 1.2 kullanalim
self.bucket = TokenBucket(capacity=10, refill_rate=1.2)
def get(self, endpoint):
# Token yoksa bekle (max 60 saniye)
if not self.bucket.consume(timeout=60):
raise TimeoutError("Rate limit icin token alinamadi")
response = self.session.get(f"https://api.github.com{endpoint}")
# Kalan limiti guncelle (opsiyonel ama faydali)
remaining = int(response.headers.get('X-RateLimit-Remaining', 10))
if remaining < 100:
print(f"UYARI: Sadece {remaining} GitHub API isteği kaldi!")
return response
Queue Sistemi ile Asenkron İstek Yönetimi
Yoğun sistemlerde senkron API çağrıları yetmez. Redis ve Celery kombinasyonu ile asenkron bir kuyruk sistemi kurabilirsiniz.
# Redis kurulumu (Ubuntu/Debian)
apt-get install redis-server -y
systemctl enable redis-server
systemctl start redis-server
# Python bağımlılıkları
pip install celery redis requests
# Celery worker başlatma
celery -A tasks worker --loglevel=info --concurrency=2
# tasks.py - Celery task tanımları
from celery import Celery
from celery.utils.log import get_task_logger
import requests
import time
app = Celery('api_tasks', broker='redis://localhost:6379/0')
logger = get_task_logger(__name__)
# Rate limit ayarlari: 30 saniyede max 10 istek
app.conf.task_annotations = {
'tasks.call_external_api': {
'rate_limit': '10/30s'
}
}
@app.task(bind=True, max_retries=5)
def call_external_api(self, endpoint, payload=None):
try:
response = requests.post(
f"https://api.example.com{endpoint}",
json=payload,
headers={"Authorization": "Bearer YOUR_TOKEN"},
timeout=30
)
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 60))
logger.warning(f"Rate limit asimi. {retry_after}s sonra tekrar denenecek.")
# Celery'nin retry mekanizmasini kullan
raise self.retry(countdown=retry_after)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as exc:
# Exponential backoff ile retry
countdown = 2 ** self.request.retries
raise self.retry(exc=exc, countdown=countdown)
Monitoring: Rate Limit Durumunu Takip Etmek
Sorun çıkmadan önce farkında olmak için monitoring şarttır. Prometheus ve Grafana kullanıyorsanız aşağıdaki yaklaşım işinize yarar.
#!/bin/bash
# check_rate_limits.sh - Cron ile her 5 dakikada bir calistir
SLACK_WEBHOOK="https://hooks.slack.com/services/xxx/yyy/zzz"
GITHUB_TOKEN="your_github_token"
THRESHOLD=500 # Kalan istek bu sayinin altina duserse uyar
check_github_rate_limit() {
response=$(curl -s "https://api.github.com/rate_limit"
-H "Authorization: token $GITHUB_TOKEN")
remaining=$(echo "$response" | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(data['rate']['remaining'])
")
reset_timestamp=$(echo "$response" | python3 -c "
import sys, json
from datetime import datetime
data = json.load(sys.stdin)
reset = datetime.fromtimestamp(data['rate']['reset'])
print(reset.strftime('%H:%M:%S'))
")
echo "GitHub API - Kalan: $remaining, Reset: $reset_timestamp"
if [ "$remaining" -lt "$THRESHOLD" ]; then
payload="{"text": "UYARI: GitHub API limiti kritik seviyede! Kalan: $remaining istek. Reset: $reset_timestamp"}"
curl -s -X POST "$SLACK_WEBHOOK"
-H "Content-Type: application/json"
-d "$payload"
fi
}
check_github_rate_limit
Cron’a eklemek için:
# Her 5 dakikada bir kontrol
*/5 * * * * /opt/scripts/check_rate_limits.sh >> /var/log/rate_limit_check.log 2>&1
Gerçek Dünya Senaryosu: E-ticaret Entegrasyonu
Bir e-ticaret platformunda ürün fiyatlarını harici bir tedarikçi API’sinden çeken sistemi düşünelim. Tedarikçi dakikada 60 istek limiti koymuş, ancak platformda 500 aktif ürün var.
Çözüm yaklaşımları:
- Caching: Fiyat verilerini Redis’te 5 dakika cache’le. 500 ürün için dakikada 60 istek yerine sadece değişen ürünleri güncelle
- Batch endpoint kullan: Tek tek ürün sorgulamak yerine toplu sorgu destekleyen endpoint varsa onu kullan
- Delta sync: Son güncellemeden bu yana değişen ürünleri sorgula, hepsini değil
- Öncelik kuyruğu: Çok satan ürünleri daha sık güncelle, az satanları saatlik güncelle
# Redis cache kontrolu ornegi
check_cache_or_fetch() {
local product_id=$1
local cache_key="product_price_${product_id}"
local ttl=300 # 5 dakika
# Once cache'e bak
cached_price=$(redis-cli GET "$cache_key")
if [ -n "$cached_price" ]; then
echo "Cache hit: $cached_price"
return 0
fi
# Cache miss - API'den cek
price=$(curl -s "https://supplier-api.com/products/$product_id/price"
-H "Authorization: Bearer $SUPPLIER_TOKEN" |
python3 -c "import sys,json; print(json.load(sys.stdin)['price'])")
# Cache'e yaz
redis-cli SETEX "$cache_key" "$ttl" "$price" > /dev/null
echo "API fetch: $price"
}
Limit Aşımını Engelleyen Yapısal Önlemler
Sorunla karşılaştıktan sonra fix uygulamak yerine, yapısal düzenlemeler yaparak tekrar yaşamanızı önleyin:
- API Gateway kullanın: Kong, AWS API Gateway veya Nginx rate limiting modülü ile kendi tarafınızda da limit koyun. Harici limite ulaşmadan önce kendi limitiniz devreye girsin
- Circuit breaker pattern: Belirli bir hata oranına ulaşınca API çağrılarını tamamen durdur, kısa süre bekle, sonra tekrar dene. Hystrix veya Resilience4j bu pattern’i uygular
- Webhook tercih et: Poll yapmak yerine API sizi bilgilendirsin. Sürekli “yeni bir şey var mı?” sormak yerine API “yeni şey oldu” diye sizi çağırsın
- API versiyonunu kontrol et: Yeni API versiyonları genellikle daha yüksek limitlerle veya daha verimli endpoint’lerle gelir
- Farklı token’lar kullan: Mümkünse farklı işlevler için farklı API anahtarları kullanın. Bir işlemin limiti dolduğunda diğerleri etkilenmesin
- Sandbox ortamda test et: Production rate limitleri gerçek trafiğe göre şekillenir, geliştirme sırasında sandbox kullanın
Nginx ile Proxy Katmanında Rate Limit Yönetimi
Kendi servislerinizi harici API’ye bağlarken araya Nginx koyabilirsiniz. Bu hem caching hem de rate limiting için merkezi bir nokta sağlar.
# /etc/nginx/conf.d/api_proxy.conf
# Rate limiting zone tanimla: IP basina dakikada 30 istek
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=30r/m;
upstream external_api {
server api.example.com:443;
keepalive 32;
}
server {
listen 8080;
server_name api-proxy.internal;
location /api/ {
# Rate limit uygula, burst 10 istek tolere et
limit_req zone=api_limit burst=10 nodelay;
limit_req_status 429;
# Cache baslik ekle
add_header X-Cache-Status $upstream_cache_status;
# GET istekleri icin 3 dakika cache
proxy_cache_valid 200 3m;
proxy_cache_methods GET;
proxy_pass https://external_api/;
proxy_ssl_server_name on;
proxy_set_header Host api.example.com;
proxy_set_header Authorization "Bearer YOUR_TOKEN";
# Timeout ayarlari
proxy_connect_timeout 10s;
proxy_read_timeout 30s;
}
# Rate limit asiminda ozel hata sayfasi
error_page 429 /rate_limit_error.json;
location = /rate_limit_error.json {
return 429 '{"error": "Rate limit asimi. Lutfen bekleyin.", "retry_after": 60}';
add_header Content-Type application/json;
add_header Retry-After 60;
}
}
Sonuç
Rate limit aşımı, doğru yaklaşımla tamamen yönetilebilir bir sorun. Önemli olan reaktif değil proaktif olmak: logları takip edin, limitlerin ne zaman dolacağını öngörün ve sisteminizi bu gerçekliğe göre tasarlayın.
Özetlemek gerekirse önce response header’larını okuyun ve Retry-After değerine saygı gösterin. Sonra exponential backoff ve jitter’ı her retry mekanizmanıza entegre edin. Token bucket gibi proaktif algoritmaları tercih edin, 429 gelmesini beklemeyin. Sık erişilen verileri mutlaka cache’leyin, Redis bu iş için biçilmiş kaftan. Celery veya benzeri araçlarla asenkron kuyruk sistemi kurun. Monitoring kurarak kritik eşik değerlerinde alarm alın, gece 3’teki sürprizleri ortadan kaldırın. Yapısal olarak webhook, batch endpoint ve circuit breaker pattern’lerini değerlendirin.
Rate limit aşımı bir felaket değil, API’nin size “yavaşla” demesi. Sisteminizi buna göre tasarladığınızda bu sinyal fırsata dönüşür: hem kaynak kullanımınızı optimize etmiş hem de daha resilient bir mimari kurmuş olursunuz.
