The Scaling Dilemma: More Cores, More Problems
Our payment processing API was peaking at 80% CPU usage on a 16-core server, yet only one core was doing most of the work. Node.js’s single-threaded nature was holding us back, but the solution wasn’t obvious:
- Should we use clustering? (Fork processes)
- Or Worker Threads? (Share memory, but not state)
We benchmarked both. Here’s what we learned.
Option 1: Clustering (Forking Processes)
How It Works
- Master process forks multiple Node.js instances (one per CPU core).
- Each process has its own memory, event loop, and V8 instance.
Best For:
✔ Stateless APIs (e.g., REST services)
✔ Crash isolation (one process dying doesn’t kill others)
✔ Simple scaling (cluster
module makes it easy)
The Catch:
- Memory overhead (each fork duplicates memory)
- No shared state (need Redis for inter-process comms)
Code Example:
const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
for (let i = 0; i < os.cpus().length; i++) {
cluster.fork(); // One process per CPU core
}
} else {
require('./server'); // Your actual app
}
Our Result:
- 4x throughput on a 4-core machine.
- But memory usage doubled (each fork cached the same data).
Option 2: Worker Threads (Shared CPU, Not Memory)
How It Works
- Single process, but offloads heavy work to threads.
-
Shares memory (via
SharedArrayBuffer
).
Best For:
✔ CPU-heavy tasks (e.g., image processing, math ops)
✔ Avoiding process duplication
✔ Tasks needing shared memory
The Catch:
- Thread-safe coding is hard (race conditions, deadlocks)
- No automatic load balancing (unlike clustering)
Code Example:
const { Worker } = require('worker_threads');
function runWorker(data) {
return new Promise((resolve) => {
const worker = new Worker('./worker.js', { workerData: data });
worker.on('message', resolve);
});
}
// Use for heavy computations, not HTTP requests!
Our Result:
- 30% faster for CPU tasks (vs clustering).
- But crashed once due to a race condition.
When to Use Which?
Scenario | Clustering | Worker Threads |
---|---|---|
Stateless HTTP server | ✅ Best | ❌ Not needed |
Heavy computations | ❌ Overkill | ✅ Best |
Need crash isolation | ✅ Yes | ❌ No |
Shared memory required | ❌ No | ✅ Yes |
Key Takeaways
✔ Clustering = Simple scaling for APIs.
✔ Worker Threads = CPU tasks without forking.
✔ Hybrid? Sometimes both (e.g., cluster + threads for CPU-heavy APIs).
We combined both—clustering for HTTP load balancing and threads for PDF generation.
Have you tried both? What worked (or backfired)?
Top comments (0)