iToverDose/Yazılım· 14 MAYIS 2026 · 12:00

Laravel RateLimiter’da Kritik Yarış Koşulu Riski ve Çözümü

Laravel’in RateLimiter’ıyla yapılan hız sınırlama, eşzamanlı 100 istekle nasıl kolayca aşılabilir? Kritik yarış koşulu sorununu ve tek satırlık çözümü keşfedin.

DEV Community3 dk okuma0 Yorumlar

Laravel’in hız sınırlama (rate limiting) mekanizması, uygulamaların API’lerini korumak için yaygın olarak kullanılır. Ancak bu sistemde gizli bir tehdit var: yarış koşulları (race conditions). Özellikle yoğun trafik altında, beklenmedik şekilde çok sayıda istek sınırı aşabiliyor. Peki, bu sorun nasıl ortaya çıkıyor ve nasıl çözülüyor?

Geliştiriciler, Laravel’in hız sınırlama örneklerini uygularken genellikle aşağıdaki kodu kullanır:

if (RateLimiter::tooManyAttempts('send-message:'.$user->id, $maxAttempts = 5)) {
    return 'Çok fazla deneme!';
}

RateLimiter::hit('send-message:'.$user->id);
// Mesaj gönder...

Bu kod, her kullanıcı için dakikada en fazla 5 istek gönderilmesini garanti eder gibi görünür. Ancak gerçek dünya senaryolarında, aynı endpoint’e 100 eşzamanlı istek gönderildiğinde durum farklılaşır. Tüm bu istekler sınırı aşabilir ve arka uçta gereksiz yük oluşturabilir.

Yarış Koşulu Neden Olur?

Hız sınırlama mekanizmasının nasıl çalıştığını adım adım incelediğimizde, sorunun kaynağı netleşir:

  • tooManyAttempts() metodu, önbellekten (cache) mevcut sayaç değerini okur ve bu değeri $maxAttempts ile karşılaştırır. Eğer sayaç sınırı aşmamışsa, true döner.
  • Eş zamanlı olarak başka bir istek de aynı anda önbelleği okuyabilir ve aynı sonucu elde edebilir.
  • Her iki istek de hit() metodunu çağırarak sayacı artırır. Böylece, sınırın aşılması gereken yerde bile istekler geçerli kabul edilir.

Bu senaryo, özellikle Redis ya da Memcached gibi önbellek sistemlerinde sıkça görülür. Çünkü önbellek okumaları ve yazmaları arasında mikro saniyelerlik bir gecikme oluşabilir. Bu süre zarfında yüzlerce istek aynı anda sınır kontrolünü geçebilir.

Neden increment() Atomik, tooManyAttempts() Değil?

Laravel’in kaynak koduna baktığımızda, RateLimiter::hit() metodunun aslında RateLimiter::increment() metodunun özel bir hali olduğunu görüyoruz. Kaynak kodu şu şekilde çalışır:

public function increment($key, $decaySeconds = 60, $amount = 1)
{
    $key = $this->cleanRateLimiterKey($key);
    $this->cache->add($key.':timer', $this->availableAt($decaySeconds), $decaySeconds);
    $added = $this->withoutSerializationOrCompression(fn () => $this->cache->add($key, 0, $decaySeconds));
    $hits = (int) $this->cache->increment($key, $amount);
    return $hits;
}

Burada kritik olan kısım, $this->cache->increment($key, $amount) satırıdır. Bu satır, Redis’in INCR komutu gibi atomik bir işlemdir. Yani, aynı anda gelen çok sayıda istek, sayacı artırırken birbirlerini engelleyerek benzersiz değerler döndürür. Örneğin, 100 eşzamanlı istek geldiğinde, her biri 1’den 100’e kadar farklı bir değer alır.

Ancak tooManyAttempts() metodu, sayacı okumakla yetinir ve bu değerin sınırı aşıp aşmadığını kontrol eder. Bu okuma işlemi atomik değildir; dolayısıyla yarış koşullarına karşı savunmasızdır.

