npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ynode/cluster

v1.12.0

Published

Smart, resource-aware Node.js cluster manager with bounded reload and shutdown orchestration.

Readme

@ynode/cluster

Copyright (c) 2026 Michael Welter [email protected]

npm version License: MIT

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 adjusts the worker pool using event-loop lag, heap, and RSS thresholds.
  • Resiliency: Automatically restarts workers if they crash.
  • Zero-Config Defaults: Works immediately with sensible defaults, but fully configurable.

Installation

npm install @ynode/cluster

Usage

Simply wrap your application startup logic in the run() function.

import cluster from "node:cluster";

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);
    process.on("message", (message) => {
        if (message === "shutdown") {
            void app.close().then(
                () => process.exit(0),
                (err) => {
                    app.log.error(err);
                    process.exit(1);
                },
            );
        }
    });

    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,
});

if (cluster.isPrimary) {
    // Manager APIs are available in the primary process only.
    setInterval(() => {
        console.log(control.getMetrics());
    }, 5000);

    // Trigger zero-downtime reload (e.g., on SIGHUP or API call)
    // await control.reload();
}

Zero-Downtime Reload

You can reload the cluster (e.g. after a code deployment) without dropping connections using control.reload(). This will:

  1. Sequentially start a new worker.
  2. Wait for it to come online, and if the old worker was serving traffic, wait for the replacement to become listening.
  3. Run the optional replacement health check.
  4. Gracefully shut down the old worker and verify that its process exits before continuing.

Only one surge process is permitted during a reload. A replacement that exits, disconnects, or fails the configured health check is reaped without disturbing the original worker. Starting cluster shutdown cancels any active reload.

if (cluster.isPrimary) {
    await control.reload();
    console.log("Reload complete!");
}

Use reloadHealthCheck when "online" or "listening" is too early for your app to receive traffic. Returning false, throwing, rejecting, or exceeding reloadHealthTimeout fails the reload before the old worker is retired.

const healthPorts = new Map();

const control = run(startServer, {
    reloadHealthTimeout: 5000,
    reloadHealthCheck: async (worker) => {
        const port = healthPorts.get(worker.id);
        if (!port) {
            return false;
        }
        const res = await fetch(`http://127.0.0.1:${port}/health`);
        return res.ok;
    },
});

Configuration

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 | Math.min(2, os.availableParallelism()) | Minimum number of workers to keep alive in "smart" mode. | | maxWorkers | number | os.availableParallelism() | Maximum active workers; rolling reload may add one temporary surge process. | | 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 capacity and smart-mode load-scaling checks. | | heartbeatStaleAfter | number | 10000 | Maximum heartbeat age (ms) included in scaling decisions. | | shutdownSignals | string[] | ['SIGINT', 'SIGTERM', 'SIGQUIT'] on POSIX | Supported process signals that trigger graceful shutdown. | | shutdownTimeout | number | 10000 | Time (ms) to wait for workers to shut down before forced exit. | | reloadOnlineTimeout | number | 10000 | Max time (ms) to wait for replacement worker online during reload. | | reloadListeningTimeout | number | 10000 | Max time (ms) to wait for replacement worker listening when replacing a listening worker. | | reloadHealthTimeout | number | 10000 | Max time (ms) to wait for reloadHealthCheck during reload. | | reloadHealthCheck | function | undefined | Optional replacement-worker health check. Returning false, throwing, rejecting, or timing out fails the reload before the old worker is retired. | | reloadDisconnectWait | number | 10000 | Max time (ms) to wait for an old worker to exit gracefully during each reload step. | | tty | object | { enabled: false } | Optional TTY command mode settings for interactive master commands. | | tty.enabled | boolean | false | Enables TTY command mode in the master process. | | tty.commands | boolean | true | Enables command handling when tty.enabled is true. | | tty.reloadCommand | string | "/rl" | Command text that triggers a zero-downtime reload. | | tty.stdin | Readable | process.stdin | Input stream used for TTY command mode; commands start only when isTTY === true. | | tty.stdout | Writable | process.stdout | Output stream used for TTY command mode. | | tty.prompt | string | (none) | Optional prompt text shown by command mode. | | scaleUpMemory | number | 0 | Threshold (MB) for average heap usage to trigger scaling up. | | scaleUpRss | number | 0 | Threshold (MB) for average resident set size to trigger scaling up. | | maxWorkerMemory | number | 0 | Max heap usage (MB) for a worker before restart, evaluated on each heartbeat. | | maxWorkerRss | number | 0 | Max resident set size (MB) for a worker before restart, evaluated on each heartbeat. | | norestart | boolean | false | If true, workers will not be restarted when they die. |

The primary owns process-level termination signals. The first configured signal begins or joins graceful shutdown; any later configured signal immediately kills the remaining workers and exits non-zero. SIGQUIT is graceful by default on POSIX. Worker retirement escalates from an IPC shutdown request to SIGTERM and finally SIGKILL, so the primary never reports a clean shutdown while descendants remain alive.

TTY Command Mode

When tty.enabled is set to true, the master process listens to process.stdin for operations. By default, the following commands are available:

  • /rl - Triggers a zero-downtime cluster reload (can be customized via tty.reloadCommand).
  • /status - Displays active/process/desired capacity plus each worker's state, PID, uptime, load lag, heap, RSS, and listening status.
  • /ping - Pings all active workers over IPC to ensure responsiveness.
  • /version - Gathers and prints the appVersion and Node.js version from all workers.

Accessing Metrics

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 (cluster.isPrimary) {
    const metrics = manager.getMetrics();
    console.log(`Current Lag: ${metrics.avgLag.toFixed(2)}ms`);
    console.log(`Active Workers: ${metrics.workerCount}`);
    console.log(`Worker Processes: ${metrics.processCount}`);
    console.log(`Desired Workers: ${metrics.desiredWorkers}`);
}

workerCount excludes draining workers. processCount includes starting, draining, and the single temporary reload surge process. Per-worker metrics expose lifecycle state, listening/readiness state, heartbeat freshness, heap, RSS, external memory, and array-buffer usage.

Lifecycle Events and Programmatic Shutdown

The returned manager also provides lifecycle events and a programmatic close API:

const manager = run(startWorker, { mode: "smart" });

if (cluster.isPrimary) {
    manager.on("scale_up", (event) => console.log("Scaled up:", event));
    manager.on("reload_fail", (event) => console.error("Reload failed:", event.error));

    await manager.reload();
    await manager.close(); // graceful shutdown without sending OS signals
}

close() settles only after every worker exits. If a process survives the full graceful/termination escalation, close() rejects rather than silently leaving an orphan.

Available event names: worker_online, worker_exit, worker_restart_scheduled, worker_listening, scale_up, scale_down, reload_start, reload_end, reload_fail, shutdown_start, shutdown_end.

Working with @ynode/autoshutdown

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."
  • @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. A voluntary idle exit can reduce a smart pool, but Cluster reconciles immediately when the exit would cross minWorkers. In mode: "max", voluntary exits are replaced to preserve maxWorkers.

Autoshutdown must retire a Node cluster worker with cluster.worker.disconnect(). Cluster uses Node's worker.exitedAfterDisconnect signal to distinguish an intentional idle exit from a crash. A smart pool above its floor reduces desiredWorkers; a failed or non-voluntary exit restores the previous desired capacity. The integration suite verifies both the scale-down-above-floor and floor-restoration cases with real worker processes.

Cluster already owns worker heartbeat and heap/RSS retirement. When using the packages together, leave Autoshutdown reportLoad disabled and configure memory thresholds with Cluster's maxWorkerMemory or maxWorkerRss. @ynode/bootify enforces this ownership split automatically.

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 });
});

License

This project is licensed under the MIT License.