[object Object]
Discord Gateway v10 Sharding & Rate Limiting: Architektur ab 2.500 Servern
Sobald ein Discord-Bot auf mehr als 2.500 Servern (Guilds) aktiv ist, erzwingt Discord zwingend das sogenannte Gateway Sharding. Ohne Sharding verweigert das Discord Gateway Verbindungsanfragen. Doch auch unterhalb dieser Grenze ist Sharding die wichtigste Architekturmaßnahme zur Vermeidung von CPU-Bottlenecks, Gateway-Disconnects und globalen Rate-Limits.
In diesem Leitfaden betrachten wir die mathematischen Grundlagen des Shardings, die Funktionsweise von Discord v10 Rate-Limit-Buckets und den Aufbau eines stabilen Sharding-Managers.
1. Wie Discord Gateway Sharding funktioniert
Ein Shard ist eine eigenständige WebSocket-Verbindung zum Discord Gateway. Discord weist jeden Server anhand einer deterministischen mathematischen Formel einem spezifischen Shard zu:
$$\text{shard_id} = (\text{guild_id} \gg 22) \pmod{\text{total_shards}}$$
Dadurch ist garantiert, dass Events für einen bestimmten Server immer auf genau demselben Shard empfangen werden.
┌─────────────────────────┐
│ Discord Gateway (v10) │
└────────────┬────────────┘
┌───────────────────────┼───────────────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ Shard 0 │ │ Shard 1 │ │ Shard 2 │
│ Guilds %3 │ │ Guilds %3 │ │ Guilds %3 │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
└───────────────────────┼───────────────────────┘
▼
┌─────────────────────────┐
│ KiroCloud Worker Pool │
└─────────────────────────┘
2. Implementierung mit dem ShardingManager
In Node.js starten wir Shards über den ShardingManager, der jeden Shard in einem separaten Node.js-Prozess oder Worker-Thread ausführt:
// bot-cluster.ts
import { ShardingManager } from 'discord.js';
import path from 'node:path';
const manager = new ShardingManager(path.join(__dirname, 'bot-worker.ts'), {
token: process.env.DISCORD_BOT_TOKEN,
totalShards: 'auto', // Discord empfiehlt automatisch die ideale Anzahl
respawn: true, // Automatischer Neustart bei Prozessabsturz
mode: 'worker', // 'worker' (Threads) oder 'process' (Child Processes)
});
manager.on('shardCreate', (shard) => {
console.log(`[Cluster] Shard #${shard.id} initialisiert und gestartet.`);
shard.on('disconnect', () => {
console.warn(`[Cluster] Shard #${shard.id} Verbindung verloren.`);
});
shard.on('reconnecting', () => {
console.log(`[Cluster] Shard #${shard.id} verbindet erneut...`);
});
});
manager.spawn({
amount: 'auto',
delay: 5500, // WICHTIG: Mindestens 5,5 Sekunden Pause zwischen Shard-Spawns (Discord Rate-Limit)
timeout: 30000,
}).then(() => {
console.log('[Cluster] Alle Shards erfolgreich mit Discord Gateway verbunden.');
}).catch((err) => {
console.error('[Cluster] Kritischer Fehler beim Spawnen der Shards:', err);
});
!IMPORTANTDas Identify-Rate-Limit: Discord erlaubt nur 1 Gateway-Identify alle 5 Sekunden pro Shard-Block (
max_concurrency). Ein zu schneller Spawn führt zum temporären IP-Ban (HTTP 429). Setze dendelay-Parameter daher immer auf mindestens 5.500 ms.
3. Shard-übergreifende Kommunikation (Cross-Shard Messaging)
Da jeder Shard in einem isolierten Thread läuft, haben sie keinen gemeinsamen Speicher. Wenn du globale Statistiken (wie die Gesamtanzahl aller Server oder Nutzer) abfragen möchtest, nutzt du broadcastEval:
// Ermittlung der plattformweiten Server- und Nutzerzahlen
export async function getPlatformMetrics(manager: ShardingManager) {
const results = await manager.broadcastEval((client) => {
return {
guildCount: client.guilds.cache.size,
memberCount: client.guilds.cache.reduce((acc, g) => acc + g.memberCount, 0),
ping: client.ws.ping,
};
});
const totalGuilds = results.reduce((acc, cur) => acc + cur.guildCount, 0);
const totalMembers = results.reduce((acc, cur) => acc + cur.memberCount, 0);
const avgPing = Math.round(results.reduce((acc, cur) => acc + cur.ping, 0) / results.length);
return { totalGuilds, totalMembers, avgPing };
}
4. Discord REST Rate-Limit Buckets meistern
Discord unterscheidet strikt zwischen Gateway-Limits (WebSockets) und REST-API-Limits (HTTP). Die REST-API nutzt ein Bucket-System, das über HTTP-Response-Header gesteuert wird:
X-RateLimit-Limit: Maximale Anzahl an Anfragen im aktuellen Zeitfenster.X-RateLimit-Remaining: Verbleibende Anfragen.X-RateLimit-Reset-After: Sekunden bis zum Reset des Buckets.X-RateLimit-Bucket: Eindeutiger Hash des betroffenen Endpunkts.
Best Practice für REST-Queues:
Wenn dein Bot Massen-Operationen ausführt (z. B. Rollen-Zuweisung an 500 Nutzer), nutze eine sequentielle Queue mit dynamischem Backoff:
export async function executeWithRateLimitRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
let attempt = 0;
while (attempt < maxRetries) {
try {
return await fn();
} catch (error: any) {
if (error?.status === 429) {
const retryAfter = (error.rawError?.retry_after ?? 1) * 1000;
console.warn(`[RateLimit 429] Warte ${retryAfter}ms vor erneutem Versuch...`);
await new Promise((resolve) => setTimeout(resolve, retryAfter + 100));
attempt++;
} else {
throw error;
}
}
}
throw new Error(`Max retries (${maxRetries}) erreicht.`);
}
5. Faustregeln für die Cluster-Dimensionierung
- Faustregel für Shard-Anzahl: Plane ca. 1.000 Server pro Shard ein (obwohl Discord bis zu 2.500 erlaubt). Ein Bot mit 4.000 Servern läuft mit 4–5 Shards deutlich stabiler als mit der Minimalanzahl von 2.
- CPU-Thread-Allokation: Teile Worker-Threads so auf, dass nicht mehr als 2 Shards auf demselben physischen CPU-Kern um Event-Loop-Zyklen konkurrieren.
- Automatisierte Überwachung: Auf KiroCloud wird die Gateway-Latenz (
ws.ping) im Sekundentakt gemessen. Bei Latenzsprüngen über 250 ms sollte ein automatisierter Reconnect eingeleitet werden.
Luca Lichtenwald
Spezialisiert auf hochverfügbare Discord-Bot-Infrastrukturen, V8-Memory-Tuning und kryptografische Zero-Trust-Architekturen.