İçeriğe geç
Muhammet Şafak
en
Soran: Emir Cevaplandı:

Cache stampede'i önlemek için singleflight doğru bir yaklaşım mı?


Soru

Go ile yazdığım bir API'de sıcak bir Redis anahtarı expire olduğunda, yüzlerce eşzamanlı istek aynı anda Postgres'e gidip aynı değeri yeniden hesaplıyor; klasik cache stampede. İstediğim şey, o anahtar boşaldığında değeri yalnızca bir isteğin yeniden kurması, gerisinin sonucu paylaşması. `singleflight` bunun için doğru araç gibi görünüyor ama üretimde tek başına yeterli mi, yoksa gözden kaçırdığım noktalar var mı?

Cevap

Kısa cevap

Evet, singleflight in-process stampede çökertmesi için doğru araçtır — ama yalnızca tek proses içinde dedupe eder.

Bağlam

Çok sayıda pod’da hâlâ pod başına bir rebuild alırsınız; bu sizin için önemliyse yanına stale-while-revalidate ya da probabilistik erken expire ekleyin.

Neden

  1. singleflight tam olarak ne yapar? golang.org/x/sync/singleflight, aynı anahtarla gelen eşzamanlı çağrıları tek bir çalıştırmada birleştirir; gerisi bekler ve sonucu paylaşır. Bu tam olarak sizin “yalnızca biri yeniden kursun” ihtiyacınızdır — tek proses içinde.
    var g singleflight.Group
    
    func (s *Service) Get(ctx context.Context, key string) (Value, error) {
        if v, ok := s.cache.Get(key); ok {
            return v, nil
        }
        v, err, _ := g.Do(key, func() (any, error) {
            val, err := s.loadFromDB(ctx, key) // yalnızca biri çalışır
            if err != nil {
                return nil, err
            }
            s.cache.Set(key, val, ttl)
            return val, nil
        })
        if err != nil {
            return Value{}, err
        }
        return v.(Value), nil
    }
  2. Pod başına uyarısı. Yük dengeleyici arkasında N replica varsa, bir değil N’e kadar eşzamanlı rebuild alırsınız. Çoğu uygulamada N küçüktür (diyelim 10) ve 10 sorgu sorun değildir. Değilse, cross-process bir lock gerekir (TTL’li Redis SETNX): yalnızca bir pod kurar, gerisi bekler ya da bayat servis eder.
  3. Paylaşılan hata ve panic tuzağına dikkat. singleflight sonucu OLDUĞU KADAR hatayı da tüm bekleyenlere paylaştırır — tek bir başarısızlık o partideki herkesi düşürür. Yavaş bir rebuild’in çağıranları sonsuza kadar bloklamaması için context’li DoChan kullanın; geçici bir hatanın cachelenmemesi için Forget’i düşünün.

Ne yapmalı

  1. Rebuild sırasında bayat servis etmeyi düşünün. En düşük latency’li desen stale-while-revalidate’tir: soft bir TTL tutun, bayat değeri anında dönün ve tek bir arka plan yenilemesini (singleflight korumalı) tetikleyin. Kullanıcı rebuild’i hiç beklemez.
  2. Probabilistik erken expire (XFetch) senkronize expire’ı azaltır: her istek, TTL’den biraz önce yenileme yapıp yapmayacağına zar atar; böylece sürü oluşma olasılığı düşer, garantisi yoktur. singleflight’in yerine değil, tamamlayıcısıdır.
  3. DB tarafını unutmayın. Tek bir rebuild sorgusu bile hızlı ve index’li olmalı. Stampede koruması yavaş bir sorguyu gizler; düzeltmez.

Sonuç: Ben olsam singleflight + stale-while-revalidate ile başlardım — bu ikili uygulamaların büyük çoğunluğunda stampede’i öldürür ve latency’yi düz tutar. Redis distributed lock’ı yalnızca pod başına rebuild’lerin Postgres’i gerçekten yorduğunu ölçtüğümde eklerdim. Kısacası: replica sayınızın gerektirdiği en basit katmana uzanın, daha fazlasına değil.

İlgili Yazılar

Yorumlar

Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.

Diğer Sorular

Tüm sorular

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi