discord-hybrid-sharding

repository·ts-rewrite·Indexed 42 days ago

https://github.com/meister03/discord-hybrid-sharding

A scaling solution for Discord bots that combines process-level clustering with internal sharding to reduce memory overhead. Version 3.0.1 allows hosting multiple shards within a single process, providing a more resource-efficient alternative to standard sharding managers. Features include a ClusterManager for process orchestration, ClusterClient for bot integration, broadcastEval for cross-cluster code execution, and plugins for zero-downtime reclustering, heartbeat monitoring, and automatic resharding.

Tokens
12.3K
Snippets
40
Records
57
Agent score
53%

What's inside discord-hybrid-sharding

  1. How hybrid sharding works

    ts-rewrite

    Hybrid sharding combines process-level sharding (clusters) with internal sharding (shards within a process). This allows you to minimize resource usage by hosting multiple internal shards within a single process/cluster.

    For example, instead of spawning 4 separate processes for 4 shards (consuming ~800MB idle), you can spawn 2 clusters, each containing 2 internal shards (consuming ~400MB idle). This reduces the memory overhead caused by multiple idle Node.js processes while still providing the performance benefits of internal sharding as the bot grows.

  2. Communicate between clusters and clients using the IPC System

    ts-rewrite

    The Inter-Process Communication (IPC) system allows bidirectional communication between the ClusterManager (and its clusters) and the ClusterClient (the main process).

    Communication Types:

    • Normal Messages: Use .send() to send a message without expecting a response.
    • Requests: Use .request() to send a message that requires a reply. This returns a Promise.

    Message Types: Check message._type against messageType.CUSTOM_REQUEST to determine if a message requires a reply using message.reply().

    // In Cluster (via manager.on('clusterCreate'))
    cluster.on('message', message => {
        if (message._type !== messageType.CUSTOM_REQUEST) return;
        message.reply({ content: 'hello world' });
    });
    cluster.request({ content: 'Are you alive?' }).then(res => console.log(res));
    
    // In Client
    client.cluster.on('message', message => {
        if (message._type !== messageType.CUSTOM_REQUEST) return;
        if (message.alive) message.reply({ content: 'Yes I am!' });
    });
    client.cluster.send({ content: 'Hello from client' });
  3. Use Zero Downtime Reclustering to restart or reshard

    ts-rewrite

    The ReClusterManager plugin allows you to reshard, recluster, or restart your bot with minimal downtime. You can choose between two restart modes:

    • gracefulSwitch: Spawns all new clusters in maintenance mode. Once all new clusters are ready (firing the client.cluster.on('ready') event), the old clusters are gracefully killed.
    • rolling: Spawns clusters in maintenance mode. As soon as a single cluster is ready, it exits maintenance mode and the corresponding old cluster is killed. This is not recommended if shardData has not been updated.

    To use it, extend your ClusterManager with new ReClusterManager() and call manager.recluster.start().

    const { ClusterManager, ReClusterManager } = require('discord-hybrid-sharding');
    const manager = new ClusterManager(`${__dirname}/bot.js`, {...});
    
    manager.extend(new ReClusterManager());
    
    // Start reclustering
    const optional = { totalShards, totalClusters };
    manager.recluster?.start({ restartMode: 'gracefulSwitch', ...optional });
  4. Use the AutoResharder plugin

    ts-rewrite

    The AutoResharderManager automatically re-shards the bot when the number of guilds per shard exceeds a configured limit.

    Setup Steps:

    1. Extend the ClusterManager with AutoResharderManager.
    2. On every client, instantiate an AutoResharderClusterClient to send guild count data to the manager.

    Configuration Options for AutoResharderManager:

    • debug: Boolean to enable debug info.
    • ShardsPerCluster: Number of shards per cluster (or 'useManagerOption').
    • MinGuildsPerShard: Minimum guilds per shard (or auto).
    • MaxGuildsPerShard: Maximum guilds per shard before triggering a re-shard.
    • restartOptions: Configuration for the ClusterManager (includes restartMode, delay, and timeout).

    Configuration for AutoResharderClusterClient:

    • sendDataIntervalMS: Interval for sending data (default 60e3).
    • sendDataFunction: A function that returns the clusterId and shardData (an array of { shardId, guildCount }).
    // Manager side
    manager.extend(new AutoResharderManager(this.cluster, {
        debug: true,
        ShardsPerCluster: 'useManagerOption',
        MinGuildsPerShard: 1400,
        MaxGuildsPerShard: 2400,
    }));
    
    // Client side
    client.cluster = new ClusterClient(client);
    new AutoResharderClusterClient(client.cluster, {
        sendDataIntervalMS: 60e3,
        sendDataFunction: (cluster) => {
            return {
                clusterId: cluster.id,
                shardData: cluster.client.guildsche.filter(g => g.shardId === shardId).map(shardId => ({
                    shardId,
                    guildCount: cluster.client.guildsche.filter(g => g.shardId === shardId).size
                }))
            }
        }
    });
  5. Configure ClusterClient in your bot file

    ts-rewrite

    In your bot's entry point (e.g., bot.js), you must attach a ClusterClient to your Discord client to enable cluster-specific functionality.

    To ensure the cluster is fully initialized (e.g., for loading databases or event listeners), listen for the ready event on client.cluster. If you are using a custom library or manual event handling, you may need to call client.cluster.triggerReady() when your client is ready.

    const { ClusterClient } = require('discord-hybrid-sharding');
    const client = new Discord.Client(...);
    client.cluster = new ClusterClient(client);
    
    if (client.cluster.maintenance) console.log(`Bot on maintenance mode with ${client.cluster.maintenance}`);
    
    client.cluster.on('ready', () => {
        // Load Events
        // Handle Database stuff
    });
    
    // If using manual triggers:
    client.on("clientReady", (readyClient) => {
        readyClient.cluster.triggerReady();
    });
    
    client.login(token);
  6. Set up the ClusterClient in your bot file

    ts-rewrite

    In your main bot file (e.g., bot.js), you must configure your Discord client to use the sharding information provided by getInfo() and initialize the ClusterClient.

    Important: You must manually trigger the cluster's readiness by calling client.cluster.triggerReady() when your client is ready. This is necessary because the library remains agnostic of your specific framework's ready event.

    const { ClusterClient, getInfo } = require('discord-hybrid-sharding');
    const Discord = require('discord.js');
    
    const client = new Discord.Client({
        shards: getInfo().SHARD_LIST,
        shardCount: getInfo().TOTAL_SHARDS,
        intents: [],
    });
    
    client.cluster = new ClusterClient(client);
    
    // You must call triggerReady when the client is ready
    client.on("clientReady", (readyClient) => {
        readyClient.cluster.triggerReady();
    });
    
    client.login('YOUR_TOKEN');
  7. Migrating to Discord-Hybrid-Sharding

    ts-rewrite

    If you are migrating from a standard sharding manager (like discord.js's built-in manager), note the following changes in naming and implementation:

    1. Trigger Readiness: You must manually call client.cluster.triggerReady() in your client's ready event.
    2. Property Renaming: Most properties previously under .shard are now under .cluster (e.g., client.shard.id $\rightarrow$ client.cluster.id).
    3. BroadcastEval Context: When using broadcastEval, the context object uses cluster instead of shard (e.g., { cluster: 0 } instead of { shard: 0 }).
    4. Respawn Methods: Method names for respawning have changed (e.g., shardDelay $\rightarrow$ clusterDelay).
    5. Information Access:
      • Total shards: client.cluster.info.TOTAL_SHARDS (previously client.shard.count).
      • Current cluster shards: client.cluster.shardList (array of internal IDs) or client.cluster.shards (WS shards collection).
  8. How AutoResharding works

    ts-rewrite

    Auto-resharding is a two-part system consisting of a Manager and a Cluster Client:

    1. The Cluster Client (AutoResharderClusterClient): Periodically executes a sendDataFunction to collect the number of guilds currently handled by each shard in its cluster. It sends this payload to the Manager via the cluster's communication channel.
    2. The Manager (AutoResharderManager): Receives data from all clusters. It monitors the guildCount of every shard. When any shard's guild count exceeds the configured MaxGuildsPerShard, the manager triggers a recluster.

    Requirements:

    • The ClusterManager must be extended with the ReCluster plugin for auto-resharding to function, as the manager uses the recluster plugin to perform the actual shard redistribution.
    • This plugin is currently not compatible with discord-cross-hosting or custom shardList specifications.
  9. Configure restart limits with Control Restarts

    ts-rewrite

    You can prevent infinite restart loops by capping the number of restarts allowed per cluster within a specific time interval using the restarts option in the ClusterManager configuration.

    const manager = new ClusterManager(`${__dirname}/bot.js`, {
        ...YourOptions,
        restarts: {
            max: 5, // Maximum amount of restarts per cluster
            interval: 60000 * 60, // Interval to reset restarts
        },
    });
  10. Use the Cluster class to manage individual processes

    ts-rewrite
    The Cluster class represents a single self-contained process or worker thread within the sharding system. Each cluster contains its own DjsDiscordClient instance. The ClusterManager handles the lifecycle of these clusters, automatically respawning them if they exit, provided the restart limits haven't been exceeded.
  11. Understand the IPCMessage structure and lifecycle

    ts-rewrite

    An IPCMessage is a wrapper around a RawMessage. When an IPCMessage is created, it ensures a nonce (a unique identifier) exists. This nonce is critical for matching requests to replies.

    Message Types

    Messages are categorized by the _type property using messageType:

    • CUSTOM_MESSAGE: Standard one-way communication.
    • CUSTOM_REQUEST: A message that expects a response.
    • CUSTOM_REPLY: A response sent back to a requester.

    Data Integrity

    • Every IPCMessage maintains a raw property which contains the original data structure.
    • The toJSON() method can be called on a BaseMessage to retrieve the underlying RawMessage object for transmission.