Post

What is sharding and why is it important? (dc.js v14)

Introduction

Sharding is an essential concept for large Discord bots (or any service really), allowing them to efficiently scale by distributing workloads across multiple processes. In this blog, I’ll talk to you about what sharding is, why it’s important, how a Shard Manager coordinates the sharding process.

What is Sharding?

A shard is a distinct WebSocket connection to Discord’s servers that handles a subset of your bot’s data. When your bot grows large enough (more than 2,500 guilds), Discord recommends using multiple shards to distribute the data load. Each shard connects separately to Discord and handles events and commands for a subset of guilds.

Why is Sharding Important?

When a bot is small, all events and data flow through a single WebSocket connection. However, as the bot grows and joins more guilds (servers), this connection can become a bottleneck. Sharding splits the bot’s guilds across multiple connections, improving performance (and reducing stress on discords servers) by reducing the load on a single WebSocket.

Example:

  • Shard 0 manages guilds 1 -> 1099.
  • Shard 1 manages guilds 2000 -> 2099, and so on.

This setup ensures the bot stays performant, as each shard is responsible for fewer guilds.

What is a Shard Manager?

A Shard Manager is a process or tool that coordinates the connection of multiple shards to Discord. It ensures that your bot’s shards connect to Discord’s API efficiently without exceeding Discord’s rate limits or max concurrency limits.

The shard manager orchestrates when each shard starts, monitors their status, and handles reconnections in case of disconnections.

Max Concurrency and Gateway Info

Discord limits how many shards can connect to their servers simultaneously, called max concurrency. To avoid hitting rate limits, you need to stagger shard connections based on the value provided by Discord’s /gateway/bot endpoint.

Here’s how you can fetch this information:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const fetch = require('node-fetch');

async function getGatewayInfo(token) {
  const response = await fetch('https://discord.com/api/v10/gateway/bot', {
    method: 'GET',
    headers: {
      'Authorization': `Bot ${token}`
    }
  });
  const data = await response.json();
  console.log(`Recommended Shards: ${data.shards}`);
  console.log(`Max Concurrency: ${data.session_start_limit.max_concurrency}`);
}

getGatewayInfo('your-bot-token');

This data helps you determine how many shards you need and how to manage their connections efficiently.


Setting Up Sharding in Discord.js

Discord.js v14 provides built-in support for sharding through the ShardingManager class. This simplifies the process of setting up and managing shards.

Basic Sharding Example

1
2
3
4
5
6
7
8
9
10
11
const { ShardingManager } = require('discord.js');
const manager = new ShardingManager('./bot.js', {
  token: 'your-bot-token',
  totalShards: 'auto' // Automatically calculate the number of shards
});

manager.on('shardCreate', shard => {
  console.log(`Launched shard ${shard.id}`);
});

manager.spawn(); // Start all shards

What’s Happening in the Code?

  1. ShardingManager: Manages the process of creating and launching shards. It spawns new instances of your bot (bot.js in this case) for each shard.
  2. totalShards: ‘auto’: Tells Discord.js to automatically determine the number of shards based on Discord’s recommendation.
  3. manager.spawn(): Spawns all the required shards. Each shard will handle a portion of the total guilds.

Handling Shard Events

You can listen for various events to monitor the status of each shard, such as when a shard is ready or disconnected.

1
2
3
4
5
6
7
8
9
10
11
manager.on('shardCreate', shard => {
  console.log(`Shard ${shard.id} created`);
});

manager.on('ready', shard => {
  console.log(`Shard ${shard.id} is ready`);
});

manager.on('shardDisconnect', (event, shard) => {
  console.error(`Shard ${shard.id} disconnected`);
});

Example of Sharding with Concurrency

As your bot scales, you may need to connect multiple shards at once while respecting Discord’s max concurrency. Discord’s API allows you to connect only a certain number of shards simultaneously, so you’ll need to batch these connections.

To handle max concurrency, you can modify your ShardingManager to stagger shard connections:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const { ShardingManager } = require('discord.js');
const manager = new ShardingManager('./bot.js', {
  token: 'your-bot-token',
  totalShards: 16,  // Set a fixed number of shards
  shardList: [0, 1, 2, 3] // Specify which shards to spawn concurrently
});

manager.on('shardCreate', shard => {
  console.log(`Launched shard ${shard.id}`);
});

// Connect the shards in batches based on max concurrency
(async () => {
  const maxConcurrency = 4;  // For example, Discord says you can connect 4 at a time
  for (let i = 0; i < manager.totalShards; i += maxConcurrency) {
    await manager.spawn({ shardList: [i, i + 1, i + 2, i + 3] });
  }
})();

This ensures that shards connect in manageable batches, preventing connection spikes and rate-limiting issues.


How Does Discord Assign Guilds to Shards?

Discord assigns guilds to specific shards using the following:

1
SHARD_ID = (GUILD_ID >> 22) % NUM_SHARDS

This ensures that each shard handles an even distribution of guilds. All Direct Messages (DMs) are handled by shard 0, while guilds are distributed across the remaining shards.


Conclusion

If your bot is rapidly expanding, implementing sharding and respecting Discord’s max concurrency will become crucial for maintaining stability and performance.

This post is licensed under CC BY 4.0 by the author.