webhook-proxy

Eine Middleware-Schicht zwischen unseren Diensten und den Discord-Webhooks. Sie reiht Ereignisse pro Ziel ein, respektiert 429-Limits und wiederholt 5xx-Fehler, sodass Benachrichtigungen bei plötzlichem Verkehr nicht verloren gehen. Ein einziger Punkt, durch den die gesamte ausgehende Kommunikation läuft.

webhook-proxy
TL;DR

Ein einziger Ausgangspunkt für die gesamte ausgehende Kommunikation zu Discord. Er stellt Ereignisse pro Ziel in eine Warteschlange, respektiert 429-Limits mit korrektem Backoff und wiederholt 5xx-Fehler, statt Benachrichtigungen bei einer Lastspitze zu verlieren. Der Nutzen ist einfach zu benennen: ein Ereignis verschwindet nicht nur, weil zufällig ein Rate-Limit getroffen hat.

Einführung

Unsere Dienste sagen einiges auf Discord: Monitoring-Alarme, Ereignisbenachrichtigungen, Betriebslogs. Jedes davon geht über einen Webhook hinaus, und Discord-Webhooks haben harte Rate-Limits. Solange der Verkehr ruhig ist, spürt es niemand. Das Problem beginnt genau dann, wenn die Benachrichtigung am nötigsten ist - bei einer Spitze, wenn mehrere Dienste gleichzeitig feuern.

webhook-proxy entstand, um das Zusammenkleistern der Limit-Behandlung in jedem Dienst einzeln zu beenden. Stattdessen geht die gesamte ausgehende Kommunikation durch einen Punkt, der die Zustellung übernimmt: Er stellt in eine Warteschlange, wartet wenn nötig, wiederholt wenn es sich lohnt und verliert das Ereignis unterwegs nicht. Für die Dienste dahinter ändert sich nichts - sie senden wie zuvor.

Das Problem, das man erst unter Last sieht

Bei ruhigem Verkehr funktioniert das naive Versenden eines Webhooks tadellos, und genau das macht es tückisch. Der Code feuert eine Anfrage ab, bekommt ein 2xx, macht weiter - und niemand hat einen Grund, etwas Fragiles zu vermuten. Fragil ist erst der Fehlerpfad, der bei ruhigem Verkehr fast nie ausgelöst wird.

Es braucht eine Spitze, um es aufzudecken. Mehrere Dienste senden gleichzeitig Benachrichtigungen, Sie treffen das Limit und bekommen ein 429, oder Discord gibt kurzzeitig ein 5xx zurück. Im naiven Ansatz verschwindet dieses Ereignis einfach: der Code macht weiter, die Ausnahme landet bestenfalls in einem Log, und auf Discord fehlt der Eintrag, der dort sein sollte. Am schlimmsten ist, dass Sie es genau bei Spitzenlast verlieren, also dann, wenn Benachrichtigungen am wichtigsten sind.

Deshalb ist das Proxy keine Performance-Optimierung. Es ist eine Zustellgarantie unter Bedingungen, unter denen naives Versenden still versagt.

1
Warteschlange pro Ziel

jeder Webhook hat seine eigene Warteschlange, sodass ein langsames Ziel die übrigen nicht blockiert.

2
Respekt vor 429

bei einem Limit wartet das Proxy genau so lange, wie der Retry-After-Header sagt, nicht nach Gefühl.

3
5xx-Wiederholungen

ein Serverfehler bedeutet exponentiellen Backoff und einen weiteren Versuch, kein verlorenes Ereignis.

4
Harter Stopp bei 4xx

ein fehlerhafter Payload wird beim dritten Versuch nicht gültig, also verschwenden wir keine Versuche darauf.

Eine Warteschlange, die den Rest nicht blockiert

Die erste Entscheidung ist eine separate Warteschlange für jedes Ziel. Wenn alle Ereignisse durch eine gemeinsame Warteschlange liefen, würde ein einzelner langsamer oder blockierter Webhook die Zustellung an jeden anderen aufhalten - ein verstopftes Ziel und Stille überall. Das Aufteilen der Warteschlangen bedeutet, dass ein Problem mit einem Webhook bei diesem einen Webhook bleibt.

