Merhaba arkadaşlar, bugün başımı çok ağrıtan ve neredeyse projeyi yavaşlatma noktasına getiren bir sorundan ve nasıl Redis ile kurtulduğumdan bahsedeceğim. Özellikle yoğun okuma (read) yapılan API endpoint'leriniz varsa, bu yazı tam size göre.
Karşılaştığım Sorun
Bir e-ticaret projesinde, ana sayfadaki "popüler ürünler" listesini her kullanıcı her açtığında veritabanından çekiyorduk. Bu sorgu JOIN'lerle dolu, oldukça karmaşık ve ağırdı. Kullanıcı sayısı arttıkça, database sunucumuzun CPU'su %90'lara dayanmaya başladı. Her istekte aynı veriyi tekrar tekrar hesaplamak, hem gereksiz bir yüktü hem de sayfa yüklenme sürelerini 2-3 saniyelere çıkarıyordu. "Bu iş böyle olmaz" dediğim nokta buydu.
Çözüm: Redis'i Cache Layer Olarak Eklemek
Redis, bellek üzerinde çalışan, inanılmaz hızlı bir key-value deposu. Yapmam gereken şey basitti: Bir kullanıcı popüler ürünleri istediğinde, önce Redis'e bakacaktık. Eğer veri oradaysa (cache'teyse) direkt onu dönecektik. Yoksa, o ağır veritabanı sorgusunu çalıştırıp sonucu hem kullanıcıya dönecek hem de bir süreliğine Redis'e kaydedecektik. Bir sonraki kullanıcı aynı veriyi istediğinde, artık veritabanına hiç gitmeyecektik!
İşte bu mantığı kurabilmek için `node-redis` kütüphanesini kullandım ve basit bir yardımcı fonksiyon yazdım.
Gerçek Senaryoda Uygulama
Popüler ürünler endpoint'imde bu fonksiyonu nasıl kullandığıma bakalım. `fetchPopularProductsFromDB` fonksiyonumuzun ağır veritabanı sorgusunu yaptığını varsayalım.
Buradaki sihir, getCachedOrFetch fonksiyonunda. İlk istekte cache boş olduğu için `fetchPopularProductsFromDB` çalışır ve sonucu Redis'e yazar. Sonraki tüm istekler, 30 dakika boyunca veritabanına dokunmadan aynı veriyi Redis'ten alır. 30 dakika sonra bu key otomatik silinir (TTL) ve bir sonraki istekte veri tekrar tazelenir.
Elde Ettiğimiz Sonuçlar
Bu implementasyondan sonra monitoring araçlarına baktığımda gördüğüm manzara inanılmazdı. Database sunucusundaki CPU kullanımı ortalama %90'lardan %15-20'lere düştü. Yani neredeyse %80'e varan bir yük azalması! API endpoint'inin ortalama response süresi de 2200ms'den 50ms'nin altına indi. Bu, kullanıcı deneyimi için dev bir sıçrama demekti.
Tabii ki cache invalidation (cache geçersiz kılma) zor bir problem. Örneğin, bir ürünün fiyatı admin panelinden değiştirildiğinde, ilgili cache key'ini (`popular_products`) manuel olarak silmemiz veya güncellememiz gerekiyor ki kullanıcılar eski, yanlış veriyi görmesin. Bu da ayrı bir strateji konusu.
Siz de benzer bir performans sorunu yaşadınız mı? Özellikle Node.js + MongoDB/PostgreSQL stack'inde caching için farklı ne gibi yöntemler denediniz? Redis dışında memcached gibi çözümler kullanan var mı aranızda? Yorumlarda deneyimlerinizi paylaşın, tartışalım!
Bir e-ticaret projesinde, ana sayfadaki "popüler ürünler" listesini her kullanıcı her açtığında veritabanından çekiyorduk. Bu sorgu JOIN'lerle dolu, oldukça karmaşık ve ağırdı. Kullanıcı sayısı arttıkça, database sunucumuzun CPU'su %90'lara dayanmaya başladı. Her istekte aynı veriyi tekrar tekrar hesaplamak, hem gereksiz bir yüktü hem de sayfa yüklenme sürelerini 2-3 saniyelere çıkarıyordu. "Bu iş böyle olmaz" dediğim nokta buydu.
Redis, bellek üzerinde çalışan, inanılmaz hızlı bir key-value deposu. Yapmam gereken şey basitti: Bir kullanıcı popüler ürünleri istediğinde, önce Redis'e bakacaktık. Eğer veri oradaysa (cache'teyse) direkt onu dönecektik. Yoksa, o ağır veritabanı sorgusunu çalıştırıp sonucu hem kullanıcıya dönecek hem de bir süreliğine Redis'e kaydedecektik. Bir sonraki kullanıcı aynı veriyi istediğinde, artık veritabanına hiç gitmeyecektik!
İşte bu mantığı kurabilmek için `node-redis` kütüphanesini kullandım ve basit bir yardımcı fonksiyon yazdım.
JavaScript:
const redis = require('redis');
const client = redis.createClient({
url: 'redis://localhost:6379'
});
client.connect();
async function getCachedOrFetch(key, fetchDataCallback, ttl = 3600) {
// Önce cache'e bak
const cachedData = await client.get(key);
if (cachedData) {
console.log(`[CACHE HIT] ${key} direkt Redis'ten geldi!`);
return JSON.parse(cachedData);
}
// Cache'te yoksa, veritabanından getir
console.log(`[CACHE MISS] ${key} veritabanından çekiliyor...`);
const freshData = await fetchDataCallback();
// Gelen veriyi Redis'e kaydet (TTL: Time To Live - Ömür süresi)
await client.setEx(key, ttl, JSON.stringify(freshData));
return freshData;
}
Popüler ürünler endpoint'imde bu fonksiyonu nasıl kullandığıma bakalım. `fetchPopularProductsFromDB` fonksiyonumuzun ağır veritabanı sorgusunu yaptığını varsayalım.
JavaScript:
app.get('/api/products/popular', async (req, res) => {
try {
const cacheKey = 'popular_products'; // Benzersiz cache anahtarı
const popularProducts = await getCachedOrFetch(
cacheKey,
fetchPopularProductsFromDB, // Callback: Cache yoksa çalışacak fonksiyon
1800 // 30 dakika (1800 saniye) boyunca cache'te kalsın
);
res.json(popularProducts);
} catch (error) {
res.status(500).json({ error: 'Bir şeyler ters gitti' });
}
});
Bu implementasyondan sonra monitoring araçlarına baktığımda gördüğüm manzara inanılmazdı. Database sunucusundaki CPU kullanımı ortalama %90'lardan %15-20'lere düştü. Yani neredeyse %80'e varan bir yük azalması! API endpoint'inin ortalama response süresi de 2200ms'den 50ms'nin altına indi. Bu, kullanıcı deneyimi için dev bir sıçrama demekti.
Tabii ki cache invalidation (cache geçersiz kılma) zor bir problem. Örneğin, bir ürünün fiyatı admin panelinden değiştirildiğinde, ilgili cache key'ini (`popular_products`) manuel olarak silmemiz veya güncellememiz gerekiyor ki kullanıcılar eski, yanlış veriyi görmesin. Bu da ayrı bir strateji konusu.
Siz de benzer bir performans sorunu yaşadınız mı? Özellikle Node.js + MongoDB/PostgreSQL stack'inde caching için farklı ne gibi yöntemler denediniz? Redis dışında memcached gibi çözümler kullanan var mı aranızda? Yorumlarda deneyimlerinizi paylaşın, tartışalım!