29.12.2025

Node.js’te Worker Threads ile CPU Yoğun İşleri Ana Döngüden Ayırma

CPU yoğun işlemleri Node.js event loop’tan ayırıp Worker Threads ile daha akıcı uygulamalar geliştirin.

Node.js tek iş parçacıklı (single-thread) bir event loop üzerinde çalıştığı için CPU yoğun işler (büyük JSON parse, sıkıştırma, kripto, görüntü işleme, büyük hesaplamalar) ana akışı tıkayıp tüm istekleri yavaşlatabilir. Bu yazıda, I/O tarafında güçlü olan Node.js’te CPU yükünü Worker Threads ile nasıl izole edebileceğinizi pratik bir örnekle göreceksiniz.

Ne zaman Worker Threads?

Aşağıdaki durumlarda Worker Threads iyi bir çözümdür:

  • Bir endpoint içinde uzun süren hesaplama yapıyorsanız
  • “Arada bir” değil, düzenli olarak yüksek CPU tüketen işleriniz varsa
  • Aynı proses içinde kalıp (ayrı servis kurmadan) performans kazanmak istiyorsanız

Not: Basit paralelleştirme için bazen cluster/process tabanlı ölçekleme de uygundur; Worker Threads aynı proses içinde paralel CPU çalıştırma sağlar.

Mini örnek: /hash endpoint’ini tıkamadan çalıştırmak

Aşağıdaki örnekte, gelen veriyi PBKDF2 ile türeterek hash’liyoruz. Bu iş CPU’ya yük bindirir. Worker ile ana event loop’u serbest bırakacağız.

1) Worker dosyası: hash.worker.js

// hash.worker.js
const { parentPort } = require('node:worker_threads');
const crypto = require('node:crypto');

parentPort.on('message', ({ payload, iterations }) => {
  const salt = crypto.randomBytes(16);

  // CPU yoğun: pbkdf2Sync (bloklar) -> bunu worker'da yapmak güvenli
  const derived = crypto.pbkdf2Sync(payload, salt, iterations, 32, 'sha256');

  parentPort.postMessage({
    salt: salt.toString('hex'),
    hash: derived.toString('hex'),
  });
});

2) Ana uygulama: server.js

const express = require('express');
const { Worker } = require('node:worker_threads');
const path = require('node:path');

const app = express();
app.use(express.json());

function runHashInWorker(payload, iterations = 250_000) {
  return new Promise((resolve, reject) => {
    const worker = new Worker(path.join(__dirname, 'hash.worker.js'));

    worker.once('message', (result) => {
      resolve(result);
      worker.terminate();
    });

    worker.once('error', reject);
    worker.once('exit', (code) => {
      if (code !== 0) reject(new Error(`Worker exit code: ${code}`));
    });

    worker.postMessage({ payload, iterations });
  });
}

app.post('/hash', async (req, res) => {
  const payload = String(req.body?.payload ?? '');
  if (!payload) return res.status(400).json({ error: 'payload required' });

  try {
    const result = await runHashInWorker(payload);
    res.json({ ok: true, ...result });
  } catch (e) {
    res.status(500).json({ ok: false, error: e.message });
  }
});

app.get('/health', (req, res) => res.send('OK'));

app.listen(3000, () => console.log('Listening on :3000'));

Bu yapı sayesinde /hash çalışırken bile /health gibi hafif endpoint’ler daha stabil yanıt verir; çünkü CPU işi ana event loop’u bloke etmez.

Küçük ama kritik notlar

  • Her istek için yeni Worker açmak pahalı olabilir. Trafik artınca bir worker pool yaklaşımı kullanın.
  • Worker’lara büyük veri taşımak masraflıdır. Mümkünse Transferable (ör. ArrayBuffer) kullanın.
  • “Async” yazmak tek başına çözüm değildir: CPU işi await ile sihirli şekilde I/O’ya dönüşmez. Asıl konu, işin nerede çalıştığıdır.

Hızlı kontrol listesi

  • CPU yoğun iş var mı? → Worker Threads düşün.
  • Yoğunluk yüksek mi? → Worker pool.
  • Veri büyük mü? → Transferable/streaming.
  • En basit kazanım? → Ağır işi endpoint’ten çıkar, worker’a taşı.

Node.js’i sadece I/O değil, doğru ayrıştırma ile CPU tarafında da verimli kullanmak mümkün. Worker Threads, “tek proses, daha akıcı istekler” için güçlü bir araç.