Tek Satırlık Çözüm: Sonucu Kontrol Et

Yarış koşullarını ortadan kaldırmak için, hız sınırlama işlemini tek bir adımda yapmak gerekir. Bunun için hit() metodunun döndürdüğü değeri doğrudan kontrol edebilirsiniz:

$attempts = RateLimiter::hit('send-message:'.$user->id);

if ($attempts > $maxAttempts) {
    return 'Çok fazla deneme!';
}

// Mesaj gönder...

Bu yaklaşımla, 100 eşzamanlı istek geldiğinde:

  • İlk 5 istek, sayacı 1’den 5’e kadar artırır ve sınırı aşmaz.
  • Kalan 95 istek, sayacı 6’dan 100’e kadar artırır ve hemen reddedilir.

Artık yarış koşulu ortadan kalkmış olur. Redis’in atomik artırma işlemi, tüm isteklerin tek bir kaynaktan kontrol edilmesini sağlar.

hit() mi, increment() mi? Hangisini Kullanmalı?

Her iki metot da aynı işi yapar. hit() metodu, varsayılan olarak increment() metodunu çağırır ve amount parametresini 1 olarak ayarlar. Bu nedenle, hız sınırlama için genellikle hit() tercih edilir. Ancak, sayacı daha büyük adımlarla artırmak istediğinizde increment() metodunu kullanabilirsiniz.

Örneğin, bir kullanıcının dakikada 10 istek göndermesini istiyorsanız, aşağıdaki gibi bir kod yazabilirsiniz:

$attempts = RateLimiter::increment('api-call', 60, 2); // Dakikada 2’şer artış

if ($attempts > 10) {
    return 'Sınır aşıldı!';
}

Sınırlamalar ve Gelecek Adımlar

Bu çözüm, tek bir Redis örneği için geçerlidir. Eğer Redis kümeleme kullanılıyorsa ve anahtarlar farklı düğümlere dağılmışsa, yarış koşulları tekrar ortaya çıkabilir. Ayrıca, farklı coğrafi bölgelerdeki Redis düğümlerine yapılan istekler de ayrı ayrı sınırlandırılmalıdır.

Diğer bir önemli nokta, yarış koşulu yalnızca sayaç tabanlı hız sınırlamalarını etkiler. Eğer bir saldırgan farklı IP adresleri kullanıyorsa, örneğin bir botnet ya da proxy ağıyla saldırı gerçekleştiriyorsa, IP tabanlı hız sınırlama yetersiz kalabilir. Bu durumda, CAPTCHA gibi ek koruma katmanları devreye alınmalıdır.

Sonuç: Küçük Bir Düzeltme, Büyük Bir Fark

Laravel’in hız sınırlama mekanizmasındaki yarış koşulu, çoğu geliştirici tarafından fark edilmeyen ancak uygulamaların güvenilirliğini ciddi şekilde tehdit eden bir sorundur. Tek satırlık bir değişiklikle bu sorunu çözmek mümkün: tooManyAttempts() ve hit() arasındaki ayrı adımları kaldırın ve sayacı doğrudan kontrol edin.

Bu basit ama etkili yöntem, uygulamalarınızın performansını ve güvenilirliğini artıracaktır. Unutmayın: hız sınırlamada güvenlik, yalnızca doğru aracı kullanmakla değil, aynı zamanda yarış koşullarını önlemekle de ilgilidir.

Yapay zeka özeti

Laravel’in RateLimiter’ında gizli bir yarış koşulu tehlikesi var. Eşzamanlı 100 istekle nasıl aşılabileceğini ve tek satırlık düzeltmeyi keşfedin.

Yorumlar

00
YORUM BIRAK
ID #9UJHKU

0 / 1200 KARAKTER

İnsan doğrulaması

9 + 3 = ?

Editör onayı sonrası yayına girer

Moderasyon · Spam koruması aktif

Henüz onaylı yorum yok. İlk yorumu sen bırak.