Laravel’de Rate Limiting: throttle ve 429 Yönetimi

Google News Google News Flipboard Flipboard Sesli oku Yazıyı beğen Favorilere Ekle 0 Yorumlar
Daha fazla

Bir API projesinde en can sıkıcı sorunlardan biri, tek bir istemcinin saniyeler içinde yüzlerce istek atıp sunucuyu boğmasıdır. Laravel tabanlı bir lisans doğrulama API’si geliştirirken tam da bunu yaşadım: kötü yapılandırılmış bir istemci aynı uca saniyede onlarca istek göndererek veritabanını gereksiz yere yordu. Çözüm, uygulamaya en baştan sağlam bir rate limiting katmanı koymaktı.

Bu rehberde Laravel’de rate limiting‘in nasıl çalıştığını, throttle middleware’i ve RateLimiter facade’ı ile kullanıcı ve plan bazlı sınırlamayı, 429 Too Many Requests hatasının yönetimini, pencere (window) stratejilerini ve üretim ortamı için performans/güvenlik notlarını gerçek proje deneyimiyle ele alıyorum.

Rate Limiting Nedir ve Neden Önemli?

Rate limiting (istek sınırlama), belirli bir zaman aralığında bir istemcinin yapabileceği istek sayısını kısıtlama tekniğidir. “Dakikada en fazla 60 istek” kuralı tipik bir örnektir.

Bu katman olmadan bir uygulama üç somut riske açık kalır. Birincisi, giriş ve doğrulama uçlarına yapılan kaba kuvvet (brute force) denemeleri. İkincisi, tek bir istemcinin CPU ve veritabanı bağlantılarını tüketip diğer kullanıcıları yavaşlatması. Üçüncüsü ise ücretli bir üçüncü parti servise (örneğin bir AI API’sine) yapılan kontrolsüz çağrıların faturayı beklenmedik şekilde büyütmesi.

Yani rate limiting yalnızca bir güvenlik önlemi değil; aynı zamanda maliyet kontrolü ve adil kaynak paylaşımı meselesidir. Türkiye’de paylaşımlı (shared) hosting üzerinde çalışan pek çok projede, tek bir agresif istemcinin tüm hesabı yavaşlatabildiğini defalarca gördüm; bu ortamlarda istek sınırlama neredeyse zorunludur.

Proje Senaryosu: Lisans Doğrulama API’sinde Sınırlama

Kendi projelerimde rate limiting’e en çok ihtiyaç duyduğum yer, dışarıya açık API uçları oldu. Örneğin bir eklenti lisansını doğrulayan uç, her istemciden dakikada yalnızca birkaç kez çağrılmalıdır; çünkü lisans bir kez doğrulandıktan sonra istemci tarafında önbelleğe alınabilir.

Bu tür bir mimaride sınırlamayı IP yerine lisans anahtarına veya kullanıcı kimliğine göre tanımlamak çok daha adil sonuç verir. İş kurallarını controller içine gömmek yerine ayrı bir limiter katmanında tutmak, projenin bakımını kolaylaştırır; benzer bir ayrım mantığını daha önce repository pattern yazısında da ele almıştım.

[Buraya kendi lisans API projenden bir istek log ekran görüntüsü ekleyebilirsin]

Laravel’de Rate Limiting Nasıl Çalışır?

Veri merkezinde sunucular - Laravel rate limiting sayaç altyapısı

Laravel, rate limiting’i iki katmanda ele alır. Alt katmanda önbellek (cache) tabanlı bir sayaç, üst katmanda ise throttle adlı hazır bir middleware bulunur.

Her istek geldiğinde Laravel, istemciyi tanımlayan bir anahtar (IP veya kullanıcı kimliği) üretir. Bu anahtara ait sayaç, yapılandırılan cache sürücüsünde (Redis, Memcached veya database) tutulur ve her istekte atomik olarak artırılır. Sayaç eşik değeri aştığında framework otomatik olarak 429 Too Many Requests yanıtı döner.

Sayaç için hangi cache sürücüsünün kullanılacağını config/cache.php içindeki limiter anahtarıyla belirleyebilirsiniz:

