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
awaitile 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ç.