Copyright (c) 2025 Michael Welter me@mikinho.com
Smart & Easy Node.js Clustering.
@ynode/cluster removes the complexity of managing Node.js cluster processes. It provides out-of-the-box support for:
- Smart Auto-Scaling: Automatically spawns and kills workers based on Event Loop Lag (CPU load).
- Resiliency: Automatically restarts workers if they crash.
- Zero-Config Defaults: Works immediately with sensible defaults, but fully configurable.
npm install @ynode/clusterSimply wrap your application startup logic in the run() function.
import { run } from "@ynode/cluster";
import Fastify from "fastify";
// Define your worker logic
const startServer = async () => {
const app = Fastify({ logger: true });
app.get("/", async () => "Hello from worker " + process.pid);
try {
await app.listen({ port: 3000 });
} catch (err) {
app.log.error(err);
process.exit(1);
}
};
// Start the cluster
const control = run(startServer, {
mode: "smart",
minWorkers: 2,
maxWorkers: 4
});
// Access metrics
setInterval(() => {
console.log(control.getMetrics());
}, 5000);
// Trigger zero-downtime reload (e.g., on SIGHUP or API call)
// control.reload();You can reload the cluster (e.g. after a code deployment) without dropping connections using control.reload(). This will:
- Sequentially start a new worker.
- Wait for it to come online.
- Gracefully shutdown the old worker.
await control.reload();
console.log("Reload complete!");The run(startWorker, options) function accepts the following options:
| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean |
true |
Whether to enable clustering. If false, runs startWorker directly in the main process. |
mode |
"smart" | "max" |
"smart" |
"smart" enables auto-scaling based on load. "max" spawns maxWorkers and keeps them running. |
minWorkers |
number |
2 |
Minimum number of workers to keep alive in "smart" mode. |
maxWorkers |
number |
os.cpus() |
Maximum number of workers to spawn. |
scaleUpThreshold |
number |
50 |
Event loop lag (ms) threshold to trigger scaling up. |
scaleDownThreshold |
number |
10 |
Event loop lag (ms) threshold to trigger scaling down. |
scalingCooldown |
number |
10000 |
Minimum time (ms) between scaling actions. |
scaleDownGrace |
number |
30000 |
Grace period (ms) after scaling up before scaling down is allowed. |
autoScaleInterval |
number |
5000 |
Interval (ms) for auto-scaling checks in "smart" mode. |
shutdownSignals |
string[] |
['SIGINT', 'SIGTERM', 'SIGQUIT'] |
Signals to listen for to trigger graceful shutdown. |
shutdownTimeout |
number |
10000 |
Time (ms) to wait for workers to shutdown before forced exit. |
scaleUpMemory |
number |
0 |
Threshold (MB) for average heap usage to trigger scaling up. |
maxWorkerMemory |
number |
0 |
Max heap usage (MB) for a worker before restart (Leak Protection). |
norestart |
boolean |
false |
If true, workers will not be restarted when they die. |
The run() function returns a ClusterManager instance (when in cluster mode) which exposes current metrics.
const manager = run(startWorker, { mode: "smart" });
// In your monitoring loop or API endpoint:
if (manager) {
const metrics = manager.getMetrics();
console.log(`Current Lag: ${metrics.avgLag.toFixed(2)}ms`);
console.log(`Active Workers: ${metrics.workerCount}`);
}This package works seamlessly with @ynode/autoshutdown.
While @ynode/cluster manages the pool size based on overall system load (scaling up when busy, down when quiet), @ynode/autoshutdown manages the lifecycle of individual workers based on their specific inactivity.
- @ynode/cluster: "We are overloaded, add more workers!" or "We are effectively idle, remove the extra workers."3
- @ynode/autoshutdown: "I haven't received a request in 10 minutes, I should shut down to save memory."
Using them together ensures optimal resource usage: responsive scaling for traffic spikes and aggressive cleanup for idle periods.
import { run } from "@ynode/cluster";
import autoShutdown from "@ynode/autoshutdown";
import Fastify from "fastify";
run(async () => {
const app = Fastify();
// Register auto-shutdown to kill this specific worker if it's unused
await app.register(autoShutdown, {
sleep: 600, // 10 minutes
});
await app.listen({ port: 3000 });
});This project is licensed under the MIT License.