Wiederholungsstrategien und Idempotenz
1) Warum es notwendig ist
In Netzwerken sind Störungen die Norm: Timeouts, transiente Fehler, Netzwerkflapping, Überlastung. Retrays erhöhen die Zuverlässigkeit nur, wenn:1. Wiederholung ist sicher (idempotent),
2. die Abstände zwischen den Wiederholungen eingehalten werden,
3. Grenzen/Quoten und die „Gesundheit“ von Süchten werden respektiert.
Das Ziel ist effectively-once Verhalten auf der Ebene des Geschäftsbetriebs ohne falsche Takes und Rennen.
2) Taxonomie der Liefersemantik
At-most-once: keine Wiederholungen, Verlustrisiko (logging, fire-and-forget).
At-least-once: Duplikate sind möglich → die Idempotenz des Verbrauchers ist erforderlich (die meisten Warteschlangen, Webhooks).
Effectively-once: Duplikate sind möglich, werden aber korrekt dedupliziert (Schlüssel, Transaktionen, Outbox).
3) Wann retrazieren und wann nicht
Retracement macht Sinn: '408', '429' (unter Beachtung von 'Retry-After'), '425' (Too Early), '499' (Client geschlossen am Perimeter), '5xx', '504', Netzwerk-Timeouts/Pausen, '502' am Gateway, 'connection reset'.
Retraim nicht ohne Änderung der Anfrage: „400/ 401/403/404/422“.
Umstrittene Fälle: „409 Conflict“ (normalerweise nicht retraim; zuerst lesen wir den Status der Operation/bestätigen die Absicht).
4) Timeouts, Backoff und Jitter
4. 1 Regeln
Erst der Timeout, dann die Retrays: Jede Anfrage muss eine „Deadline“ haben.
Exponentieller Backoff: 'delay _ n = base 2 ^ n', limitieren 'max _ delay'.
Jitter ist ein Muss: Fügen Sie den Zufall hinzu, um „stumpfe Synchronwellen“ zu entkoppeln.
4. 2 Jitter-Vorlagen
Full jitter: 'sleep = rand (0, base2 ^ n)' ist die beste Gesamtauswahl.
Decorrelated jitter: 'sleep = min (max_delay, rand (base, sleep_prev3))' - für lange Dialoge.
Equal jitter: 'sleep = base2 ^ n/2 + rand (0, base2 ^ n/2)' ist eine weiche Variante.
4. 3 Retry-budget
Begrenzen Sie den Anteil der Retrays:- `retry_budget_per_min = max(α success_rps, floor β)`; Normalerweise' α = 0. 1–0. 2`.
- Wenn das Budget erschöpft ist, wechseln wir zu fail-fast/circuit breaker „open“.
5) Interaktion mit Rate Limiting und Circuit Breaker
Respektiere' Retry-After', 'RateLimit-Reset' und zähle es im Back-Off.
Bei hohen '5xx '/Timeouts - Reduzieren Sie die Retrae-Frequenz und die allgemeine Parallelität.
- Half-open: erlaubt eine begrenzte Probe.
- Offen: lehnt sofort ab (spart Ressourcen).
- Geschlossen: normale Arbeit.
- Bei Write-Operationen ist es vorzuziehen, den 409/503 mit einem klaren Hinweis zurückzugeben, als aggressive Retrays zu drehen.
6) Idempotenz der Schreiboperationen
6. 1 Allgemeine Idee
Gleiche Absichten → ein Ergebnis. Grundlage ist der Idempotenzschlüssel und die Speicherung der Ausführungsaufzeichnungen.
6. 2 HTTP-Vertrag
Der Kunde sendet den Titel:
Idempotency-Key: 7a6b7f9e-2a46-4d0b-9c3a-2b30e1c3c9e3
Idempotency-Key-Expiry: 24h # optional
Server:
- Bei der ersten erfolgreichen Ausführung speichert (der Schlüssel → das Ergebnis, den Status, den Hash des Körpers);
- gibt bei Wiederholung die vorherige Antwort und den Titel 'Idempotency-Replay: true' zurück;
- bei einem Körperkonflikt (gleicher Schlüssel, aber anderer Payload) ist „409 Conflict“.
6. 3 Lagerung und TTL
Tabelle/Schlüsselwert: 'idempotency _ key', 'request _ hash', 'result',' status', 'expiry _ at'.
TTL = Fenster für mögliche Wiederholungen und verspätete Lieferungen (in der Regel 24-72 Stunden für Zahlungen).
Indizes nach 'idempotency _ key'; für hohe Belastung - Hash-Sharding.
6. 4 Beispielschema (SQL)
sql
CREATE TABLE idempo_store (
key UUID PRIMARY KEY,
req_hash BYTEA NOT NULL,
status INT NOT NULL,
response JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expiry_at TIMESTAMPTZ NOT NULL
);
6. 5 Pseudocode des Handlers
pseudo handle_write(req):
k = req. headers["Idempotency-Key"]
h = hash(req. body)
rec = idempo_store. get(k)
if rec and rec. req_hash == h:
return rec. status, rec. response, {"Idempotency-Replay": "true"}
if rec and rec. req_hash!= h:
return 409, problem("IDEMPOTENT_CONFLICT")
begin tx result = apply_business_mutation (req) # change status upsert once (idempo_store, key = k, req_hash=h, status = 201, response = result, expiry = now () + 2d)
commit
return 201, result
7) „effectively-once“ Muster
Transactional Outbox: Aufzeichnen eines Geschäftsereignisses und Senden einer Nachricht aus derselben DB-Transaktion über einen Hintergrund-Relayer; Der Verbraucher ist idempotent.
Inbox/Processed-table beim Verbraucher: Wir speichern 'event _ id', um die Takes zu ignorieren.
Exactly-once bei Kafka ≠ exactly-once im Business: Auch bei der EOS des Produzenten/Konsumenten muss die angewandte Logik noch idempotent sein.
Kompensationstransaktionen (Saga): Wenn sich die Schritte zurückziehen und Nebenwirkungen verursachen, kehren wir das System zur Invariante zurück.
8) Sonderfälle: Zahlungen und Finanztransaktionen
Starke Identität: Der Schlüssel ist an die Logik der Operation gebunden (z.B. 'external _ payment _ id').
Deduplizierung auf der PSP: Speichern Sie' merchant _ reference' → wenn die PSP wiederholt wird, wird das vorherige Ergebnis zurückgegeben.
Retrays „vom Kunden“: nur bei 'Idempotency-Key' erlauben, sonst Risiko einer doppelten Abbuchung.
Wettbewerb: Sperren „pro Konto/Tool/Vertrag“ für die Dauer der Ausführung; bei Wiederholung 409/423 zurückgeben.
Beobachtbarkeit: Metriken 'idempo _ replay _ total', 'idempo _ conflict _ total'.
9) Webhooks und externe Herausforderungen
HMAC-Signaturen und Zeitfenster; Erst die Prüfung, dann die Bearbeitung.
Absender-Retrays: exponentieller Backoff + Jitter, 'max _ attempts' und DLQ.
Benutzer - idempotent: 'event _ id' → Tabelle/in-memory cache; Eine „saubere“ Ordnung ist nicht garantiert.
Codes: 2xx = erfolgreich, 4xx = nicht wiederholen, 5xx/timeout = wiederholen.
10) Warteschlangen und Hintergrundaufgaben
At-least-once standardmäßig → Duplikate sind unvermeidlich.
Speichern Sie' task _ id '/' event _ id 'und den Ausführungsstatus; wenn doppelt - kurzer Weg „replay“.
DLQ und poison-messages: Versuch-Zähler, Quarantäne, manuelle Analyse.
Wettbewerbslimits (Semaphoren) und idempotente Worker.
11) Versionierung und „natürliche“ Schlüssel
Natürliche Schlüssel (Kontonummer + Datum + Dokumentennummer) erhöhen die Wiederholungsresistenz.
Wenn Sie das Schema/die Version ändern, fügen Sie den Versionsschlüssel in den 'Idempotency-Key' oder in den Abfrage-Hash ein.
12) HTTP-Header und Client-Hinweise
„Idempotency-Key“, „Idempotency-Replay“, „Retry-After“, „Prefer: wait = <sec>“ (bei langen Operationen), „If-Match “/„ ETag“ (optimistische Blockaden).
409 bei einem Schlüsselkonflikt, 425/429/503 mit dem gültigen 'Retry-After'.
Bei „langen“ Vorgängen den asynchronen Status akzeptieren ('202 Accepted' + 'Location' auf die Statusressource).
13) Test- und Chaos-Szenarien
Negative Tests: Doppelsendung, Wiederholung mit einem anderen Körper, ein Rassynhron der Uhr.
Ordnungswidrigkeit: 't2' kommt vor 't1'.
Timeout-Injektion/„ RST “/„ EOF “, halbherzige Abfragen (slow-POST).
Gefallener idempotency Speicher → fail-closed Verhalten (bessere Ablehnung als doppeltes Abschreiben).
14) Metriken und Alerts
`retries_total{reason}`, `retry_budget_used{route}`, `backoff_seconds_bucket`.
`idempo_replay_total`, `idempo_conflict_total`, `duplicate_detected_total`.
Anteil 409/425/429/5xx nach Strecken; p95/p99 „Zeit bis zum Erfolg“ mit Retrays.
Alertas: Burn-Rate-Budget für Retrays, Anstieg der Idempotenzkonflikte, DLQ-Wachstum.
15) Antipatterns
Retrace alle Fehler in einer Reihe.
Das Fehlen von Jitter → synchrone Wellen von Retrays.
Langlebige Schlüssel ohne TTL und Reinigung.
Speichern des Ergebnisses nach einem Nebeneffekt-Commit (Outbox-Verletzung).
Logs ohne' trace _ id '/' idempotency _ key '→ keine Forensik möglich.
Aggressive parallele Retrays auf Write-Operationen.
16) Checkliste Prod-Ready
- Einheitliche Politik: Was retraim, was nicht; Codes und Hinweise an den Kunden.
- Exponentieller Backoff + Volljitter; wird 'retry _ budget' gesetzt.
- Vertrag 'Idempotency-Key' + Speicherung der Ergebnisse mit TTL.
- Outbox/Inbox für Ereignisse; DLQ; Grenzen der Wettbewerbsfähigkeit.
- Integration mit Circuit Breaker, respect 'Retry-After'.
- Metriken/Warnungen nach Retrays/Duplikaten/Konflikten.
- Eine Reihe von Chaos-Tests und Emulation von Netzwerkfehlern.
- Kundendokumentation: Beispiele für Backoffs und Status.
17) TL; DR
Retrays sind nur zusammen mit Idempotenz nützlich. Geben Sie' Idempotency-Key 'und Ergebnisspeicher ein, wenden Sie exponentiellen Backoff mit Jitter und Retry-Budget an, respektieren Sie' Retry-After', integrieren Sie sich in den Circuit Breaker. Für Ereignisse - outbox/inbox; für Zahlungen - strenge Deduplizierung und Blockierung. Messen Sie Retrays und Konflikte, testen Sie Duplikate und Timeouts.