README
Discord-Hybrid-Sharding
The first package which combines sharding manager & internal sharding to save a lot of resources, which allows clustering!
In other words: "Mixing both: if you need x
shards for n
process!"
If you are interested in auto-scaling & cross-hosting on other machines, check out this package npmjs.com/discord-cross-hosting
Why?
The sharding manager is very heavy and uses more than 300MB per shard during light usage, while internal sharding uses just 20% of it. Internal sharding reaches its' limits at 14000 guilds and becomes slow when your bot gets bigger.
Your only solution becomes converting to the sharding manager. That's why this new package will solve all your problems (tested by many bots with 20-170k guilds), because it spawns shards, which has internal shards. You can save up to 60% on resources!
- Decentralized ClusterEval function -> Listenerless, less memory leaks & cluster/client doesn't have to be ready
- Heartbeat System -> Respawn unresponsive or dead
ClusterClient
s - IPC System -> Client <-> ClusterManager ->
.request()
,.reply()
,.send()
- Memory efficient -> 60% less memory when clustering
- Debug event -> A good overview of cluster information
- EvalOnManager function & other cool functions you need...
Strings
&Functions with Context
support on.broadcastEval()
- Optional timeout feature on
.broadcastEval()
to prevent memory leaks - Supports cross-hosting:
Shard/Cluster
managing and cross-host communication (.broadcastEval()
,IPC
) - Supports syncing Discord rate limits globally Scroll down to check our new functions!
How does it work?
There are clusters/master shards, which behave like regular shards in the sharding manager and spawns clusters in addition to internal shards. You do not have to spawn as many regular Shards (master shards), which can be replaced with internal shards.
"for process n
, n
internal shards"
Example: A Discord bot with 4000 guilds
Normaly we would spawn 4 shards with the Sharding Manager (~4 x 300MB memory
), but in this case we start with 2 clusters/master shards, which spawns 2 internal shards ==> We just saved 2 shards in comparison to the regular Sharding Manager (~2 x 300MB memory
).
See below for the Guide
If you need help, feel free to join our Discord server. ☺
Download
npm i discord-hybrid-sharding
------ or ---------------------
yarn add discord-hybrid-sharding
Discord.js v13
- Full Discord.js v13 support
Strings
andFunctions
withcontext
are supported in.broadcastEval()
- Most public methods accept sole objects, such as
.spawn({ amount: 20, timeout: -1 })
- Very similar functions to the Discord.js ShardingManager and more for the advanced usage
Setting up
First, add the module into your project (into your shard/cluster file).
Filename: Cluster.js
const Cluster = require('discord-hybrid-sharding');
const manager = new Cluster.Manager(`${__dirname}/bot.js`, {
totalShards: 7 , // or 'auto'
/// Check below for more options
shardsPerClusters: 2,
// totalClusters: 7,
mode: 'process' , // you can also choose "worker"
token: 'YOUR_TOKEN',
});
manager.on('clusterCreate', cluster => console.log(`Launched Cluster ${cluster.id}`));
manager.spawn({ timeout: -1 });
After that, insert the code below into your bot.js
file
const Cluster = require('discord-hybrid-sharding');
const Discord = require('discord.js');
const client = new Discord.Client({
shards: Cluster.data.SHARD_LIST, // An array of shards that will get spawned
shardCount: Cluster.data.TOTAL_SHARDS, // Total number of shards
});
client.cluster = new Cluster.Client(client); // initialize the Client, so we access the .broadcastEval()
client.login('YOUR_TOKEN');
Eval-ing over clusters
Following examples assume that your Discord.Client
is called client
.
client.cluster.broadcastEval(`this.guilds.cache.size`)
.then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`));
// or with a callback function
client.cluster.broadcastEval(c => c.guilds.cache.size)
.then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`));
Cluster.Manager
Option | Type | Default | Description |
---|---|---|---|
totalShards | number or string | "auto" | Amount of internal shards which will be spawned |
totalClusters | number or string | "auto" | Amount of processes/clusters which will be spawned |
shardsPerClusters | number or string | - | Amount of shards which will be in one process/cluster |
shardList | Array[number] | - | OPTIONAL - On cross-hosting or spawning specific shards you can provide a shardList of internal Shard IDs, which will get spawned |
mode | "worker" or "process" | "worker" | Cluster.Manager mode for the processes |
token | string | - | OPTIONAL -Bot token is only required totalShards are set to "auto" |
The Manager.spawn options are the same as for Sharding Manager
Cluster Events
Event | Description |
---|---|
clusterCreate | Triggered when a Cluster gets spawned |
Cluster Client Properties
All properties like for .broadcastEval()
are available, just replace the client.shard
with client.cluster
Other properties:
| Property | Description |
| ------------- | -------------- |
| client.cluster.count | Returns the amount of all clusters |
| client.cluster.id | Returns the current cluster ID |
| client.cluster.ids | Returns all internal shards of the cluster |
Feel free to contribute/suggest or contact me on my Discord server or in DM: Meister#9667
Changes | Migrating to Discord-Hybrid-Sharding
Options are now labelled as cluster
instead of shard
:
- client.shard...
+ client.cluster...
- .broadcastEval((c, context) => c.guilds.cache.get(context.guildId), { context: { guildId: '1234' }, shard: 0 })
+ .broadcastEval((c, context) => c.guilds.cache.get(context.guildId), { context: { guildId: '1234' }, cluster: 0 })
Small changes in naming conventions:
- client.shard.respawnAll({ shardDelay = 5000, respawnDelay = 500, timeout = 30000 })
+ client.cluster.respawnAll({ clusterDelay = 5000, respawnDelay = 5500, timeout = 30000 })
- manager.shard.respawnAll({ shardDelay = 5000, respawnDelay = 500, timeout = 30000 })
+ manager.respawnAll({ clusterDelay = 5000, respawnDelay = 5500, timeout = 30000 })
Get current cluster ID:
- client.shard.id
+ client.cluster.id
Get current shard ID:
- client.shard.id
+ message.guild.shardId
Get total shards count:
- client.shard.count
+ client.cluster.info.TOTAL_SHARDS
Get all ShardID's in the current cluster:
- client.shard.id
+ [...client.cluster.ids.keys()]
New functions & events:
HeartbeatSystem
- Checks if Cluster/Client sends a heartbeat on a given interval
- When the Client doesn't send a heartbeat, it will be marked as dead/unresponsive
- Cluster will get respawned after the given amount of missed heartbeats has been reached
const manager = new Cluster.Manager(`${__dirname}/bot.js`, {
totalShards: 8,
shardsPerClusters: 2,
keepAlive: {
interval: 2000, // Interval to send a heartbeat
maxMissedHeartbeats: 5, // Maximum amount of missed Heartbeats until Cluster will get respawned
maxClusterRestarts: 3 // Maximum Amount of restarts that can be performed in 1 hour in the HeartbeatSystem
},
});
EvalOnCluster
Decentralized ClusterClient eval function taht doesn't open any listeners and minimizes the risk of creating a memory leak during .broadcastEval()
- Build-in eval timeout which resolves after a given time
- No additional listeners - less memory leaks, better than
.broadCastEval()
- Client & all clusters don't need to be ready
client.cluster.evalOnCluster('this.cluster.id', { cluster: 0, timeout: 10000 });
IPC System
- The IPC System allows you to listen to your messages
- You can communicate between the cluster and the client
- This allows you to send requests from the client to the cluster and reply to them and vice versa
- You can also send normal messages which do not need to be replied
ClusterManager | cluster.js
const Cluster = require('discord-hybrid-sharding');
const manager = new Cluster.Manager(`${__dirname}/testbot.js`, {
totalShards: 1,
totalClusters: 1,
});
manager.on('clusterCreate', cluster => {
cluster.on('message', message => {
console.log(message);
if (!message._sRequest) return; // Check if the message needs a reply
message.reply({ content: 'hello world' });
});
setInterval(() => {
cluster.send({ content: 'I am alive' }); // Send a message to the client
cluster.request({ content: 'Are you alive?', alive: true }).then(e => console.log(e)); // Send a message to the client
}, 5000);
});
manager.spawn({timeout: -1})
ClusterClient | client.js
const Cluster = require('discord-hybrid-sharding');
const Discord = require('discord.js');
const client = new Discord.Client({
shards: Cluster.data.SHARD_LIST, // An array of shards that will get spawned
shardCount: Cluster.data.TOTAL_SHARDS, // Total number of shards
});
client.cluster = new Cluster.Client(client);
client.cluster.on('message', message => {
console.log(message);
if(!message._sRequest) return; // Check if the message needs a reply
if(message.alive) message.reply({ content: 'Yes I am!' }):
});
setInterval(() => {
client.cluster.send({ content: 'I am alive as well!' });
}, 5000);
client.login('YOUR_TOKEN');
Evals a Script on the ClusterManager:
client.cluster.evalOnManager('process.memoryUsage().rss / 1024 ** 2');
Listen to debug messages and internal stuff:
manager.on('debug', console.log);
Optional Timeout on broadcastEval (Promise will get rejected after given time):
client.cluster.broadcastEval('new Promise((resolve, reject) => {})', { timeout: 10000 });
Open a PR/Issue when you need other Functions :)
Bugs, glitches and sssues
If you encounter any problems feel free to open an issue in our GitHub repository or join the Discord server.
Credits
Credits goes to the discord.js libary for the Base Code (See changes.md
) and to this helpful server