Dieselbe Struktur gibt Widerstandsfähigkeit gegen Spitzen. Wenn eine Welle hereinkommt, werden Ereignisse nicht verworfen - sie landen in der Warteschlange ihres Ziels und warten auf ein Fenster, in dem das Limit nachlässt. Von außen sieht das aus wie eine kurzzeitige Verzögerung statt eines Verlusts, und das ist eine völlig andere Qualität: eine verzögerte Benachrichtigung erfüllt weiterhin ihren Zweck, eine verlorene keinen.

Das Herz: die Zustellschleife

Die gesamte Logik läuft auf eine Entscheidung pro Antwort hinaus: Erfolg beendet sie, 429 sagt warte, 5xx sagt wiederhole, andere 4xx bedeuten Stopp ohne Wiederholung. Diese Fälle auseinanderzuhalten ist der Unterschied zwischen einem Proxy, das zustellt, und einem, das entweder Ereignisse verliert oder endlos auf einem kaputten Payload mahlt.

dispatch.ts · ts
async function dispatch(target: WebhookTarget, payload: Payload) {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    const res = await post(target.url, payload);
    if (res.status < 300) return;
    if (res.status === 429) {
      await sleep(retryAfterMs(res));
      continue;
    }
    if (res.status >= 500) {
      await sleep(backoff(attempt));
      continue;
    }
    throw new PermanentError(res.status);
  }
}

Asymmetrie: 429 gegenüber 5xx

Der Schlüssel ist die Asymmetrie zwischen 429 und 5xx. Bei einem 429 sagt Discord selbst, wie lange zu warten ist - wir lesen es aus dem Header und warten genau das, nicht weniger und nicht mehr. Bei einem 5xx sagt niemand etwas, also fügen wir exponentiellen Backoff hinzu, um einen kurzzeitig ausgefallenen Server nicht mit einer Salve sofortiger Wiederholungen zu bombardieren.

!
Achtung

Der häufigste Fehler ist, alles blind zu wiederholen. Bei einem 429 ergibt eine Wiederholung Sinn, aber bei einem 400 oder 404 nicht: der Payload repariert sich nicht selbst, und Sie erreichen das Limit nur schneller und mahlen endlos auf etwas, das nie durchgeht. Wiederholen Sie, was vorübergehend ist, und nur das. Der Rest sollte sofort scheitern, laut und ein einziges Mal.

Der Effekt: eine Spitze, die die andere Seite nie sieht

Für die Dienste dahinter ist das Proxy unsichtbar - sie senden genau wie zuvor und wissen nichts von Limits oder Wiederholungen. All diese Komplexität sitzt an einem Ort, statt über jeden Dienst einzeln verschmiert zu sein. Das bedeutet auch, dass eine Korrektur in der Limit-Behandlung einmal eingeht, nicht in mehrere Codebasen gleichzeitig.

Was das Proxy mit einer Antwort macht (illustrativ)

2xx zugestellt
92
429 warten und wiederholen
5
5xx Backoff und wiederholen
2
4xx harter Stopp
1

Der Unterschied zeigt sich erst bei der nächsten Verkehrswelle, und das ist der ganze Sinn. Benachrichtigungen verschwinden nicht still in dem Moment, in dem sie am nötigsten sind - sie warten in der Warteschlange und treffen ein, sobald das Limit nachlässt. Ein Ausgangspunkt verwandelte stillen Ereignisverlust in höchstens eine kurzzeitige Verzögerung, und das ist der ganze Unterschied zwischen einem Monitoring, dem man vertrauen kann, und einem, das genau dann verstummt, wenn das Feuer ausbricht.

Weitere Projekte

Weitere Projekte aus derselben Kategorie - sehen Sie, wie wir ähnliche Herausforderungen angehen.

Haben Sie ein ähnliches Projekt?

Melden Sie sich - ein Angebot ist kostenlos und kommt innerhalb einer Stunde.