How do you scale a Node.js application and avoid blocking the event loop?
Quick answer
Keep the main thread free by moving CPU-heavy work to worker threads or a job queue, and scale across cores and machines by running multiple processes (cluster or PM2) behind a load balancer with shared, external state.
One Node.js process uses one core for your JavaScript. Run one process per core with the cluster module, PM2 or by running several containers behind a load balancer. Because the processes do not share memory, session data, caches and rate-limit counters must move to an external store such as Redis.
Blocking the event loop stalls every request in that process. Common causes are synchronous file or crypto calls (fs.readFileSync, bcrypt sync versions), parsing huge JSON documents, heavy loops, and regular expressions with catastrophic backtracking. Fix them with async APIs, worker_threads for CPU work, queues such as BullMQ for background jobs, streaming instead of buffering, and input size limits. Measure event loop delay with perf_hooks.monitorEventLoopDelay() and alert on it, and make the app stateless so instances can be added or removed freely.
import { Worker } from "node:worker_threads";
function runInWorker(data) {
return new Promise((resolve, reject) => {
const worker = new Worker("./hash-worker.js", { workerData: data });
worker.once("message", resolve);
worker.once("error", reject);
});
}Key points
- One process per core, behind a load balancer
- Move CPU work to workers or queues
- Keep state in Redis or a database, not in process memory