Discord-Rate-Limits sauber behandeln
Begrenzte API-Kapazität verlangt Planung statt hektischer Wiederholungen. Ein Rollenabgleich zeigt, wie Yurna Arbeit bündeln, warten, verwerfen und fortsetzen kann, während Mitglieder verständliche Rückmeldungen erhalten und wichtige Aktionen nicht hinter Routineaufgaben verschwinden.
1554 Wörter · 8 Min. Lesezeit
- yurna
- discord
- rate-limits
- warteschlangen
Schematische Illustration zur Artikelserie; keine privaten Betriebsdaten.
Nach einer größeren Änderung sollen mehrere Rollenmenüs aktualisiert werden. Gleichzeitig öffnen Mitglieder Tickets, rufen Ranglisten ab und ändern ihre Themenrollen. Jede einzelne Funktion erscheint klein. Zusammengenommen können sie jedoch viele externe Anfragen erzeugen. Wenn Yurna bei einer Begrenzung sofort alles erneut sendet, wächst die Last ausgerechnet dann, wenn weniger Kapazität verfügbar ist. Ein guter Umgang mit Rate-Limits beginnt deshalb bereits bei der Planung der Arbeit und nicht erst bei der Behandlung einer Fehlermeldung.
Der untersuchte Yurna-Code verwendet Discord-Bibliotheken und besitzt eigene Hintergrundaufgaben, etwa für Rollenmenüs. Bei Teilnehmerabfragen für Giveaways werden mehrere Nutzerabrufe parallel gestartet. Diese Beobachtungen zeigen unterschiedliche Quellen externer Arbeit. Sie erlauben keine Aussage über tatsächlich erreichte Grenzen im laufenden Betrieb. Für den Entwurf ist aber klar: Bibliotheksinterne Begrenzung und fachliche Arbeitsplanung sind zwei Ebenen. Selbst wenn eine Bibliothek Anfragen korrekt verzögert, kann die Anwendung noch eine unübersichtliche Menge unnötiger Aufgaben erzeugen.
Plattformgrenzen und eigene Cooldowns unterscheiden
Ein Command-Cooldown begrenzt, wie häufig ein Mitglied eine bestimmte Funktion verwenden darf. Ein Discord-Rate-Limit betrifft dagegen externe Anfragen und kann mehrere Funktionen gemeinsam betreffen. Ein fünfsekündiger Abstand für einen Befehl sagt deshalb wenig darüber aus, wie viele API-Aufrufe ein Hintergrundjob erzeugt. Beide Mechanismen können sinnvoll sein, lösen aber unterschiedliche Probleme. Die Benennung im Code und in Protokollen sollte diesen Unterschied erhalten.
Discord beschreibt in seiner Rate-Limit-Dokumentation unter anderem routenbezogene und globale Begrenzungen sowie Antwortinformationen für die Wartezeit. Statt feste Grenzwerte als dauerhafte Wahrheit in den eigenen Code einzubauen, sollte die zuständige Transportschicht diese Informationen auswerten. Für die Fachlogik ist anschließend wichtig, ob ihre Arbeit noch wartet, bereits ausgeführt wurde oder endgültig gescheitert ist. Ein technisches Warten darf nicht automatisch als fachliche Ablehnung erscheinen.
Die Anwendung muss trotzdem entscheiden, wie lange eine Arbeit sinnvoll bleibt. Eine aktuelle Ranglistenansicht kann nach langer Wartezeit überholt sein. Eine gespeicherte Ticketabschlussaktion bleibt dagegen relevant und muss später fortgesetzt werden können. Rate-Limits ändern nicht die Bedeutung der Aufgabe. Sie machen lediglich sichtbar, dass diese Bedeutung ausdrücklich modelliert werden sollte. Ohne Gültigkeit, Priorität und Wiederholungsregel wird aus einer Warteschlange schnell ein Speicher längst überholter Absichten.
Unnötige Arbeit vor dem Senden vermeiden
Die günstigste Anfrage ist diejenige, die für das gewünschte Ergebnis nicht benötigt wird. Wenn ein Rollenmenü dreimal kurz hintereinander bearbeitet wird, muss nicht jede Zwischenfassung veröffentlicht werden. Ein Entwurf kann offene Aktualisierungen derselben Nachricht zusammenfassen und nur den letzten gewünschten Stand ausführen. Das gilt jedoch nur für ersetzbare Zustände. Drei eigenständige Ticketnachrichten dürfen nicht auf dieselbe Weise zu einer zusammenfallen, weil jede einen eigenen Inhalt trägt.
Deshalb erhält eine Hintergrundaufgabe einen fachlichen Typ. Eine Synchronisation bedeutet „bringe dieses Objekt auf den aktuellen Stand“. Eine Ereignisnachricht bedeutet „veröffentliche diese konkrete Information“. Bei der ersten Aufgabe kann die neueste Konfiguration ältere offene Varianten ersetzen. Bei der zweiten muss die einzelne Absicht erhalten bleiben. Diese Unterscheidung reduziert Last, ohne Informationen zu verlieren. Sie ist oft wirksamer als später eine immer größere Parallelität zuzulassen.
Auch lesende Zugriffe lassen sich bündeln. Wenn mehrere Funktionen dieselbe Serverrollenliste innerhalb eines kurzen geeigneten Zeitraums benötigen, kann eine begrenzte gemeinsame Sicht genügen. Vor einer sicherheitsrelevanten Mutation bleibt die aktuelle Prüfung notwendig. Eine pauschale Zwischenspeicherung aller Daten wäre daher keine saubere Lösung. Ziel ist, wiederholte Arbeit dort zu vermeiden, wo ihre Ergebnisse tatsächlich austauschbar sind. Die fachliche Verwendung bestimmt den zulässigen Umfang der Einsparung.
Ein Rollenabgleich mit begrenzter Parallelität
Als Beispiel wurden Bezeichnungen in fünf Themenmenüs geändert. Jedes Menü gehört zu einer eigenen Nachricht. Der Bot soll sie aktualisieren, während gewöhnliche Mitgliedsaktionen weiter bedient werden. Der Entwurf legt fünf Synchronisationsaufgaben an, jeweils mit Konfigurationsbezug und Revision. Ein Worker nimmt nur eine begrenzte Zahl gleichzeitig auf. Die gewählte Zahl ist ein Startwert für Tests und keine Behauptung über eine allgemein optimale Discord-Kapazität.
// Illustrativer Fachvertrag einer wartenden Synchronisation.
type SyncJob = {
targetRef: string;
revision: number;
state: "queued" | "running" | "waiting" | "done" | "failed";
nextAttemptAt?: string;
attempts: number;
};Vor der Ausführung lädt der Worker die aktuelle Revision. Ist eine ältere offene Aufgabe überholt, kann sie durch den neuesten Stand ersetzt werden. Trifft die Transportschicht auf eine vorübergehende Begrenzung, bleibt die Aufgabe als wartend erkennbar. Sie wird nicht sofort als endgültiger Fehler markiert und auch nicht als neue unabhängige Aufgabe dupliziert. Nach erfolgreicher Veröffentlichung wird genau die ausgeführte Revision bestätigt. So kann das Dashboard zwischen gespeicherter Konfiguration und sichtbarer Nachricht unterscheiden.
Wichtig ist außerdem die Reihenfolge pro Ziel. Zwei Updates derselben Nachricht sollten nicht so parallel laufen, dass die ältere Fassung zuletzt ankommt. Begrenzte globale Parallelität allein verhindert diesen Fehler nicht. Der Entwurf serialisiert deshalb Arbeit für dasselbe Ziel oder prüft die Revision unmittelbar vor dem wirksamen Schritt. Für unterschiedliche Nachrichten kann weiterhin Parallelität möglich sein. Diese feinere Ordnung schützt Aktualität, ohne sämtliche Aufgaben unnötig nacheinander abzuarbeiten.
Warteinformationen korrekt behandeln
Eine vom Dienst vorgegebene Wartezeit wird respektiert. Sie ist kein Vorschlag, den die Anwendung durch häufiges Probieren verkürzen sollte. Der allgemeine HTTP-Header Retry-After kann laut MDN eine Verzögerung oder einen Zeitpunkt ausdrücken. Ein konkreter Client muss das für den jeweiligen Dienst dokumentierte Format korrekt interpretieren. Sekunden und Millisekunden zu verwechseln kann sowohl hektische Wiederholungen als auch unnötig lange Pausen erzeugen.
Zusätzliche zufällige Streuung kann bei eigenen Wiederholungsplänen helfen, gleichzeitige Neustarts vieler Aufgaben zu entzerren. Sie darf aber eine vom Dienst geforderte Mindestwartezeit nicht unterschreiten. Ebenso sollte ein lokales Zeitlimit für die gesamte Aufgabe berücksichtigt werden. Eine unwichtige Vorschau muss nicht noch nach langer Verzögerung erstellt werden, wenn niemand sie mehr benötigt. Ein dauerhafter fachlicher Vorgang erhält dagegen einen abrufbaren Status, damit die ursprüngliche Interaktion nicht die einzige Verbindung zur Arbeit bleibt.
Eine wartende Aufgabe sollte keine Datenbanktransaktion offen halten. Ihr nächster Versuch wird gespeichert, danach wird die lokale Änderung abgeschlossen. Der Worker kann währenddessen andere geeignete Arbeit erledigen. Eine lange blockierende Pause innerhalb einer kritischen Datenbankphase würde zwei unabhängige Engpässe verbinden. Gerade in einer gemeinsam verwendeten SQLite-Datenbank wäre das unnötig. Die Wartezeit gehört zur Arbeitsplanung, nicht in einen lange gehaltenen Schreibabschnitt.
Fehlerarten nicht in denselben Wiederholungstopf werfen
Eine vorübergehende Begrenzung, eine fehlende Berechtigung und ein nicht mehr existierender Kanal verlangen unterschiedliche Reaktionen. Bei einer fehlenden Berechtigung ist häufig eine Konfigurationsänderung nötig. Ein gelöschtes Ziel wird durch wiederholtes Senden nicht wiederhergestellt. Ein Netzwerkabbruch lässt möglicherweise offen, ob eine verändernde Anfrage bereits wirksam war. Deshalb braucht die Wiederholungsentscheidung mehr als den allgemeinen Satz „der Aufruf hat einen Fehler geworfen“.
Für die Rollenmenüs wird eine nicht mehr vorhandene Nachricht als eigener Zustand behandelt. Ob eine neue Nachricht erstellt werden darf, hängt vom Inhaltsmodus und der Konfiguration ab. Der Worker sollte nicht bei jedem Fehler automatisch Ersatz veröffentlichen. Andernfalls könnte ein vorübergehend nicht abrufbares Ziel zu doppelten Menüs führen. Eine Wiederherstellung ist eine bewusste fachliche Handlung mit eigenen Bedingungen, nicht die Standardantwort auf jede fehlgeschlagene Anfrage.
Bei einem unklaren Ergebnis einer Mutation folgt zuerst ein Abgleich. Wurde die gewünschte Revision bereits veröffentlicht, kann die Aufgabe abgeschlossen werden. Ist das nicht feststellbar, wird die Unsicherheit sichtbar gehalten oder kontrolliert weiter untersucht. Eine blinde erneute Erstellung kann doppelte Nachrichten erzeugen. Die richtige Strategie hängt vom konkreten API-Vorgang ab. Aktualisieren, Erstellen und Löschen haben unterschiedliche Wiederholungsfolgen und verdienen entsprechend getrennte Behandlung.
Prioritäten aus Sicht der Mitglieder setzen
Ein großer Hintergrundabgleich sollte eine aktuelle Mitgliedsinteraktion nicht unnötig lange verdrängen. Das bedeutet nicht, dass jede Nutzeranfrage sämtliche Warteschlangen umgehen darf. Vielmehr werden Arbeitsklassen mit nachvollziehbaren Prioritäten gebildet. Eine unmittelbar erwartete Antwort erhält mehr Aufmerksamkeit als die regelmäßige Aktualisierung einer dekorativen Übersicht. Gleichzeitig muss verhindert werden, dass Hintergrundarbeit bei dauerhafter Aktivität niemals weiterkommt. Eine faire begrenzte Verteilung ist besser als absolute Vorrangregeln ohne Ausgleich.
Die Oberfläche braucht keine detaillierte Erklärung der API-Buckets. Sie sollte aber zwischen „wird bearbeitet“ und „konnte nicht ausgeführt werden“ unterscheiden. Bei einem gespeicherten Rollenmenü kann ein Hinweis auf ausstehende Veröffentlichung ausreichen. Bei einer persönlichen Aktion ist eine frühe Bestätigung wichtig, damit das Mitglied nicht durch weitere Klicks zusätzliche Arbeit erzeugt. Verständliche Rückmeldungen sind deshalb selbst ein Bestandteil der Lastbegrenzung. Sie reduzieren Wiederholungen aus Unsicherheit.
Für administrative Sammelaktionen ist ein Fortschrittsbild hilfreich, das erledigte, wartende und gescheiterte Ziele trennt. Eine einzige Prozentzahl kann Fehler verdecken: Neun von zehn Aufgaben könnten abgeschlossen sein, während die letzte dauerhaft ein ungültiges Ziel verwendet. Die Verwaltung braucht dann eine konkrete Liste der erforderlichen Korrekturen. Der Fortschritt wird nach fachlichen Aufgaben gezählt, nicht nach der Anzahl gesendeter HTTP-Anfragen, die durch Wiederholungen beliebig wachsen kann.
Kontrollierte Überlastung im Test erzeugen
Ein Testadapter kann nach bestimmten Aufrufen eine Begrenzung mit definierter Wartezeit liefern. Mit einer kontrollierten Uhr wird geprüft, dass vorher keine erneute Anfrage erfolgt und danach dieselbe Aufgabe fortgesetzt wird. Anschließend werden zwei Aktualisierungen derselben Nachricht eingeplant. Die zuletzt gewünschte Revision muss am Ende sichtbar sein. Dieser Test prüft sowohl Warteverhalten als auch fachliche Reihenfolge, ohne einen echten Discord-Server absichtlich mit Anfragen zu belasten.
Weitere Versuche kombinieren wartende Hintergrundarbeit mit einer neuen Mitgliedsaktion, einen Neustart während der Wartezeit und ein dauerhaft gelöschtes Ziel. Die Aufgabe darf weder verschwinden noch unbegrenzt wiederholt werden. Erfasst werden Wartedauer, Anzahl der Versuche, offene Aufgaben und konkrete Fehlergründe. Daraus lässt sich ableiten, ob zu viel Arbeit erzeugt wird oder die verfügbare Kapazität nur vorübergehend schwankt. Eine bloße Gesamtzahl aller Fehler wäre dafür zu grob.
Saubere Rate-Limit-Behandlung ist damit vor allem geordnete Arbeit. Yurna muss wissen, welche Absichten noch gültig sind, welche zusammengefasst werden können und welches Ergebnis bereits feststeht. Der Discord-Client übernimmt die protokollgerechte Kommunikation; die Anwendung sorgt für verständliche Prioritäten und wiederholbare Vorgänge. Wenn beide Ebenen zusammenpassen, wird eine zeitweilige Begrenzung zu einer erklärbaren Verzögerung, statt durch hektische Wiederholungen eine größere Störung auszulösen.
Auch der Abbruch einer Sammelaktion braucht eine klare Grenze. Noch nicht begonnene Veröffentlichungen können aus der Planung entfernt werden, bereits bestätigte Änderungen bleiben jedoch bestehen. Die Oberfläche sollte deshalb nach einem Abbruch den erreichten Teilstand zeigen. „Abgebrochen“ bedeutet nicht automatisch „alles rückgängig gemacht“. Soll eine frühere Konfiguration wiederhergestellt werden, entsteht daraus ein eigener begrenzter Abgleich. Diese Unterscheidung verhindert, dass eine Verwaltungsperson aus einem gestoppten Worker fälschlich auf unveränderte Discord-Nachrichten schließt.