'default' => env('CACHE_STORE', 'database'),

'limiter' => 'redis',

Bu ayrım önemlidir: uygulamanızın genel cache sürücüsü database olsa bile, rate limiting sayaçlarını ayrı ve daha hızlı bir sürücüde (Redis) tutabilirsiniz. Yüksek trafikte bu tek satır, veritabanı yükünü belirgin şekilde azaltır.

throttle Middleware ile Hızlı Başlangıç

throttle middleware ile istek izleme ve rate limit çıktısı

En basit yöntem, route tanımına throttle middleware’ini eklemektir. Aşağıdaki örnek, ilgili route grubunu dakikada 60 istekle sınırlar:

Route::middleware('throttle:60,1')->group(function () {
    Route::get('/posts', [PostController::class, 'index']);
    Route::get('/posts/{id}', [PostController::class, 'show']);
});

Buradaki 60,1 ifadesi “1 dakikada en fazla 60 istek” anlamına gelir. Limit aşıldığında Laravel yanıta X-RateLimit-Limit, X-RateLimit-Remaining ve Retry-After gibi başlıkları otomatik ekler. İstemci uygulamanız bu başlıkları okuyarak ne zaman yeniden deneyeceğini bilebilir.

Farklı uçlara farklı limitler vermek isterseniz middleware’i route bazında da uygulayabilirsiniz. Örneğin herkese açık arama ucuna daha düşük, giriş yapmış kullanıcıya ait panele daha yüksek bir limit tanımlamak yaygın bir pratiktir.

RateLimiter Facade ile Kullanıcı Bazlı Limitler

Sabit sayılar yerine, isim verilmiş (named) limiter’lar tanımlamak çok daha esnek bir yaklaşımdır. Bunu genellikle bir servis sağlayıcının boot metodunda yaparsınız:

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;

RateLimiter::for('api', function ($request) {
    return $request->user()
        ? Limit::perMinute(120)->by($request->user()->id)
        : Limit::perMinute(30)->by($request->ip());
});

Bu tanımda giriş yapmış kullanıcılara dakikada 120, anonim ziyaretçilere ise IP başına 30 istek hakkı verilir. Route tarafında yalnızca limiter adını çağırmanız yeterlidir: throttle:api.

Kullanıcı kimliğini anahtar olarak kullanmak, yetki katmanınızla da tutarlı olur. Bu tür kimlik ve rol bazlı kararları merkezî yönetmek istiyorsanız role based access control yaklaşımı rate limiting ile birlikte iyi çalışır.

Plana Göre Dinamik Limitleme

Plana göre dinamik rate limit ve büyüme grafiği

SaaS projelerinde kullanıcının abonelik planına göre farklı limitler uygulamak yaygın bir ihtiyaçtır. Named limiter içinde bu mantığı kolayca kurabilirsiniz:

RateLimiter::for('uploads', function ($request) {
    $plan = $request->user()?->plan ?? 'free';

    return match ($plan) {
        'pro'      => Limit::perMinute(300)->by($request->user()->id),
        'business' => Limit::none(),
        default    => Limit::perMinute(20)->by($request->ip()),
    };
});

Limit::none() ile belirli planlar için sınırı tamamen kaldırabilirsiniz. Bu yapı, ücretlendirme mantığınızı denetlenebilir bir şekilde API katmanına taşır. Böylece “pro kullanıcı neden limite takıldı?” gibi bir soruya, tek bir limiter tanımına bakarak yanıt verebilirsiniz.

Aynı fonksiyon içinde birden fazla limit döndürerek katmanlı sınırlama da yapabilirsiniz; örneğin hem dakikalık hem de günlük bir tavan koyabilirsiniz. Bu, tek bir istemcinin dakikalık limite takılmadan gün içinde toplam kotayı tüketmesini engeller.

Fixed Window ve Sliding Window Farkı

Rate limiting stratejilerinde en çok karıştırılan konu pencere (window) tipidir. Fixed window yaklaşımında sayaç sabit zaman dilimlerinde sıfırlanır; örneğin her dakikanın başında sayaç başa döner.

