Redis Cache Invalidation: Pratikten 4 Pattern
Cache invalidation "computer science'in iki zor şeyinden biri" diye geçer. Doğru — ama nedeni gizemli değil, sadece her sistemin kendi şartı için doğru pattern'i seçmek lazım. iGaming üretimden 4 saha pattern'i.
Cache koymak kolay, çıkarmak zor. Her ekibin "Redis koyup hızlandırdık" dediği proje, bir noktada "ama eski veri gösteriyoruz" dediği şikayetlere dönüşür. Sebep cache'in kendisi değil — invalidation stratejisinin yokluğu.
Üretimde kullandığım 4 pattern var. Hepsi belirli bir senaryo için doğru, hiçbiri evrensel değil.
1. TTL — sadece TTL
En basit pattern: cache'e koy, X saniye sonra otomatik silinsin. Yenisini gerektiğinde DB'den çek.
Ne zaman kullanılır: veri "biraz eski olsa olur" toleransı varsa. Lider tablosu, popüler içerik listesi, kullanıcı profili summary.
Örnek: liderlik tablosunu 30 saniye TTL ile cache'liyoruz. Skor değişse bile en geç 30 saniye sonra yenilenir. 10.000+ eşzamanlı kullanıcı için DB yükünü %95 azalttı.
Tuzak: TTL süresini "nasıl olsa expire olur" diye uzun tutmak. 10 dakikalık TTL = 10 dakika boyunca yanlış veri gösterme riski. Soruyu sor: "kaç saniye yanlış veri gösterebilirim?" Cevap TTL'in.
2. Write-through invalidation
Yazma işlemi sırasında cache'i de güncelle. DB write + cache update, ikisi birden.
Ne zaman kullanılır: veri tutarlılığı önemli, "güncel hâl mutlaka cache'te olsun" istiyorsan.
Örnek: kullanıcı bakiyesi. Para çıkışı/girişi olunca DB'yi yaz, hemen Redis'i de güncelle. Kullanıcı bir saniye sonra bakiyesini kontrol edince doğru rakamı görür.
Tuzak: transaction boundary. DB write başarılı, Redis write başarısız → tutarsızlık. Çözüm: cache'i invalidate et (sil), bir sonraki read DB'den taze gelsin.
DB.update(...) # önce DB
cache.delete(key) # sonra cache'i sil — set değil
Cache'i set etmek değil silmek, race condition riskini düşürür.
3. Event-driven invalidation
Veri değişiminde event yayınla, ilgili cache'leri o event'i dinleyen consumer silsin.
Ne zaman kullanılır: birden fazla cache'in aynı veriye bağımlı olduğu, çoklu servis durumu.
Örnek: ürün fiyatı değişti — fiyat cache'i, kategori cache'i, "popüler ürün" cache'i, hepsi etkilenir. Tek bir product.price.changed event'i yayınla, her cache kendi key'ini silsin.
Tuzak: event'in kaybolması. Eğer event sistemi (RabbitMQ vs.) bir sebeple mesajı yutarsa, cache stale kalır sonsuza. Çözüm: event-driven + arka planda TTL safety net. İkisi birlikte.
4. Versioned key pattern
Cache key'ine bir version numarası ekle. Veri değiştiğinde version'ı arttır, eski key'lere dokunma — Redis kendi TTL'iyle siler zamanla.
cache.get(`user:profile:${userId}:v${currentVersion}`)
Version'ı bumplamak DB'de tek bir field güncellemek (ya da Redis'te ayrı bir counter).
Ne zaman kullanılır: geniş ölçekli invalidation gerektiğinde — "tüm bu kullanıcının sahip olduğu cache'leri sil" gibi.
Örnek: kullanıcı subscription değişti, tüm onun yetkilerine bağlı 20 farklı cache vardı. Hepsini bulup silmek yerine user:${userId}:perm:v${++version} — bir sonraki istek otomatik fresh gelir, eski key'ler kendi kendine ölür.
Tuzak: Redis memory'sinde ölü key birikmesi. TTL set etmeyi unutma.
Cache stampede — bütün pattern'lerin ortak düşmanı
Bir popüler key cache'ten düştüğü an, bin paralel istek aynı anda DB'ye gider. Recurring pattern'i.
Çözüm 1: stale-while-revalidate. Cache'in TTL'i bittiğinde eski veriyi geri ver, arka planda yenile. Kullanıcı eski veri görür, ama hızlı görür.
Çözüm 2: probabilistic early refresh. TTL'in son %10'unda her istekte küçük bir olasılıkla cache yenilenir. Yenileme yükü zamana yayılır.
Çözüm 3: lock-based. Cache miss olduğunda ilk istek lock alır, DB'den çeker, cache'i doldurur. Diğer istekler lock'u bekler. Stampede olmaz ama latency yapay olarak artar.
İlk ikisini production'da kullanıyorum. Üçüncüsü teorik olarak temiz ama operasyonel karmaşaya değmez.
Hangi pattern'i seçerim
Karar ağacım:
- Veri "biraz eski olsa olur" mu? → TTL only
- Yazma sırasında tutarlılık istiyorum mu? → Write-through invalidation (delete pattern)
- Birden fazla cache aynı veriye mi bağımlı? → Event-driven
- Geniş ölçekli silme mi gerekiyor? → Versioned key
Çoğu zaman birden fazlasını birleştirirsin: TTL + write-through, birbirinin yedeği. Kuşak ve kemer.
Sonuç
Cache invalidation zor değil — doğru sorusunu sormak zor. "Ne kadar stale veri kabul edilebilir?" sorusuna cevap verince doğru pattern kendiliğinden çıkıyor.
Cache koymak kolay. Doğru cache stratejisi kurmak, sistemin nasıl çalıştığını gerçekten anlamayı gerektiriyor. Yarım saat kazandığını sandığın gün, gerçekte iki günlük debug session'a hazırlık yapıyor olabilirsin.
Distributed sistemler, üretim stabilitesi, AI destekli geliştirme üzerine uzun yazılar. Spam yok, çıkmak kolay.
Yorumlar
Henüz yorum yok. İlk yorumu sen bırak.