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.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir