Node.js, event-loop tabanlı yapısıyla yüksek I/O workload'larında mükemmeldir. Ancak yanlış kullanımda CPU-bound işler tüm sunucuyu durdurabilir. Bu makalede üretimde karşılaşılan gerçek performans sorunlarını ve çözümleri ele alıyoruz.
Event Loop'u Anlamak
// JAVASCRIPT //
// Event Loop — tek thread, sıralı çalışır
// CPU-bound kod tüm event loop'u bloklar
// KÖTÜ — 10.000 satır JSON parse event loop'u durdurur
app.get('/rapor', (req, res) => {
const buyukVeri = JSON.parse(fs.readFileSync('10mb.json')); // Bloklayan!
const sonuc = buyukVeriIsle(buyukVeri); // Bloklayan hesaplama!
res.json(sonuc);
// Bu sürede başka hiçbir istek işlenemiyor!
});
// İYİ — async I/O, non-blocking
app.get('/rapor', async (req, res) => {
const govde = await fs.promises.readFile('10mb.json', 'utf8'); // Non-blocking
const buyukVeri = JSON.parse(govde);
// CPU-bound kısım için Worker Thread kullan (aşağıda)
const sonuc = await isciThreadIsle(buyukVeri);
res.json(sonuc);
});Worker Threads — CPU-bound İşler
// JAVASCRIPT //
// worker.js
const { workerData, parentPort } = require('worker_threads');
function agirHesaplama(veri) {
return veri.map(n => n ** 2).filter(n => n % 2 === 0).reduce((a, b) => a + b, 0);
}
parentPort.postMessage(agirHesaplama(workerData));
// main.js
import { Worker } from 'worker_threads';
function isciThreadIsle(veri) {
return new Promise((resolve, reject) => {
const isci = new Worker('./worker.js', { workerData: veri });
isci.on('message', resolve);
isci.on('error', reject);
isci.on('exit', code => {
if (code !== 0) reject(new Error(`Worker ${code} ile çıktı`));
});
});
}
// İşçi havuzu — her istek için Worker oluşturmak pahalı
import { StaticPool } from 'node-worker-threads-pool';
const havuz = new StaticPool({
size: require('os').cpus().length,
task: './worker.js',
});
app.post('/agir-hesaplama', async (req, res) => {
const sonuc = await havuz.exec(req.body.veri);
res.json({ sonuc });
});Cluster Modülü
// JAVASCRIPT //
// cluster.js — CPU çekirdeklerini kullan
import cluster from 'cluster';
import os from 'os';
import process from 'process';
if (cluster.isPrimary) {
const cpuSayisi = os.cpus().length;
console.log(`Ana süreç ${process.pid}, ${cpuSayisi} worker başlatılıyor`);
for (let i = 0; i < cpuSayisi; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code) => {
if (code !== 0) {
console.error(`Worker ${worker.process.pid} çöktü, yeniden başlatılıyor`);
cluster.fork();
}
});
} else {
// Worker — Express uygulamasını başlat
import('./server.js');
console.log(`Worker ${process.pid} başlatıldı`);
}Cache Stratejileri
// TYPESCRIPT //
// Redis ile çok katmanlı cache
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);
async function onbellekliSorgu<T>(
anahtar: string,
sureDk: number,
sorgu: () => Promise<T>
): Promise<T> {
// L1: Redis cache
const onbellekte = await redis.get(anahtar);
if (onbellekte) {
return JSON.parse(onbellekte) as T;
}
// L2: Veritabanı
const veri = await sorgu();
await redis.setex(anahtar, sureDk * 60, JSON.stringify(veri));
return veri;
}
// Kullanım
app.get('/api/popüler-makaleler', async (req, res) => {
const makaleler = await onbellekliSorgu(
'populer:makaleler:v1',
5, // 5 dakika cache
() => prisma.makale.findMany({
where: { yayinlandi: true },
orderBy: { gorunumler: 'desc' },
take: 10,
})
);
res.json(makaleler);
});
// Cache invalidation
async function makaleGuncellendinde(makaleId: string) {
// Pattern ile ilgili cache'leri temizle
const anahtarlar = await redis.keys('populer:*');
if (anahtarlar.length > 0) await redis.del(...anahtarlar);
await redis.del(`makale:${makaleId}`);
}HTTP Yanıt Optimizasyonu
// TYPESCRIPT //
import compression from 'compression';
// Gzip/Brotli sıkıştırma
app.use(compression({
filter: (req, res) => {
if (req.headers['x-no-compression']) return false;
return compression.filter(req, res);
},
level: 6, // 1-9 arası (hız vs. sıkıştırma tradeoff)
}));
// ETags ile conditional requests
app.get('/api/makaleler', async (req, res) => {
const makaleler = await getirMakaleler();
const etag = `"${require('crypto')
.createHash('md5')
.update(JSON.stringify(makaleler))
.digest('hex')}"`;
if (req.headers['if-none-match'] === etag) {
return res.status(304).end(); // Değişmedi — veri gönderme
}
res.setHeader('ETag', etag);
res.setHeader('Cache-Control', 'public, max-age=60, stale-while-revalidate=300');
res.json(makaleler);
});
// Streaming büyük yanıtlar
app.get('/api/export', async (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.write('[');
let ilk = true;
for await (const satir of veritabanindenStream()) {
if (!ilk) res.write(',');
res.write(JSON.stringify(satir));
ilk = false;
}
res.write(']');
res.end();
// Bellek: her zaman küçük, veri: milyonlarca satır
});Profiling ve Performans Ölçümü
// BASH //
# Node.js built-in profiler
node --prof server.js
# Trafik oluştur
node --prof-process isolate-*.log > profil.txt// JAVASCRIPT //
// clinic.js ile görsel profil
// npm install -g clinic
// clinic doctor -- node server.js (CPU/event loop/memory)
// clinic flame -- node server.js (flame graph)
// clinic bubbleprof -- node server.js (async profil)
// Kod içi ölçüm
const { performance, PerformanceObserver } = require('perf_hooks');
const gozlemci = new PerformanceObserver((liste) => {
liste.getEntries().forEach(giris => {
console.log(`${giris.name}: ${giris.duration.toFixed(2)}ms`);
});
});
gozlemci.observe({ entryTypes: ['measure'] });
async function olculuIslem() {
performance.mark('baslangic');
await agirIslem();
performance.mark('bitis');
performance.measure('agir-islem', 'baslangic', 'bitis');
}Bellek Sızıntısı Tespiti
// JAVASCRIPT //
// EventEmitter'da listener birikiyor
class OlayYoneticisi {
constructor() {
this.emitter = new EventEmitter();
this.emitter.setMaxListeners(50); // Uyarıyı artır
}
// KÖTÜ — her istek için listener ekleniyor, hiç temizlenmiyor
aboneOl(kullaniciId) {
this.emitter.on('mesaj', (data) => console.log(data));
}
// İYİ — cleanup fonksiyonu döndür
aboneOl(kullaniciId) {
const handler = (data) => console.log(data);
this.emitter.on('mesaj', handler);
return () => this.emitter.off('mesaj', handler); // Cleanup
}
}
// Global nesnelere veri biriktirme
// KÖTÜ
const globalCache = {};
app.use((req) => {
globalCache[req.ip] = req.body; // Asla temizlenmiyor!
});
// İYİ — TTL ile otomatik temizleme
const LRU = require('lru-cache');
const cache = new LRU({ max: 500, ttl: 1000 * 60 * 5 }); // 5dk TTL, max 500 entrySonuç
Node.js performansı; event loop'u boşta tutmak (async I/O, Worker Threads), akıllı cache (Redis), HTTP seviyesinde optimizasyon (gzip, ETag) ve bellek yönetiminin birleşimidir. Cluster modülü ile tüm CPU çekirdeklerinden yararlanın. Profiling araçlarıyla darboğazları tespit edin, tahminde bulunmayın.