Bu yaklaşımın bilinen bir zayıflığı vardır: bir istemci, dilim sınırının hemen öncesinde ve hemen sonrasında istek göndererek kısa sürede limitin neredeyse iki katını atabilir. Sliding window ise son X saniyeyi kayan biçimde değerlendirdiği için bu yığılmayı yumuşatır ve daha adil bir dağıtım sağlar.

Laravel’in yerleşik limiter’ı pratikte fixed window mantığına yakın çalışır ve çoğu proje için yeterlidir. Çok hassas senaryolarda, Redis üzerinde kayan pencere sayacı kurarak daha katı bir kontrol elde edebilirsiniz; ancak bunun ek karmaşıklık getirdiğini ve çoğu blog/kurumsal API için gereksiz olduğunu unutmayın.

Karşılaşılan Hatalar ve 429 Yanıtını Yönetme

Rate limit aşıldığında Laravel varsayılan olarak şu yanıtı döndürür:

HTTP/1.1 429 Too Many Requests
Retry-After: 58

{
    "message": "Too Many Attempts."
}

Bu yanıt çoğu API için yeterlidir, ancak istemcilerinize Türkçe ve tutarlı bir mesaj döndürmek isteyebilirsiniz. response metodu bu noktada devreye girer:

RateLimiter::for('api', function ($request) {
    return Limit::perMinute(60)
        ->by($request->ip())
        ->response(function () {
            return response()->json([
                'message' => 'İstek limitine ulaşıldı. Lütfen biraz bekleyin.',
            ], 429);
        });
});

Geliştirme sırasında sık karşılaşılan bir kafa karışıklığı da şudur: Yerelde file veya array cache kullanırken sayaçlar beklediğiniz gibi paylaşılmaz ve limit bir türlü tetiklenmez. Üretim ortamında Redis’e geçtiğinizde davranışın netleştiğini görürsünüz. Bir diğer yaygın hata, ->by() anahtarını unutmaktır; anahtar verilmezse tüm istekler aynı sayaçta toplanır ve kullanıcılar birbirinin limitini tüketir.

429 durum kodunun anlamı ve önerilen başlıklar için MDN’in 429 Too Many Requests sayfası iyi bir başvuru kaynağıdır.

Rate Limiting’i Test Etme

Kurduğunuz limitin gerçekten çalıştığını görmenin en hızlı yolu, aynı uca art arda istek atmaktır. Basit bir curl döngüsü çoğu durumda yeterlidir:

for i in $(seq 1 70); do
  curl -s -o /dev/null -w "%{http_code}\n" https://site.test/api/posts
done

60. isteğe kadar 200, sonrasında 429 görmeye başlamalısınız. Daha gerçekçi bir yük için ab (ApacheBench) veya hey gibi araçlarla eşzamanlı istek gönderebilir, Retry-After başlığının doğru saniye değerini döndürüp döndürmediğini kontrol edebilirsiniz.

Otomatik testlerde ise Laravel’in RateLimiter::clear() metodunu kullanarak sayaçları test öncesi sıfırlamayı unutmayın; aksi halde bir test diğerinin sayaç durumundan etkilenir ve testler kararsız hale gelir.

Performans, Güvenlik ve Loglama Kontrolleri

Rate limiting’i üretime almadan önce şu kontrolleri yapmanızı öneririm:

  • Cache sürücüsü: Yüksek trafikte file yerine Redis kullanın; dosya tabanlı önbellek yarış koşullarına (race condition) açıktır.
  • Anahtar seçimi: Mümkünse IP yerine kullanıcı kimliği ile sınırlayın. Kurumsal ağlarda birçok kullanıcı aynı IP’yi paylaştığı için IP bazlı limit haksız engellemelere yol açar.
  • Loglama: Sık sık limite takılan anahtarları kaydedin; bu, olası kötüye kullanım için erken uyarı verir. Bu tür bir izleme katmanını activity log sistemi yazısındaki mantıkla kurabilirsiniz.
  • Önbellek dostu tasarım: Bir yandan da yanıtları önbelleğe alarak gereksiz istekleri azaltın; sunucu tarafı cache için LiteSpeed Cache ayarları yazısı pratik bir başlangıç noktasıdır.
  • Proxy arkasında IP: Cloudflare veya bir yük dengeleyici arkasındaysanız, gerçek istemci IP’sini almak için TrustProxies yapılandırmasını doğru kurun; aksi halde tüm istekler tek bir proxy IP’sinden geliyormuş gibi görünür.

