Cache hızlıdır. Cache tasarımı ise zordur.

Bir endpoint yavaşladığında en kolay önerilerden biri şudur:

Redis koyalım.

Bazen gerçekten doğru çözümdür. Ancak cache eklemek problemi çözmek yerine sisteme yeni bir consistency problemi de ekleyebilir.

Çünkü asıl soru veriyi Redis’e nasıl koyacağınız değil, hangi veriyi ne kadar süreyle doğru kabul edeceğinizdir.

En sık kullanılan modellerden biri cache-aside’dır. Uygulama önce Redis’e bakar. Veri yoksa ana veritabanından okur ve sonucu cache’e yazar. Redis’in resmi dokümantasyonu da cache-aside modelini özellikle tekrar eden read operasyonlarında database yükünü azaltmak için konumlandırıyor.

Basit haliyle:

GET /products/42

1. Redis GET product:42
2. Cache hit  → response
3. Cache miss → PostgreSQL SELECT
4. Redis SET product:42 EX 300
5. response

Buraya kadar kolay.

Sorun 300 saniye sonra başlıyor.

Cache stampede

product:42 çok popüler bir kayıt olsun.

TTL dolduğu anda aynı milisaniyede 500 request gelirse 500 request de cache miss görebilir. Bu durumda tamamı database’e gider ve aynı sorguyu çalıştırır.

Cache’in database’i koruması gerekirken tam tersine yük patlamasının sebebi olur.

Redis dokümantasyonu da popüler bir key expire olduğunda aynı kaydı isteyen çok sayıda process’in aynı anda database’e yönelmesini cache stampede olarak ele alıyor.

Bunu azaltmak için TTL jitter, request coalescing, distributed lock veya stale-while-revalidate gibi teknikler kullanılabilir.

Örneğin her key’i tam 300 saniyede expire etmek yerine:

long ttl = 300 + ThreadLocalRandom.current().nextLong(0, 30);

gibi küçük bir dağılım bile toplu expiration dalgalarını azaltabilir.

En zor problem: invalidation

Cache ile ana veri kaynağı arasında iki farklı gerçeklik oluşur.

Bir kullanıcı adını database’de değiştirdiğiniz halde Redis’teki veri beş dakika daha eski kalıyorsa sisteminiz teknik olarak hızlıdır ama yanlış veri dönmektedir.

Bu nedenle her cache için şu sorunun cevabı verilmelidir:

Bu verinin kaç saniye eski olması kabul edilebilir?

Ürün katalogunda 30 saniye kabul edilebilir olabilir.

Banka bakiyesinde olmayabilir.

Authorization bilgisinde ise bazı durumlarda birkaç saniyelik stale veri bile güvenlik problemi oluşturabilir.

Bu yüzden TTL teknik bir parametre değil, aslında iş kuralıdır.

Cache hit ratio tek başına yeterli değildir

Cache sistemini değerlendirirken yalnızca hit ratio’ya bakmak yanıltıcı olabilir.

%99 hit ratio çok iyi görünebilir. Ama kalan %1 miss database’i çökerten birkaç hot key’den oluşuyorsa sistem yine sorunludur.

Ben aşağıdaki sinyalleri birlikte izlerdim:

cache hit/miss oranı, P95/P99 cache latency, database query hacmi, hot key dağılımı, eviction sayısı ve expiration sonrası oluşan yük.

Cache optimizasyonunun başarısı “Redis çalışıyor” değildir.

Başarılı cache tasarımı, Redis devre dışı kaldığında veya bir anda yüzlerce key expire olduğunda sistemin nasıl davranacağını da bilmektir.

Çünkü cache performans katmanıdır.

Ana veri kaynağı değildir.