Exponential Backoff ist eine Retry-Strategie, bei der die Wartezeit vor jedem neuen Versuch um einen konstanten Faktor wächst, meist durch Verdopplung, bis zu einer maximalen Verzögerung. Zusammen mit zufälligem Jitter und einer Obergrenze für Versuche verteilt sie Retries zeitlich, sodass Clients einen ohnehin gestörten oder drosselnden Service nicht überlasten.
Was ist Exponential Backoff?
Exponential Backoff ist das Standardverfahren, um fehlgeschlagene Remote-Aufrufe zu wiederholen, ohne den Fehler zu verschlimmern. Statt sofort oder in festen Abständen erneut zu versuchen, wartet ein Client nach dem ersten Fehler kurz, nach dem zweiten doppelt so lange, nach dem dritten viermal so lange und so weiter, bis der Aufruf gelingt, eine maximale Verzögerung erreicht ist oder er nach einer festgelegten Zahl von Versuchen aufgibt.
Der Gedanke dahinter: Ist eine Abhängigkeit überlastet, erzeugt jeder sofortige Retry zusätzliche Last, und das genau im falschen Moment. Wachsende Wartezeiten senken den Retry-Druck schnell und geben der Abhängigkeit Zeit zur Erholung. Kurze, transiente Fehler wie eine abgebrochene Verbindung oder eine kurze Leader Election werden trotzdem abgefangen.
Die Formel für Exponential Backoff
Die Grundformel für Versuch n (beginnend bei 0) lautet:
delay = min(cap, base * 2^n)
base ist die Anfangsverzögerung, 2 der Multiplikator (manche Bibliotheken nutzen 1,5 oder machen ihn konfigurierbar) und cap die maximale Verzögerung. Mit einer beispielhaften Basis von 100 ms und einem Cap von 10 s ergeben sich 100 ms, 200 ms, 400 ms, 800 ms, 1,6 s, 3,2 s, 6,4 s und danach 10 s für jeden weiteren Versuch. Eine maximale Anzahl an Versuchen oder eine Gesamt-Deadline beendet die Folge.
Warum Exponential Backoff Jitter braucht
Reines Exponential Backoff hat eine Schwäche: Fallen tausend Clients im selben Moment aus, versuchen sie es alle nach genau 100 ms erneut, dann nach genau 200 ms, und treffen den Service in synchronisierten Wellen. Zufällige Streuung, Jitter genannt, löst diese Wellen auf. Gängige Varianten, bekannt geworden durch einen Artikel im AWS Architecture Blog zu Backoff und Jitter:
| Variante | Formel | Verhalten |
|---|---|---|
| Ohne Jitter | min(cap, base * 2^n) | Synchronisierte Retry-Wellen |
| Full Jitter | random(0, min(cap, base * 2^n)) | Verteilt Retries breit, geringe Gesamtlast |
| Equal Jitter | d/2 + random(0, d/2) mit d = min(cap, base * 2^n) | Garantiert mindestens die halbe Wartezeit |
| Decorrelated Jitter | min(cap, random(base, previous * 3)) | Jede Wartezeit hängt von der vorherigen ab |
Full Jitter ist für die meisten Clients ein guter Standard. Entscheidend ist, überhaupt Jitter zu verwenden.
Beispiel: Retries in Go
base, maxDelay := 100*time.Millisecond, 10*time.Second
for attempt := 0; attempt < 6; attempt++ {
err := callPaymentAPI(ctx)
if err == nil || !isRetryable(err) {
return err
}
d := base << attempt // base * 2^attempt
if d > maxDelay { d = maxDelay }
time.Sleep(time.Duration(rand.Int63n(int64(d)))) // Full Jitter
}
Wiederholt werden nur wiederholbare Fehler, etwa Timeouts, HTTP 429, 502, 503 und 504 oder gRPC UNAVAILABLE. Ein 400 Bad Request scheitert jedes Mal auf dieselbe Weise. Sendet der Server bei 429 oder 503 einen Retry-After-Header, hat dieser Vorrang vor der berechneten Wartezeit. So arbeitet Backoff mit serverseitigem Rate Limiting zusammen.
Best Practices
- Nur idempotente Operationen wiederholen oder Idempotency Keys verwenden, damit ein Retry nach einem Timeout keinen Kunden doppelt belastet.
- Alles begrenzen: ein Cap für die Wartezeit, eine maximale Zahl an Versuchen und eine Gesamt-Deadline, die zum Timeout des Aufrufers passt.
- Nur auf einer Ebene wiederholen. Machen drei Schichten jeweils drei Versuche, sieht der unterste Service bis zu 27 Aufrufe pro Nutzer-Request. Retry Budgets begrenzen Retries auf einen Anteil des normalen Traffics.
- Mit einem Circuit Breaker kombinieren, der Aufrufe komplett stoppt, wenn eine Abhängigkeit offensichtlich ausgefallen ist.
- Eingebaute Policies nutzen. AWS SDKs, gRPC-Retry-Policies (
initialBackoff,maxBackoff,backoffMultiplier) und Resilience-Bibliotheken setzen Backoff mit Jitter korrekt um. - An eine Queue übergeben, wenn die Arbeit warten kann: Nach dem letzten Versuch landet die Nachricht in einer Dead Letter Queue, statt endlos wiederholt zu werden.
Retries sichtbar machen
Retries lösen Probleme, verdecken sie aber auch. Ein Request, der im vierten Versuch gelingt, sieht im Fehlerzähler gesund aus, kostet aber Sekunden an Latenz. Erfassen Sie die Versuchsnummer als Attribut, zählen Sie Retries pro Abhängigkeit und machen Sie jeden Versuch als eigenen Client-Span sichtbar. Ein plötzlicher Anstieg der Retry-Rate ist oft das früheste Anzeichen dafür, dass eine nachgelagerte Abhängigkeit schwächelt. Vergleichen Sie außerdem die Zahl der Versuche mit der Zahl der ursprünglichen Requests: Steigt das Verhältnis deutlich, verstärken Ihre Clients gerade die Last auf einem Service, der bereits Probleme hat.
Wie Obseria hilft
Mit OpenTelemetry-Instrumentierung erscheint jeder Retry-Versuch als eigener Span im Distributed Tracing. Obseria zeigt so, wohin die Zeit geflossen ist und welche Abhängigkeit die Retries ausgelöst hat. Retry-Zähler und Latenz lassen sich gemeinsam in OQL oder PromQL abfragen und für Alarme nutzen, bevor Nutzer etwas merken.