Bu kontrolleri atlamak, rate limiting’in sessizce yanlış çalışmasına yol açan en yaygın nedenlerdir. Özellikle proxy arkasındaki IP sorunu, üretime çıktıktan sonra fark edilen türden sinsi bir hatadır.

Sonuç: Laravel Rate Limiting Özeti

Rate limiting, bir API’nin hem güvenliğini hem de kararlılığını doğrudan etkileyen küçük ama kritik bir katmandır. Öne çıkan noktaları özetleyelim:

  • Basit senaryolar için throttle:60,1 gibi hazır middleware yeterlidir.
  • Kullanıcı ve plan bazlı gerçek dünya ihtiyaçları için named limiter’lar kullanın.
  • Anahtarı IP yerine kullanıcı kimliğine bağlayın; üretimde Redis tercih edin.
  • 429 yanıtını özelleştirin, Retry-After başlığını istemci tarafında değerlendirin.

Kendi projenizde önce basit bir throttle:api ile başlayıp, ihtiyaç arttıkça plan bazlı limiter’lara geçmenizi öneririm. Böylece hem geliştirme sürecini yavaşlatmaz hem de üretimde sağlam bir koruma elde edersiniz.

Yazar: Özgür Bayram — WordPress, Laravel ve API mimarisi üzerine çalışan bir yazılım geliştiricisi olarak, dışarıya açık API’lerde rate limiting’i sahadaki lisans doğrulama ve ödeme sistemlerinde bizzat kurup test ettim.

Sıkça Sorulan Sorular

Laravel’de varsayılan rate limit değeri nedir?

Yeni Laravel projelerinde API route grubu genellikle dakikada 60 istekle sınırlıdır. Bu değeri servis sağlayıcıdaki api limiter tanımından değiştirebilirsiniz.

Rate limiting için Redis şart mı?

Hayır, şart değildir; ancak yüksek trafikli projelerde Redis, atomik ve hızlı sayaç güncellemeleri sayesinde güçlü şekilde önerilir. Düşük trafikli uygulamalarda database sürücüsü de çalışır.

Limit aşıldığında kullanıcı ne kadar beklemeli?

Laravel, 429 yanıtında Retry-After başlığıyla kaç saniye beklenmesi gerektiğini bildirir. İstemci uygulamanız bu başlığı okuyarak otomatik yeniden deneme mantığı kurabilir.

IP yerine kullanıcı bazlı limitlemek neden daha iyi?

Aynı IP’yi paylaşan kurumsal ağlarda IP bazlı limit haksız engellemelere yol açar. Giriş yapmış kullanıcılar için kimlik bazlı anahtar daha adil ve güvenlidir.

Rate limiting brute force saldırılarını tamamen engeller mi?

Tek başına yeterli değildir ancak önemli bir katmandır. Giriş uçlarında rate limiting’i captcha, hesap kilitleme ve loglama ile birlikte kullanmak en sağlam sonucu verir.

Fixed window ve sliding window arasındaki fark nedir?

Fixed window sabit zaman dilimlerinde sayar ve dilim sınırında ani yığılmalara açıktır. Sliding window son X saniyeyi kayan biçimde değerlendirir ve daha adil sonuç verir.

Rate limit’i kod yazmadan test edebilir miyim?

Evet. curl ile aynı uca art arda istek atarak ya da ab/hey gibi yük araçlarıyla limiti kolayca doğrulayabilir, 429 yanıtının ne zaman döndüğünü gözlemleyebilirsiniz.

İlgili Yazılar

Kaynaklar

Yazar Hakkında

Benzer Yazılar

Bir Cevap Yaz

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

0/30 karakter