Wie gehst du mit Retry-After um, wenn es ein Datum statt Sekunden ist oder ganz fehlt? In einem Policy Lab habe ich den Provider-Wert als Untergrenze verwendet, ein wenig Jitter danach addiert und Inline-Wartevorgänge begrenzt — alles, was das Budget überschreitet, wurde aufgeschoben, statt endlos zu loopen. Was macht deine Retry-Policy, wenn die Wartezeit dein Inline-Budget überschreiten würde?
Hey @operantsolo, während du auf eine Antwort wartest, könnten dir diese Ressourcen helfen:
Empfohlene Ressourcen
Automatisch auf deine Frage abgestimmt.
Dokumentation:
Forum:
@Niffzy, @Emmas, @A_A4 - ihr habt bei ähnlichen Problemen schon geholfen, könnt ihr euch das mal ansehen?
Automatisch vorgeschlagen vom Community-Bot von n8n. Es ist ein Pilotprojekt - bitte gebt hier Feedback.
(Beitrag vom Autor gelöscht)
Die meisten Grenzfälle treten beim Parsing auf, deshalb normalisiere ich alles auf „Millisekunden zum Warten
Hallo @operantsolo, ein Test, den ich deinem Lab hinzufügen würde, ist ein gültiger datumbasierter Retry-After mit einem ungültigen Date-Header. In @maxgritsenkos Snippet wird dadurch waitMs zu NaN, und der waitMs === null-Fallback erfasst das nie.
Ersetze die const now = ...-Zeile durch:
const serverNow = Date.parse(String(h.date ?? ''));
const now = Number.isFinite(serverNow) ? serverNow : Date.now();
Das fällt zur lokalen Uhr zurück, wenn das Server-Datum nicht geparst werden kann, sodass dieser Fall eine numerische Verzögerung für die Wait/Defer-Prüfung erzeugt.
@Anshul_Namdev Guter Fang — dieser NaN-Fall ist durch meine Checks gerutscht. Ich werde deinen Test und dein Number.isFinite-Fallback zur lokalen Uhr hinzufügen, und ich werde auch das finale waitMs auf die gleiche Weise schützen, damit ein schlechter Date-Header niemals einen NaN-Wait produzieren kann — stattdessen wird auf den Standard-Wait zurückgegriffen.
Ein weiterer Fall, der zum Lab hinzugefügt werden könnte, ist ein gesamt Inline-Budget statt einer Pro-Verzögerungs-Grenze. Bei einem 60-Sekunden-Budget, 58 Sekunden bereits verstrichener Zeit und einer 3-Sekunden-Wiederholungsverzögerung sollte die nächste Wartezeit aufgeschoben werden, obwohl 3 < 60.
Ich würde eine absolute Frist einmal festlegen, bevor die erste Anfrage erfolgt, und sie durch die Schleife tragen, ohne sie zurückzusetzen. Nach jeder Antwort berechne ich die verbleibende Zeit. Nach Validierung endlicher, nicht-negativer Verzögerungen vergleiche ich die gerundete Verzögerung, wenn ganze Sekunden im Wait-Knoten verwendet werden:
const waitSec = Math.ceil(waitMs / 1000);
const fitsNextWait = waitSec * 1000 <= remainingMs;
Zum Beispiel benötigt eine 1-ms-Verzögerung mit nur 999 ms verbleibender Zeit einen Aufschub, wenn sie als 1-Sekunden-Wartezeit geplant ist. Behalte die Mindestverzögerung des Providers in der aufgeschobenen Fälligkeitszeit; kürze sie nicht, um sie in das Inline-Budget zu passen. Überprüfe die Frist erneut nach der Wiederaufnahme und budgetiere die nächste HTTP-Anfrage separat — dieses Prädikat entscheidet nur, ob die Wartezeit passt.
Ich habe diese Routing-Logik lokal in JavaScript gegen 11 Fälle geprüft, einschließlich dieser Grenzen, NaN, Infinity und negativer Werte. Ungültige oder nicht darstellbare Verzögerungen gingen zur Überprüfung, statt direkt zurück in die Anfrage. Dies war kein n8n/Provider-Integrationtest.
@operantsolo Parsing wird oben behandelt, daher geht es hier nur um die aufgeschobene Arbeit.
- Das Warten gehört zur API, nicht zum Element. Behalte ein
blocked_untilpro API (oder pro Berechtigung, wenn Limits pro Schlüssel sind). Neue Läufe prüfen es zuerst und verschieben sofort, anstatt eine Anfrage zu verschwenden, um die gleiche 429 zu erhalten. - Reguliere die Wiederholung. Wenn es jede Zeile mit
next_attempt_at <= now()nimmt, wird alles im Moment ausgelöst, wenn das Fenster öffnet, und du bist wieder bei 429. Nimm einen kleinen Batch pro Tick, dimensioniert nach dem Rate Limit. In Postgres,FOR UPDATE SKIP LOCKEDbei diesem Select verhindert auch, dass zwei Replay-Läufe die gleichen Zeilen nehmen. - Zähle eine Verschiebung nicht als fehlgeschlagenen Versuch. 429 bedeutet noch nicht. Gib jedem Element eine Frist, und sende es zur Überprüfung, sobald diese verstreicht.
- Halte Timeouts aus dieser Tabelle. Eine 429 wurde abgelehnt, also ist ein Neuversand sicher. Ein Timeout ist unbekannt.
@operantsolo noch etwas dazu, als Folge auf meine frühere Antwort. Ich habe die Policy aus diesem Thread als n8n-Workflows gebaut und auf n8n 2.47.7 gegen eine Stand-in-API ausgeführt, die 429/503 mit jeder Form des Headers beantwortet:
Zu deiner Frage: Wenn das Warten nicht passt, geht die Anfrage in eine Data Table mit due_at = jetzt + die volle Wartezeit, und der Run endet. Ein geplanter Workflow übernimmt die fälligen Reihen und sendet sie. Das Warten wird nie gekürzt, um zu passen.
Das kam heraus:
- 17 Header-Formen (Sekunden, Daten, Server-Uhr eine Stunde falsch, @Anshul_Namdev’s schlechter
Date-Header, vergangenes Datum, Müll, fehlend): kein zu frühes Retry, kein NaN - @PrecioClaro’s Gesamtbudget:
Retry-After: 2mit 5-s-Budget gab Anfragen bei 0, 2 und 4 s, dann Aufschiebung - 20 aufgeschobene Elemente mit 30 Abflüssen, die feuerten, während sie fällig wurden: jedes wurde einmal gesendet
- eine API, die immer 429 sagt, bekam genau
max_attemptsAnfragen - n8n’s eigenes Retry On Fail ignoriert den Header. Mit
Retry-After: 10wurde nach 1 s erneut versucht
Kleine Sache zu @maxgritsenko’s Snippet: Date.parse("1.5") und Date.parse("-5") kommen als Daten im Jahr 2001 zurück, daher würden sie als „jetzt erneut versuchen
@operantsolo noch eine Sache dazu, als Folgeantwort auf meine frühere Antwort. Ich habe die Policy aus diesem Thread als n8n-Workflows gebaut und gegen eine Dummy-API getestet, die auf jede Form des Headers mit 429/503 antwortet:
Zu deiner Frage: Wenn die Wartezeit nicht passt, geht die Anfrage in eine Data Table mit due_at = jetzt + die volle Wartezeit, und der Run endet. Ein geplanter Workflow holt die fälligen Zeilen und sendet sie. Die Wartezeit wird nie gekürzt, um zu passen.
Das Ergebnis:
- 17 Header-Formen (Sekunden, Daten, Server-Uhr eine Stunde falsch, @Anshul_Namdev’s defekter
Date-Header, Datum in der Vergangenheit, Müll, fehlend): kein Retry kam zu früh an, kein NaN - @PrecioClaro’s Gesamtbudget:
Retry-After: 2mit 5-s-Budget gab Anfragen bei 0, 2 und 4 s, dann Aufschub - 20 aufgeschobene Elemente mit 30 Drains, die abgearbeitet wurden: jedes wurde genau einmal gesendet
- eine API, die immer 429 sagt, bekam genau
max_attemptsAnfragen - n8n’s eigenes Retry On Fail ignoriert den Header. Mit
Retry-After: 10wiederholte es nach 1 s
Kleine Sache zu @maxgritsenko’s Snippet: Date.parse("1.5") und Date.parse("-5") geben Daten aus dem Jahr 2001 zurück, also würden sie als „jetzt wiederholen