Node.js Uygulamalarında Redis Neden Kullanılır?
Cache-aside örneğinden TTL, invalidation, stampede ve eviction kararlarına; Redis'in performans kazancı ile tutarlılık maliyetini birlikte değerlendirelim.
Yavaş bir API'ye Redis eklemek bazen büyük fark yaratır, bazen de yanlış veriyi daha hızlı döndürür. Redis'in yararı, hangi veriyi neden sakladığınız ve bu verinin ne kadar eski olabileceğini bildiğinizde ortaya çıkar.
Bir ürün kataloğunda aynı ürün bilgisi binlerce kez okunuyorsa her istekte aynı veritabanı sorgusunu yapmak gereksiz olabilir. Fakat ödeme anında kullanılan fiyat veya stok için aynı cache politikası uygun olmayabilir. İş kuralı, teknoloji seçiminden önce gelir.
Redis yalnızca bir cache değildir
Redis; string, hash, set ve başka veri yapılarıyla çalışan bir veri sunucusudur. Cache, session store, sayaç, rate limit veya kuyruk altyapısının parçası olabilir. Bu kullanımların kayıp toleransı aynı değildir.
Cache verisi yeniden üretilebiliyorsa kayıp kabul edilebilir. Bir iş kuyruğundaki henüz tamamlanmamış ödeme işi için aynı varsayım geçerli olmayabilir. Kalıcılık, replikasyon ve hata durumundaki davranış kullanım amacına göre seçilmelidir.
Bellek ağırlıklı çalışma düşük gecikme sağlayabilir, fakat “Redis var, artık veritabanına gerek yok” genellemesi yanlıştır. Veri modeli, sorgulama gereksinimi ve dayanıklılık hedefleri birlikte değerlendirilir.
Cache-aside akışı
Cache-aside yaklaşımında uygulama önce cache'i okur. Kayıt yoksa asıl veri kaynağından getirir, cache'e yazar ve sonucu döndürür.
İstek → Cache'te var mı?
├─ Evet → Sonucu döndür
└─ Hayır → Veritabanı → Cache'e yaz → Sonucu döndür
Aşağıdaki örnek, bağlanmış bir node-redis istemcisi, yetkilendirme tamamlandıktan sonra çağrılan bir fonksiyon ve database.findProduct adaptörü varsayar. Tam bir uygulama değil, cache katmanının davranışını gösteren parçadır.
async function getProduct({ tenantId, productId }) {
const key = ["product", "v1", tenantId, productId]
.map(encodeURIComponent)
.join(":");
try {
const cached = await redis.get(key);
if (cached !== null) return JSON.parse(cached);
} catch {
metrics.increment("product_cache_read_failure");
}
const product = await database.findProduct({ tenantId, productId });
if (!product) return null;
try {
const ttl = 60 + Math.floor(Math.random() * 15);
await redis.set(key, JSON.stringify(product), { EX: ttl });
} catch {
metrics.increment("product_cache_write_failure");
}
return product;
}
İstemci bağlantısının kurulması ve error event'inin ele alınması node-redis rehberinde açıklanır. Burada metrics nesnesi uygulamanın ölçüm adaptörüdür. Redis komut timeout'ları ayrıca sınırlandırılmalıdır; sınırsız bekleme, fallback tasarımını etkisizleştirir.
Anahtar tasarımı veri sınırıdır
Anahtarda tenant bilgisinin bulunması farklı müşterilerin cache kayıtlarını ayırır. Fakat bu yetkilendirme yerine geçmez. Kullanıcının tenant ve ürün erişimi önceden doğrulanmalıdır.
Versiyon öneki, temsil yapısı değiştiğinde eski JSON ile yeni kodun karışmasını azaltabilir. Fiyat veya dil gibi sonucu değiştiren parametreler varsa cache anahtarına uygun biçimde dahil edilmelidir. Aksi halde hızlı ama yanlış yanıt üretirsiniz.
Anahtar sayısını sınırsız kullanıcı girdisiyle büyütmeyin. Çok farklı filtre kombinasyonları, neredeyse hiç tekrar okunmayan pahalı bir cache oluşturabilir.
TTL ve invalidation
TTL, kaydın cache'te kalabileceği süreyi sınırlar. SET komutunun expiration seçenekleri komut belgesinde bulunur. Örnekte küçük rastgele ek süre, çok sayıda anahtarın aynı anda sona ermesini azaltmaya yardımcı olur.
Veri güncellendiğinde cache'i silmek yaygın bir yaklaşımdır. Ancak şu yarış hâlâ mümkündür: bir okuma eski veriyi alır, yazma yeni veriyi kaydeder ve cache'i siler, ilk okuma eski sonucu tekrar cache'e koyar. Bu nedenle TTL ve silme kombinasyonunu güçlü tutarlılık garantisi gibi sunmayın.
Kritik işlemlerde cache'i kullanmamak, versiyonlu anahtarlar veya daha açık invalidation protokolleri tercih edilebilir. “Ne kadar eski veri kabul edilebilir?” sorusu çözümü belirler.
Cache stampede ve fallback yükü
Popüler bir anahtarın süresi dolduğunda yüzlerce istek aynı sorguyu başlatabilir. Aynı process içindeki eşzamanlı istekleri tek promise altında birleştirmek, TTL jitter veya kontrollü arka plan yenileme yardımcı olabilir.
Birden fazla instance varsa yerel birleştirme yalnızca o instance'ı korur. Dağıtık kilit kullanmak da sahiplik, süre aşımı ve güvenli serbest bırakma gibi yeni sorumluluklar getirir.
Cache erişilemezken tüm trafiği veritabanına yönlendirmek, veritabanının çökmesine neden olabilir. Fallback kapasitesini yük testiyle ölçün; gerektiğinde concurrency sınırı, kısa süreli yerel cache veya belirli işlevlerde kontrollü hizmet azaltma kullanın.
Bellek sınırı, eviction ve kalıcılık
Redis bellek limitine geldiğinde eviction politikası hangi anahtarların çıkarılabileceğini belirler. Cache ile kritik session veya kuyruk verisini aynı instance ve politikada karıştırmak beklenmedik kayıplar yaratabilir. Politika seçenekleri key eviction belgesinde açıklanır.
RDB ve AOF gibi kalıcılık seçenekleri de farklı dayanıklılık ve maliyet dengeleri sunar. Bunlar otomatik olarak sıfır veri kaybı garantisi değildir; replikasyon, fsync ve failover koşulları sonucu etkiler. Redis persistence rehberi bu farkları ayrıntılandırır.
Başarıyı yalnızca cache hit oranıyla ölçmeyin. Veritabanı yükü, son kullanıcı gecikmesi, eviction sayısı ve stale veri etkisi de önemlidir. Redis doğru sınırda kullanıldığında iyi bir hızlandırıcıdır; belirsiz veri sahipliğini ise kendiliğinden düzeltmez.
İlgili Yazılar
Node.js ve Express ile İlk REST API’ni Geliştirmek
Node.js ve Express ile bir görev yönetimi API’si geliştirelim: endpoint tasarımı, veri doğrulama, HTTP durum kodları ve merkezi hata yönetimi.
Node.js Uygulamasını Production’a Hazırlamak: Logging, Error Handling ve Security
Çalışan bir Node.js API'sini işletilebilir hale getirelim: yapılandırma doğrulama, hata sınırları, güvenli loglama, kaynak limitleri ve kontrollü kapanma.
Backend Uygulamalarında Authentication: JWT Nasıl Çalışır?
JWT'nin kodlama, imza ve doğrulama katmanlarını ayıralım; issuer, audience, algoritma sınırı, refresh token ve kaynak yetkilendirmesini birlikte tasarlayalım.