Enabling Multi-CPU Usage in Node.js
The Problem
When traffic spiked, my app started crashing 😅 Despite running in a pod with 2 CPU cores, it was happily chugging along on just one. The other core? Just sitting there, sipping tea or whatever idle cores do.
Turns out, Node.js doesn’t automatically use multiple cores, so even though more resources were available, the app hit its limit and gave up.
Here’s how I scaled my app across multiple cores using the built-in cluster module, plus a quick dive into why this approach isn’t true multithreading—and why that’s perfectly fine.
🔧 The setup
App Side
Use the cluster
module to spin up multiple Node.js processes — each one gets its own core.
1import cluster from 'cluster'2import os from 'os'34const defaultProcessCount = os.cpus().length5const clusterSize = process.env.PROCESS_COUNT ?? defaultProcessCount67if (cluster.isPrimary) {8 for (let i = 0; i < clusterSize; i++) {9 cluster.fork()10 }1112 cluster.on('exit', (worker) => {13 console.log(`worker ${worker.process.pid} died`)14 })15} else {16 // app logic goes here17}
Kubernetes Side
On the infra side (e.g. Kubernetes), I made sure to assign enough CPUs by adding this to the deployment:
1resources:2 limits:3 cpu: '2'4 memory: '2048Mi'
💡 Make sure to match the number of processes with the available CPU cores. If 4 processes are forked but only 1 CPU is given to the pod, they’ll just fight over that single core.
🧠 multi-process ≠ multi-threaded
Using cluster
means multi-process, not multi-threaded.
Each forked worker is a completely separate process. They don’t share memory, meaning memory usage will increase with each process.
This is why it’s still not as efficient as something like Go or Java, where threads are managed inside the same process and share memory space.
Still, for web apps with lots of I/O, this kind of parallelism works really well.
💭 Why JS is single-threaded
“JavaScript was designed to be single-threaded so that developers could write code without worrying about race conditions and thread safety.” — MDN Web Docs
Since JS was originally made for the browser, the single-thread model kept things simple.
If it were multi-threaded, the DOM would be a nightmare to manage with concurrent updates.
Instead, async behavior is handled via the event loop — which is why async/await
and setTimeout
feel so natural.
✅ wrap-up
cluster.fork()
= multiple Node.js processes- But not real threads — memory isn’t shared
- Update CPU limits in Kubernetes to match how many processes I wanna run
- JS is single-threaded by design, and I’m cool with that