Cache stampede, çok sayıda eşzamanlı isteğin süresi dolmuş aynı anahtarı kaçırıp veriyi aynı anda yeniden üretmesiyle oluşur. Bu Laravel rehberi atomik kilitler, stale-while-revalidate, TTL jitter ve güvenli yedeklerle sorunu önlemeyi açıklar.
Cache stampede, çok sayıda eşzamanlı isteğin popüler bir önbellek girdisinin eksik veya süresi dolmuş olduğunu görüp aynı veriyi aynı anda yeniden üretmeye çalışmasıdır. Önbellek veritabanını korumak yerine, tekrarlanan işlerden oluşan ani bir yükü doğrudan ona yönlendirir. Bu durum thundering herd veya dog-pile etkisi olarak da bilinir.
Bir Laravel uygulamasının “en çok satan ürünler” sorgusunu on dakika sakladığını düşünün. Anahtar varken binlerce istek hızlı cevap alır. Anahtarın süresi dolduğu anda 200 istek gelirse hepsi cache miss görür, aynı sorguyu çalıştırır ve aynı sonucu yazmaya uğraşır. Bir kez çalışması gereken sorgu 200 kez çalışır.
Bu kısa patlama veritabanı bağlantılarını tüketebilir, CPU kullanımını yükseltebilir, gecikmeyi artırabilir ve bağlı servislerde zaman aşımına yol açabilir. Yavaş cevaplar istekleri daha uzun süre açık tuttuğu için eşzamanlılık büyür ve tek bir süresi dolmuş anahtar zincirleme arızaya dönüşebilir.
Cache::remember, normal önbellekleme için mükemmel bir başlangıçtır:
$products = Cache::remember('products:top', 600, function () {
return Product::query()
->where('is_active', true)
->orderByDesc('sales_count')
->limit(20)
->get();
});Laravel anahtarı kontrol eder, anahtar yoksa closure’ı çalıştırır, sonucu saklar ve döndürür. Fakat okuma ile yeniden üretme tek bir atomik işlem değildir. İlk worker sorguyu tamamlamadan birkaç PHP worker aynı miss’i görürse hepsi closure’a girebilir. Kod düşük trafikte doğru çalışsa da yüksek eşzamanlılık altında sıcak bir anahtar sona erdiğinde stampede’e açıktır.
Sıcak anahtarın sona ermesi: sık okunan bir kayıt yoğun trafikte TTL sınırına ulaşır.
Aynı TTL’ye sahip çok sayıda anahtar: toplu ısıtma yüzlerce anahtarın birlikte sona ermesine neden olur.
Cache temizliği: dağıtım veya bakım işlemi popüler anahtarları aynı anda kaldırır.
Soğuk başlangıç: yeni uygulama örnekleri önemli değerler hazırlanmadan trafik alır.
Yavaş üretim: pahalı SQL veya uzak API çağrıları daha fazla isteğin birikmesine zaman verir.
Cache altyapısı arızası: Redis ya da Memcached kesintisi kök sisteme taşıyabileceğinden fazla yük gönderir.
Amaç, veriyi yalnızca bir isteğin üretmesini sağlamak; diğerlerinin kısa süre beklemesi veya yedek değer kullanmasıdır. Laravel dağıtık atomik kilitleri Cache::lock üzerinden sunar.
use Illuminate\Support\Facades\Cache;
function topProducts()
{
$key = 'products:top';
if ($cached = Cache::get($key)) {
return $cached;
}
return Cache::lock("lock:{$key}", 15)->block(5, function () use ($key) {
// Biz beklerken başka bir worker anahtarı doldurmuş olabilir.
return Cache::get($key) ?? tap(
Product::query()
->where('is_active', true)
->orderByDesc('sales_count')
->limit(20)
->get(),
fn ($products) => Cache::put($key, $products, now()->addMinutes(10))
);
});
}Kilit içindeki ikinci cache kontrolü kritiktir. Bu kontrol olmazsa bekleyen her istek sırayla kilidi alır ve ilk istek değeri yenilemiş olsa bile tekrar hesaplar. Buna double-checked locking denir.
Kilit süresi beklenen hesaplama süresinden uzun, fakat çöken worker’ların sistemi uzun süre engellemeyeceği kadar sınırlı olmalıdır. block ile kısa bir bekleme sınırı belirleyin. LockTimeoutException oluştuğunda eski veri, sadeleştirilmiş cevap veya kontrollü hata döndürün.
Verinin birkaç dakika eski olması kabul edilebiliyorsa stale-while-revalidate iyi bir kullanıcı deneyimi sağlar. Laravel’daki Cache::flexible taze ve eski pencereleri tanımlar:
$products = Cache::flexible(
'products:top',
[300, 900],
fn () => Product::query()
->where('is_active', true)
->orderByDesc('sales_count')
->limit(20)
->get()
);İlk 300 saniyede taze değer döner. 300 ile 900 saniye arasında eski değer hemen sunulur ve cevaptan sonra ertelenmiş yenileme kaydedilir. 900 saniyeden sonra veri tamamen sona ermiş sayılır ve eşzamanlı olarak hesaplanır.
Bu yöntem gecikmeyi öngörülebilir tutar; makale listeleri, sıralamalar, panolar, öneriler ve küçük bir gecikmenin kabul edildiği veriler için uygundur.
Birlikte yazılan anahtarlar aynı süreyi kullanırsa birlikte sona erebilir. Yenilemeleri zamana yaymak için TTL’ye küçük bir rastgele fark ekleyin:
$ttl = now()->addSeconds(600 + random_int(0, 120));
Cache::put("product:{$product->id}", $payload, $ttl);TTL jitter, tek bir sıcak anahtar için yarışan istekleri tek başına durdurmaz. Kilitleri ve stale-while-revalidate yaklaşımını tamamlar; çok sayıda anahtarın eşzamanlı sona ermesini önler.
Yüksek trafikli sistemler genellikle normal anahtar, daha uzun ömürlü eski anahtar ve atomik kilidi birlikte kullanır:
use Illuminate\Contracts\Cache\LockTimeoutException;
use Illuminate\Support\Facades\Cache;
function dashboardStats(): array
{
$key = 'dashboard:stats';
$staleKey = 'dashboard:stats:stale';
if ($value = Cache::get($key)) {
return $value;
}
try {
return Cache::lock("lock:{$key}", 20)->block(2, function () use ($key, $staleKey) {
if ($value = Cache::get($key)) {
return $value;
}
$value = app(StatsService::class)->calculate();
$freshTtl = now()->addSeconds(300 + random_int(0, 60));
Cache::put($key, $value, $freshTtl);
Cache::put($staleKey, $value, now()->addHours(2));
return $value;
});
} catch (LockTimeoutException) {
return Cache::get($staleKey, ['status' => 'temporarily_unavailable']);
}
}Bir worker veriyi hesaplar; diğerleri kısa süre bekler veya eski kopyayı alır. Süreleri sorgu maliyetine, kabul edilen eskiliğe ve beklenen trafiğe göre ayarlayın.
Tüm uygulama sunucuları atomik kilit destekleyen ortak bir cache backend kullanmalıdır. Laravel Redis, Memcached, DynamoDB, database, file ve başka sürücüleri destekler; dağıtık sunucular aynı merkezi depoya bağlanmalıdır.
Kaynak, tenant, dil ve filtre kombinasyonu için benzersiz kilit adı kullanın.
Worker arızalarında toparlanmak için kilit süresini sınırlayın.
Kilitleri aşırı yüklü veritabanında tutmanın etkisini değerlendirin.
Mümkün olduğunda closure biçiminin kilidi otomatik bırakmasını sağlayın.
Boş sonuçları açıkça önbellekleyin; Cache::has veya sentinel değer kullanın.
İlgisiz işleri sıraya sokan tek bir global kilitten kaçının.
TTL sınırlarıyla eşleşen periyodik sıçramalara bakın: hit oranında ani düşüş, aynı SQL sorgularından oluşan patlama, bağlantı sayısında artış, kilit çekişmesi ve yükselen p95/p99 gecikmesi. Üretim closure’ının süresini, kilit bekleme zamanını, timeout sayısını ve eski veri kullanılıp kullanılmadığını kaydedin.
Sıcak bir anahtarı silip eşzamanlı istek gönderen yük testi çözümü doğrulamanın basit yoludur. Koruma doğruysa pahalı işi yalnızca bir istek yürütmeli; diğerleri beklemeli veya fallback almalıdır.
Ucuz veya düşük eşzamanlı anahtarlar için Cache::remember kullanın.
Hesaplama yalnızca bir kez çalışmalıysa Cache::lock kullanın.
Biraz eski veri kabul edilebiliyorsa Cache::flexible kullanın.
Birçok anahtar birlikte yazılıyorsa TTL jitter ekleyin.
Az sayıdaki kritik ve öngörülebilir sıcak anahtarı önceden ısıtın.
En yoğun yollar için kilit, fallback, jitter ve izlemeyi birleştirin.
Hayır. Tek bir cache miss normaldir. Stampede, çok sayıda eşzamanlı isteğin aynı girdiyi kaçırıp pahalı işi birlikte tekrarlamasıdır.
Hayır. Redis hızlı erişim ve kilit primitive’leri sağlar; uygulama yine de bir yeniden üretim stratejisi uygulamalıdır.
Hayır. Kilitler koordinasyon ve gecikme ekler. Yalnızca üretimi backend’i tehdit edebilecek sıcak anahtarları koruyun.
Eski veri uygunsa Cache::flexible ile başlayın. Hesaplama bir kez çalışmalıysa miss yolunu Cache::lock ile koruyup kilit içinde anahtarı tekrar kontrol edin.
Cache stampede, önbellek sorunu gibi görünen bir eşzamanlılık problemidir. Yalnızca TTL’yi uzatmak yeterli değildir. Üretimi koordine edin, güvenliyse eski veri sunun, sona erme zamanlarını dağıtın ve sıcak anahtar kaybolduğunda sistemi ölçün. Laravel’ın Cache::lock, Cache::flexible ve cache sürücüleri tek bir süresi dolmuş anahtarın üretim olayına dönüşmesini engellemek için gerekli araçları sağlar.
Henüz onaylı yorum yok. Yeni yanıtlar moderasyon bekleyebilir.