Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/disgoorg/disgo/llms.txt

Use this file to discover all available pages before exploring further.

As your Discord bot grows, you’ll need to shard your gateway connection across multiple WebSocket connections. DisGo provides a powerful shard manager with support for custom sharding strategies, auto-scaling, and advanced shard management.

Understanding sharding

Sharding splits your bot’s guilds across multiple WebSocket connections. Each shard handles a subset of guilds based on the formula:
shard_id = (guild_id >> 22) % shard_count
Discord recommends approximately 1,000 guilds per shard, but the optimal count depends on your bot’s workload.
Discord requires sharding once your bot reaches 2,500 guilds. You can implement sharding earlier to improve performance and reliability.

Setting up the shard manager

Replace the standard gateway connection with a shard manager:
import (
    "context"
    "github.com/disgoorg/disgo"
    "github.com/disgoorg/disgo/bot"
    "github.com/disgoorg/disgo/gateway"
    "github.com/disgoorg/disgo/sharding"
)

client, err := disgo.New(token,
    bot.WithShardManagerConfigOpts(
        sharding.WithGatewayConfigOpts(
            gateway.WithIntents(
                gateway.IntentGuilds,
                gateway.IntentGuildMessages,
            ),
        ),
    ),
)
if err != nil {
    panic(err)
}
defer client.Close(context.TODO())

// Open all shards
if err = client.OpenShardManager(context.TODO()); err != nil {
    panic(err)
}
When using the shard manager, Discord automatically determines the recommended shard count based on your bot’s guild count.

Manual shard configuration

You can manually specify shard IDs and shard count:
client, err := disgo.New(token,
    bot.WithShardManagerConfigOpts(
        sharding.WithShardIDs(0, 1, 2, 3), // Manage shards 0-3
        sharding.WithShardCount(4),         // Total of 4 shards
        sharding.WithGatewayConfigOpts(
            gateway.WithIntents(gateway.IntentGuilds),
        ),
    ),
)
If you specify shard IDs manually, you must also set the shard count. The shard count must match across all instances of your bot.

Distributed sharding

For very large bots, you can distribute shards across multiple processes or servers:

Process 1 (shards 0-1)

client, err := disgo.New(token,
    bot.WithShardManagerConfigOpts(
        sharding.WithShardIDs(0, 1),
        sharding.WithShardCount(4),
    ),
)

Process 2 (shards 2-3)

client, err := disgo.New(token,
    bot.WithShardManagerConfigOpts(
        sharding.WithShardIDs(2, 3),
        sharding.WithShardCount(4),
    ),
)
1

Calculate shard distribution

Determine how many total shards you need and distribute them across processes:
totalShards := 16
shardsPerProcess := 4
processID := 0 // 0, 1, 2, or 3

startShard := processID * shardsPerProcess
endShard := startShard + shardsPerProcess
2

Configure each process

Each process manages its assigned shard range:
shardIDs := make([]int, shardsPerProcess)
for i := 0; i < shardsPerProcess; i++ {
    shardIDs[i] = startShard + i
}

client, err := disgo.New(token,
    bot.WithShardManagerConfigOpts(
        sharding.WithShardIDs(shardIDs...),
        sharding.WithShardCount(totalShards),
    ),
)
3

Coordinate shard count

Ensure all processes use the same total shard count. Consider using environment variables:
totalShards, _ := strconv.Atoi(os.Getenv("TOTAL_SHARDS"))
processID, _ := strconv.Atoi(os.Getenv("PROCESS_ID"))

Auto-scaling shards

DisGo can automatically re-shard when Discord requests it:
client, err := disgo.New(token,
    bot.WithShardManagerConfigOpts(
        sharding.WithAutoScaling(true),
        sharding.WithShardSplitCount(2), // Split shards in 2 when scaling
    ),
)
When a shard receives a SHARDING_REQUIRED close code, the shard manager:
  1. Closes the problematic shard
  2. Calculates new shard IDs based on ShardSplitCount
  3. Opens new shards to replace the old one

Split count example

With ShardSplitCount: 2:
  • Shard 0 splits into shards 0 and 4
  • Shard 1 splits into shards 1 and 5
  • Total shard count doubles from 4 to 8
sharding.WithAutoScaling(true)
sharding.WithShardSplitCount(2) // Default
Auto-scaling changes your shard count dynamically. If you’re using distributed sharding, you’ll need to coordinate scaling across processes.

Shard lifecycle management

Opening specific shards

// Open shard 0
err := client.ShardManager().OpenShard(context.TODO(), 0)
if err != nil {
    slog.Error("failed to open shard", slog.Int("shard", 0), slog.Any("err", err))
}

Closing specific shards

// Close shard 0
client.ShardManager().CloseShard(context.TODO(), 0)

Resuming shards

If you’ve stored session information, you can resume shards:
state := sharding.ShardState{
    SessionID: "saved_session_id",
    Sequence:  12345,
    ResumeURL: "wss://gateway.discord.gg",
}

err := client.ShardManager().ResumeShard(context.TODO(), 0, state)
if err != nil {
    slog.Error("failed to resume shard", slog.Any("err", err))
}

Getting shard information

// Get specific shard
shard := client.ShardManager().Shard(0)
if shard != nil {
    fmt.Printf("Shard %d status: %s\n", shard.ShardID(), shard.Status())
}

// Get shard for a guild
guildShard := client.ShardManager().ShardByGuildID(guildID)

// Iterate all shards
for shard := range client.ShardManager().Shards() {
    fmt.Printf("Shard %d: %d guilds\n", shard.ShardID(), shard.GuildCount())
}

Shard-aware events

Some events provide shard-specific information:
import "github.com/disgoorg/disgo/events"

client, err := disgo.New(token,
    bot.WithEventListeners(&events.ListenerAdapter{
        OnGuildReady: func(event *events.GuildReady) {
            slog.Info("guild ready",
                slog.Any("guild_id", event.GuildID),
                slog.Int("shard_id", event.ShardID),
            )
        },
        OnGuildsReady: func(event *events.GuildsReady) {
            slog.Info("all guilds ready on shard",
                slog.Int("shard_id", event.ShardID),
                slog.Int("guild_count", len(event.Guilds)),
            )
        },
    }),
)

Rate limiting

The shard manager includes a global identify rate limiter to prevent exceeding Discord’s limits.

Custom rate limiter

import "golang.org/x/time/rate"

type customRateLimiter struct {
    limiter *rate.Limiter
}

func (r *customRateLimiter) Wait(ctx context.Context) error {
    return r.limiter.Wait(ctx)
}

func (r *customRateLimiter) Close() {
    // Cleanup
}

client, err := disgo.New(token,
    bot.WithShardManagerConfigOpts(
        sharding.WithIdentifyRateLimiter(&customRateLimiter{
            limiter: rate.NewLimiter(rate.Every(6*time.Second), 1),
        }),
    ),
)

Configure default rate limiter

client, err := disgo.New(token,
    bot.WithShardManagerConfigOpts(
        sharding.WithIdentifyRateLimiterConfigOpt(
            gateway.WithIdentifyRateLimiterMaxConcurrency(1),
        ),
    ),
)

Persisting shard state

For zero-downtime restarts, persist shard session information:
type ShardState struct {
    ShardID   int
    SessionID string
    Sequence  int
    ResumeURL string
}

// Save state before shutdown
func saveShardStates(manager sharding.ShardManager) map[int]sharding.ShardState {
    states := make(map[int]sharding.ShardState)
    
    for shard := range manager.Shards() {
        states[shard.ShardID()] = sharding.ShardState{
            SessionID: shard.SessionID(),
            Sequence:  shard.Sequence(),
            ResumeURL: shard.ResumeURL(),
        }
    }
    
    return states
}

// Resume with saved states
func resumeShards(manager sharding.ShardManager, states map[int]sharding.ShardState) {
    for shardID, state := range states {
        err := manager.ResumeShard(context.TODO(), shardID, state)
        if err != nil {
            slog.Error("failed to resume shard",
                slog.Int("shard_id", shardID),
                slog.Any("err", err),
            )
        }
    }
}

Complete sharding example

Here’s a complete example with distributed sharding and state persistence:
package main

import (
    "context"
    "encoding/json"
    "log/slog"
    "os"
    "os/signal"
    "strconv"
    "syscall"

    "github.com/disgoorg/disgo"
    "github.com/disgoorg/disgo/bot"
    "github.com/disgoorg/disgo/events"
    "github.com/disgoorg/disgo/gateway"
    "github.com/disgoorg/disgo/sharding"
)

func main() {
    token := os.Getenv("TOKEN")
    totalShards, _ := strconv.Atoi(os.Getenv("TOTAL_SHARDS"))
    processID, _ := strconv.Atoi(os.Getenv("PROCESS_ID"))
    shardsPerProcess := 4

    // Calculate shard range for this process
    startShard := processID * shardsPerProcess
    shardIDs := make([]int, shardsPerProcess)
    for i := 0; i < shardsPerProcess; i++ {
        shardIDs[i] = startShard + i
    }

    // Load saved states if available
    savedStates := loadShardStates()
    states := make(map[int]sharding.ShardState)
    for _, id := range shardIDs {
        if state, ok := savedStates[id]; ok {
            states[id] = state
        }
    }

    client, err := disgo.New(token,
        bot.WithShardManagerConfigOpts(
            sharding.WithShardIDsWithStates(states),
            sharding.WithShardCount(totalShards),
            sharding.WithAutoScaling(true),
            sharding.WithGatewayConfigOpts(
                gateway.WithIntents(
                    gateway.IntentGuilds,
                    gateway.IntentGuildMessages,
                ),
            ),
        ),
        bot.WithEventListeners(&events.ListenerAdapter{
            OnGuildsReady: func(event *events.GuildsReady) {
                slog.Info("shard ready",
                    slog.Int("shard_id", event.ShardID),
                    slog.Int("guilds", len(event.Guilds)),
                )
            },
        }),
    )
    if err != nil {
        panic(err)
    }

    if err = client.OpenShardManager(context.TODO()); err != nil {
        panic(err)
    }

    slog.Info("bot running",
        slog.Int("process_id", processID),
        slog.Any("shard_ids", shardIDs),
    )

    // Wait for interrupt
    s := make(chan os.Signal, 1)
    signal.Notify(s, syscall.SIGINT, syscall.SIGTERM)
    <-s

    // Save shard states before shutdown
    saveShardStates(client.ShardManager())

    client.Close(context.TODO())
}

func loadShardStates() map[int]sharding.ShardState {
    data, err := os.ReadFile("shard_states.json")
    if err != nil {
        return nil
    }

    var states map[int]sharding.ShardState
    json.Unmarshal(data, &states)
    return states
}

func saveShardStates(manager sharding.ShardManager) {
    states := make(map[int]sharding.ShardState)

    for shard := range manager.Shards() {
        states[shard.ShardID()] = sharding.ShardState{
            SessionID: shard.SessionID(),
            Sequence:  shard.Sequence(),
            ResumeURL: shard.ResumeURL(),
        }
    }

    data, _ := json.MarshalIndent(states, "", "  ")
    os.WriteFile("shard_states.json", data, 0644)
}

Configuration options

  • WithShardIDs(...int) - Specify shard IDs to manage
  • WithShardIDsWithStates(map[int]ShardState) - Specify shards with resume states
  • WithShardCount(int) - Set total shard count
  • WithShardSplitCount(int) - How many shards to split into when scaling
  • WithAutoScaling(bool) - Enable automatic re-sharding
  • WithLogger(*slog.Logger) - Custom logger
  • WithGatewayCreateFunc(gateway.CreateFunc) - Custom gateway factory
  • WithGatewayConfigOpts(...gateway.ConfigOpt) - Configure all gateways
  • WithIdentifyRateLimiter(gateway.IdentifyRateLimiter) - Custom rate limiter
  • WithCloseHandler(gateway.CloseHandlerFunc) - Handle shard closures

Best practices

Don’t wait until you hit the 2,500 guild limit. Start sharding around 1,000-1,500 guilds:
// Good: Shard early
if guildCount > 1000 {
    // Enable sharding
}
Auto-scaling is powerful but can complicate distributed setups. Consider manual scaling for distributed bots:
// For single-process bots
sharding.WithAutoScaling(true)

// For distributed bots
sharding.WithAutoScaling(false)
sharding.WithShardCount(precalculatedCount)
Track shard status and reconnection patterns:
for shard := range client.ShardManager().Shards() {
    metrics.RecordShardStatus(
        shard.ShardID(),
        shard.Status(),
        shard.Latency(),
    )
}
Save shard session information before shutting down for faster reconnection:
func shutdown(manager sharding.ShardManager) {
    saveShardStates(manager)
    manager.Close(context.Background())
}

Build docs developers (and LLMs) love