Satu proses Node.js = satu thread untuk JavaScript. Komputer modern punya 4, 8, 16 CPU core — tapi aplikasi Node.js default hanya pakai satu. Solusinya: clustering.
Masalah single-thread
// 1 proses Node.js di server 8-core
// → hanya core 0 yang bekerja, 7 core lain idle
// Throughput maksimal = kemampuan 1 core
cluster module — fork banyak proses
Module cluster membuat proses anak (worker) yang masing-masing punya event loop sendiri. Semuanya berbagi port yang sama (OS round-robin ke masing-masing worker).
import cluster from "cluster";
import os from "os";
import http from "http";
if (cluster.isPrimary) {
const cpus = os.cpus().length;
console.log(`Primary ${process.pid} running, forking ${cpus} workers`);
for (let i = 0; i < cpus; i++) {
cluster.fork(); // buat worker
}
cluster.on("exit", (worker) => {
console.log(`Worker ${worker.process.pid} died, restarting...`);
cluster.fork(); // auto-restart worker yang crash
});
} else {
// Ini worker — jalankan server seperti biasa
http.createServer((req, res) => {
res.end(`Handled by worker ${process.pid}\n`);
}).listen(3000);
}
Di Node 16+ gunakan cluster.isPrimary (dulu namanya cluster.isMaster, sekarang deprecated). cluster.isWorker untuk memeriksa sisi sebaliknya.
Cluster vs Worker Threads
| Worker Threads | cluster | |
|---|---|---|
| Apa? | Thread dalam 1 proses | Proses terpisah |
| Berbagi memori? | Ya (via SharedArrayBuffer) | Tidak — IPC message |
| Use case | CPU-intensive JS (hashing, parsing) | Scale HTTP server ke banyak core |
| Overhead | Ringan | Lebih berat (proses penuh) |
| Crash | Matikan 1 thread | Matikan 1 worker — primary masih hidup |
Aturan jempol:
- HTTP server di server multi-core → cluster (atau PM2 cluster mode)
- Kalkulasi berat per-request (PDF generate, hash bcrypt) → Worker Threads
PM2 cluster mode — lebih praktis
Daripada menulis cluster manual, pakai PM2:
pm2 start app.js -i max # fork sebanyak jumlah CPU core
pm2 start app.js -i 4 # fork 4 worker
pm2 reload app # restart 1-per-1 (zero-downtime)
PM2 menangani: forking, auto-restart, log aggregation, monitoring, graceful reload.
ecosystem.config.cjs:
module.exports = {
apps: [{
name: "api",
script: "src/app.js",
instances: "max",
exec_mode: "cluster",
}],
};
Kapan tidak perlu cluster?
- Deployment sudah di container orchestrator (Kubernetes, ECS) — tiap replica = 1 pod, scaling di level orchestrator
- Di belakang reverse proxy (nginx) yang sudah round-robin ke banyak proses
- Aplikasi I/O-bound ringan yang 1 core sudah cukup
State antar worker?
Setiap worker punya memori sendiri — tidak bisa share in-memory state (misal cache di object). Solusi:
- Redis untuk shared cache/session
- Sticky sessions jika pakai WebSocket (agar client yang sama selalu ke worker yang sama)
- Simpan state di database, bukan di memori proses