Dört Adımlı Desen
Hata dört sıralı, atomik olmayan adımda gizlidir:
// DEFECT
Value value = cache.get(key);
if (value == null) {
value = expensiveCompute(key);
cache.put(key, value);
}
return value;
Adım 1: önbelleği kontrol et. Adım 2: kaçırma. Adım 3: hesapla. Adım 4: sakla. Dört adımın tamamı atomik değil. Adım 1 ile Adım 4 arasında herhangi sayıda thread Adım 1’i çalıştırabilir ve hepsi null görebilir.
Idempotency Tuzağı
Bu deseni koruyan mantık: 'İki thread aynı değeri hesaplayıp saklasa bile sorun olmaz. Sonuç idempotenttir. Veri bozulması meydana gelmez.'
Bu mantık: doğruluk açısından doğrudur. Maliyet açısından ölümcüldür.
Önbellek kaçırma durumunda 1.000 thread ile: 1.000 thread'in her biri expensiveCompute(key) fonksiyonunu çalıştırır. Eğer expensiveCompute bir veritabanı sorgusu yapıyorsa, aynı anda 1.000 veritabanı sorgusu tetiklenir. Eğer harici bir servise çağrı yapıyorsa, aynı anda 1.000 HTTP isteği gönderilir. Doğru sonuçlar üreten sistem, bu sonuçları üretme maliyeti altında çöker.
Üç Tetikleyici
Bir Thundering Herd, bir önbellek anahtarının aynı anda birçok thread'de warm'dan cold'a geçiş yapmasıyla tetiklenir:
Cold start: servis boş bir önbellekle yeniden başlar. İlk istek dalgası: her anahtar kaçırılır. Tüm hesaplamalar aynı anda yapılır.
Servis yeniden başlatma: yuvarlanan yeniden başlatma, tüm örneklerde önbelleği sıfırlar. Trafik cold örneklerine yeniden dağıtılır.
TTL süresi dolma: yüksek trafikli bir anahtarın süresi dolar. N thread'in hepsi kontrol eder, hepsi kaçırır, ilk thread sonucu saklamadan önce hepsi hesaplar.
Her üç tetikleyici de trafik artışlarıyla ilişkilidir. Herd, trafik zirve yaptığında ve önbellek soğukken tetiklenir. En kötü olası zaman.
Elasticsearch EnrichCache Örneği
Elasticsearch EnrichCache: belgelenmiş yorumda 'kasıtlı olarak basitlik için kilitsiz... aynı anahtar/değerin yarış durumunda tekrar yazılması sorun değil' ifadesi yer alır. Saniyede 10.000 belge ve soğuk bir enrich cache ile: tüm 10.000 istek aynı anda enrich index'ine isabet eder. Ara sıra yapılan aramalar için tasarlanmış enrich index'i, 10.000 eşzamanlı sorguyla karşılaşır. Küme dengesizleşir.
Idempotency mantığı: kod yorumunda doğru. Saniyede 10.000 belge seviyesinde felaket.
MOAD-0001 ile Bağlantı
MOAD-0001 (Sedimentary Defect), yüksek verimli sistemlerde O(N²) darboğaz yaratır. MOAD-0001'i düzeltmek (O(N²) → O(N)) o iş istasyonunu serbest bırakır. Artan verim, aşağı akışa daha fazla istek gönderir. Daha önce MOAD-0001 darboğazı tarafından korunan aşağı akış önbellekleri artık korelasyonlu trafik artışları alır. MOAD-0005, daha önce hiç tetiklenmediği önbelleklerde devreye girer. Bir MOAD'ı düzelt; diğerini sahneye koy.
Idempotency Tuzağının Yanlış Anladığı Şey
Elasticsearch yorumu, yanlış soruya uygulanmış dikkatli mühendislik mantığını temsil eder. Idempotency: üzerinde düşünmeye değer gerçek bir özelliktir. Tuzak: analizi yalnızca doğrulukta durdurup maliyete devam etmemek.
computeIfAbsent & singleflight
Çözüm: kontrol ve hesaplama işlemini atomik hale getirin. Tek bir thread hesaplar. Diğer tüm thread’ler bu sonucu bekler.
Java: computeIfAbsent
// HATA: dört atomik olmayan adım
Value value = cache.get(key);
if (value == null) {
value = expensiveCompute(key);
cache.put(key, value);
}
return value;
// DÜZELTME: atomik kontrol-ve-hesapla
return cache.computeIfAbsent(key, k -> expensiveCompute(k));
computeIfAbsent: anahtar yoksa, tam olarak bir kez hesaplar, saklar ve döndürür. Aynı anahtar için computeIfAbsent çağıran diğer tüm thread’ler ilk hesaplamanın bitmesini bekler. N-kat hesaplama olmaz. Stampede olmaz.
Go: singleflight.Group
var g singleflight.Group
func getOrCompute(key string) (Value, error) {
v, err, _ := g.Do(key, func() (interface{}, error) {
return expensiveCompute(key)
})
return v.(Value), err
}
singleflight: Eğer bir anahtar için hesaplama zaten çalışıyorsa, aynı anahtar için tüm çağıranlar bekler ve tek sonucu paylaşır. Tek hesaplama, N bekleyen, tek sonuç paylaşılır. 'flight' soyutlaması: uçuş halindeki istekleri tekilleştirir.
Lock vs singleflight
Saf bir anahtar başına kilit serileştirir: thread 1 hesaplar, thread 2 bekler, thread 3 bekler. Thread 1 bittikten sonra thread 2 girer ve önbelleği kontrol eder (isabet). Thread 3 girer ve önbelleği kontrol eder (isabet). N-1 kilit edinimi ve önbellek okuması.
singleflight tekilleştirir: thread 1 hesaplar, thread 2'den N'e kadar tüm thread'ler thread 1'in sonucunu bekler. Ayrı kilit edinimleri yok. Ayrı önbellek okumaları yok. Tek hesaplama, tek sonuç, N bekleyene dağıtılır. Anahtar başına kilitten daha az işlem.
İkisi de stampede'yi önler. singleflight gereksiz işi daha tamamen önler.
Deseni Yeniden Yaz
Düzeltilmiş hali somut bir senaryoya uygula.
// Yüksek trafikli bir Java servisinde kullanıcı profili önbelleği
public UserProfile getProfile(String userId) {
UserProfile profile = profileCache.get(userId);
if (profile == null) {
profile = database.loadProfile(userId); // pahalı: 50ms veritabanı sorgusu
profileCache.put(userId, profile);
}
return profile;
}
Her sabah saat 02:00’da servis yeniden başlatılır. Saat 08:00’da 10.000 kullanıcı aynı anda profillerini talep eder.