PERFORMANS12m READ8 Haziran 2026

Node.js Performance: Event Loop'tan Worker Threads'e

CPU-bound işler, Worker Thread havuzu ve HTTP optimizasyonu.

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 entry

Sonuç

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.