Support Online
Skip to main content

Entrance

Node.js has a single-threaded architecture. While this structure is great for I/O-oriented operations, CPU-intensive tasks block the Event Loop and reduce the responsiveness of the entire application.

In this guide, you will learn how to separate CPU-intensive jobs from the main thread, run workloads with true parallelism, and worker pools, resource limits, and security practices used in production using Node.js' worker_threads structure.

📘 What Will You Learn in This Guide?

  • logic of worker_threads module
  • Isolate CPU intensive tasks from the main thread
  • Usage of single worker
  • Setting up a Worker Pool with Piscina
  • Resource limits: memory, timeout, stack
  • Task queue & performance monitoring (Prometheus)
  • Secure multithreading in production environment

1. Understanding Node.js Multithreading Logic

There are two working models in Node.js:

StructureDescription
Event LoopManages I/O tasks. It is single threaded.
Worker ThreadsThey are additional threads to execute CPU-intensive tasks in parallel.
Worker Threads provide:
  • Working independently from the main thread
  • Have your own Event Loops
  • Data transfer with postMessage
  • Shared memory with SharedArrayBuffer

✔ In this way, CPU-intensive tasks do not freeze the server.


2. Project Setup

Indexing
mkdir genixnode-worker-demo
cd genixnode-worker-demo
npm init -y
Activating ES Modules

npm pkg set type=module
Required packages

npm install express piscina poolifier p-queue prom-client

3. Creating a Worker File


// worker.js
import { parentPort, workerData } from 'node:worker_threads';
import { createHash } from 'node:crypto';

function hashBuffer(payload) {
const hash = createHash('sha256');
hash.update(payload, 'utf8');
return hash.digest('hex');
}

try {
const result = hashBuffer(workerData.payload);
parentPort.postMessage({ status: 'ok', result });
} catch (error) {
parentPort.postMessage({ status: 'error', message: error.message });
}
Bu worker, CPU yoğun SHA-256 hash işlemi yapar.
Sonucu ana iş parçacığına gönderir.

4. Running a Task with a Single Worker (Classic Method)


// index.js
import express from 'express';
import { Worker } from 'node:worker_threads';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const workerPath = join(__dirname, 'worker.js');

const app = express();
app.use(express.json({ limit: '1mb' }));

function runWorker(workerData) {
return new Promise((resolve, reject) => {
const worker = new Worker(workerPath, {
workerData,
resourceLimits: { maxOldGenerationSizeMb: 64, stackSizeMb: 4 }
});

const timeout = setTimeout(() => {
worker.terminate();
reject(new Error('İşçi zaman aşımı (10s)'));
}, 10_000);

worker.once('message', msg => {
clearTimeout(timeout);
worker.terminate();
msg.status === 'ok' ? resolve(msg.result) : reject(msg.message);
});

worker.once('error', err => reject(err));
});
}

app.post('/api/hash-tekil', async (req, res) => {
const hash = await runWorker({ payload: req.body.text });
res.json({ hash });
});

app.listen(3000, () => console.log('Server up')); ✔ A new worker is created for each request → it is costly. ✔ DOES NOT come to high traffic.

That's why Worker Pool is required.


5. High Performance with Worker Pool (Piscina)


import Piscina from 'piscina';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

export const piscina = new Piscina({
filename: resolve(__dirname, 'worker.js'),
minThreads: 2,
maxThreads: Math.max(4, Piscina.availableParallelism()),
idleTimeout: 30000,
resourceLimits: { maxOldGenerationSizeMb: 80 }
});

export async function hashWithPool(payload) {
const result = await piscina.run({ payload });
return result.result;
}

✔ Workers are created and stored ✔ Jobs are distributed to vacant workers ✔ Queue system improves performance


6. Integrating Pool into the API


import express from 'express';
import { hashWithPool } from './pool.js';

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

app.post('/api/pool/hash', async (req, res) => {
const hash = await hashWithPool(req.body.text);
res.json({ hash });
});

app.listen(3000, () => console.log('Pool aktif'));

7. Resource Limits and Security

When creating Worker:

resourceLimits: {
maxOldGenerationSizeMb: 64,
maxYoungGenerationSizeMb: 16,
stackSizeMb: 4
}

✔ Prevents memory overflow ✔ Reduces the risk of infinite loops ✔ Prevents server crash

Payload verification:

if (req.body.text.length > 1_000_000) {
return res.status(400).json({ error: 'Yük çok büyük.' });
}

8. Production Environment Monitoring (Prometheus)


import client from 'prom-client';

export function createMetricsRegistry(piscina) {
const register = new client.Registry();
client.collectDefaultMetrics({ register });

const queueGauge = new client.Gauge({
name: 'worker_queue_size',
help: 'Kuyrukta bekleyen iş sayısı'
});

register.registerMetric(queueGauge);

setInterval(() => {
queueGauge.set(piscina.queueSize);
}, 5000).unref();

return register;
}

9. /metrics endpoint


import { createMetricsRegistry } from './metrics.js';
import { piscina } from './pool.js';

const register = createMetricsRegistry(piscina);

app.get('/metrics', async (req, res) => {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
});

✔ Full monitoring is possible with Grafana & Prometheus. ✔ Worker queue density is monitored.


10. FAQ – Frequently Asked Questions

  1. Why can't Node.js handle CPU intensive tasks?

Event Loop is single threaded → CPU intensive work freezes the entire system.

  1. Why are Worker Threads better?

It has a separate event loop and a separate JS context.

  1. Is it mandatory to use Worker Pool?

YES if traffic is heavy.

  1. What is the difference between cluster and worker_threads?

cluster → process based

worker_threads → thread based


✔ Result

With this guide:

You separated CPU intensive processes from the main thread

You learned Worker Threads

You installed a high performance pool with Piscina

You implemented resource limit and security techniques

You added production tracking with Prometheus

Your Node.js application has now moved to true parallelism. You can achieve maximum performance by trying this architecture on multi-core servers on GenixNode. 🚀🔥