Clustering & Multi-Core

Use Every Core With Node Cluster (or PM2)

Clustering & Multi-Core

Node runs JS on a single thread. Spawn one process per core to saturate the CPU and serve more requests per second.

4 min read Level 2/5 #fastify#cluster#performance
What you'll learn
  • Use node cluster or PM2 cluster mode
  • Share a port across forked workers
  • Handle SIGTERM gracefully in every worker

A single Node process uses one CPU core. On a four-core box that leaves 75% of your CPU on the table. The fix is to run one Node process per core; the OS routes connections to whichever worker is free.

Option 1: node:cluster

import cluster from 'node:cluster'
import os from 'node:os'

if (cluster.isPrimary) {
  const workers = Number(process.env.WEB_CONCURRENCY ?? os.availableParallelism())
  for (let i = 0; i < workers; i++) cluster.fork()
  cluster.on('exit', () => cluster.fork())
} else {
  const { start } = await import('./server.js')
  await start()
}

The primary forks workers and replaces any that crash. Workers share the listening port, so clients only see a single endpoint.

Option 2: PM2

PM2 wraps the same idea with a process manager and a dashboard.

npm install -g pm2
pm2 start dist/index.js -i max --name web
pm2 logs web
pm2 reload web

pm2 reload does a rolling restart — handy for zero-downtime deploys.

Graceful Shutdown In Every Worker

Each worker must handle SIGTERM so reloads are clean:

const app = buildApp()
await app.listen({ port: 3000, host: '0.0.0.0' })

for (const sig of ['SIGTERM', 'SIGINT'] as const) {
  process.once(sig, async () => {
    app.log.info({ sig }, 'shutting down')
    await app.close()
    process.exit(0)
  })
}

If you serve WebSockets, configure your load balancer for sticky sessions — otherwise the upgrade and the subsequent traffic might land on different workers.

Microservices With Fastify →