Eine Anfrage ohne doppelte Wirkung wiederholen
Idempotenz erlaubt es, eine POST-Anfrage zu wiederholen, ohne einen doppelten Seiteneffekt zu riskieren. Ein Client, der die Antwort durch ein Timeout verliert, kann mit demselben Idempotency-Key erneut senden und erhält die gespeicherte Antwort, anstatt den Handler erneut auszuführen. CaptainDNS folgt der Stripe-Konvention mit einem 24-Stunden-Fenster.
Warum Idempotenz
Netzwerke sind unzuverlässig. Ein Client kann:
- eine Anfrage senden, die serverseitig erfolgreich ist, deren Antwort jedoch verloren geht,
- mitten im automatischen Retry abstürzen,
- ein Timeout am Load Balancer sehen, obwohl die API die Anfrage bereits verarbeitet hat.
Ohne Idempotenz müssen Sie zwischen zwei schlechten Optionen wählen:
- nicht wiederholen und legitime Operationen verlieren,
- blind wiederholen und dieselbe Operation mehrfach ausführen.
Idempotenz bietet Ihnen einen dritten Weg: sicheres Wiederholen.
Verwendung
Fügen Sie einen Idempotency-Key-Header zu Ihrer POST-Anfrage hinzu:
POST /public/v1/deliverability/score HTTP/1.1
Host: api.captaindns.com
Authorization: Bearer cdns_live_a3f2XK7mN9QrVtZ4yP1sH6eL8cF2dB5aR3gW7kJxM
Content-Type: application/json
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
{"email":"contact@captaindns.com"}
Der Key muss ein opaker String mit 1 bis 255 druckbaren ASCII-Zeichen ohne Leerzeichen sein. UUID v4 oder ULID sind als Best Practice empfohlen (ausreichende Entropie, statistisch unmögliche Kollisionen), aber jeder clientseitig eindeutige Identifier funktioniert.
Serverseitiges Verhalten
Beim ersten Aufruf:
- CaptainDNS berechnet den SHA-256 des Request-Body.
- Nach Erfolg speichert es die Verbindung zwischen Idempotency-Key und Body-Hash.
- Status und Body der Antwort werden erfasst, bevor sie an den Client geht.
Beim zweiten Aufruf mit demselben Schlüssel:
- Die Middleware findet den vorhandenen Datensatz.
- Stimmt der neue Body-Hash mit dem gespeicherten überein, wird die gespeicherte Antwort zurückgegeben, mit
X-Idempotent-Replay: true. - Bei abweichendem Hash liefert die API
409 IDEMPOTENCY_CONFLICTmit einer eindeutigen Meldung.
Ein Replay ruft den zugrundeliegenden Handler nicht auf und schreibt keine zweite Usage-Zeile: die gespeicherte Antwort geht unverändert zurück. Er durchläuft aber alle vorgelagerten Stufen. Credit-Prüfung und Token-Bucket pro Schlüssel greifen, bevor die Idempotenz überhaupt zum Zug kommt: ein Replay verbraucht also ein Rate-Limit-Token und einen Credit, genau wie ein gewöhnlicher Aufruf. Idempotenz schützt Sie vor doppelter Verarbeitung, nicht vor doppelter Abrechnung. Planen Sie Ihre Retries entsprechend.
Gültigkeitsfenster
Idempotenz-Einträge leben 24 Stunden. Danach werden sie durch einen Purge-Job entfernt, und ein wiederverwendeter Schlüssel ruft den Handler ganz normal auf.
Das Fenster ist ein Kompromiss: zu kurz, und verzögerte Retries laufen ins Leere; zu lang, und Sie halten immer mehr alte Antworten vor. 24 Stunden decken die üblichen Retry-Muster ab, also den sofortigen Retry nach einem Netzwerkfehler, den Retry in den Minuten nach dem Wiederanlauf eines Jobs und den nächtlich eingeplanten Retry nach einem im Monitoring erkannten Fehlschlag.
Erfasste Methoden
Idempotenz gilt nur für mutierende oder teure Methoden. In der Praxis sind alle öffentlichen CaptainDNS-Endpunkte POST-Anfragen, also alle geeignet. GET-Anfragen laufen nicht durch den Mechanismus: sie sind auf HTTP-Ebene ohnehin idempotent.
Der Idempotency-Key-Header ist optional. Ohne ihn wird die Anfrage ganz normal verarbeitet, allerdings ohne Replay-Garantie.
Konflikte
Ein 409 IDEMPOTENCY_CONFLICT entsteht, wenn:
- Sie einen Idempotency-Key für eine Anfrage wiederverwenden, deren Body sich unterscheidet, und sei es um ein einziges Zeichen.
- Das passiert schnell, wenn Ihr Client die Anfrage zwischen zwei Versuchen neu aufbaut: Zeitstempel, eingebettete UUIDs, Reihenfolge der JSON-Schlüssel.
Vorbeugung:
- Berechnen Sie den Idempotency-Key vor dem Serialisieren des Body, nicht danach.
- Mischen Sie keine verschiedenen Aufrufe unter demselben Key.
- Wenn Ihr Framework JSON-Schlüssel umsortiert, fixieren Sie die Reihenfolge von Hand.
Bei einem berechtigten Konflikt, wenn Sie also tatsächlich eine andere Operation ausführen wollen, erzeugen Sie einfach einen neuen Schlüssel.
Verwendungsbeispiele
Retry nach Netzwerk-Timeout
idempotencyKey = uuid()
for attempt in 1..3:
try:
response = post(url, body, headers={"Idempotency-Key": idempotencyKey})
return response
except TimeoutError:
sleep(2 * attempt)
raise
Jeder Retry verwendet denselben Key. Ist einer der Aufrufe serverseitig bereits durchgelaufen, liefern die folgenden die gespeicherte Antwort.
Deduplizierung in einer Job-Queue
job = fetch_next_job()
idempotencyKey = hash(job.id)
response = post(url, job.payload, headers={"Idempotency-Key": idempotencyKey})
mark_job_done(job.id, response)
Verteilt die Queue einen Job nach einem Absturz erneut, greift der zweite Durchlauf derselben Job-ID auf denselben Key zurück und holt die gespeicherte Antwort, statt dem Kunden ein zweites Mal zu berechnen.
Batch mit serverseitiger Dedupe
for domain in domains:
idempotencyKey = "batch-" + run_id + "-" + domain
post(url, {"domain": domain}, headers={"Idempotency-Key": idempotencyKey})
Wird der Batch nach einem Fehlschlag neu gestartet, springt jede bereits verarbeitete Domain direkt in den Replay, und nur die offenen verbrauchen Credits.
Einschränkungen
- 24 h Speicherlimit: darüber hinaus ist kein Replay mehr möglich. Stark verzögerte Jobs müssen die Antworten clientseitig vorhalten.
- Scope pro API-Schlüssel: derselbe Idempotency-Key kollidiert nicht, wenn er mit zwei verschiedenen API-Schlüsseln verwendet wird. Das ist Absicht: jeder Schlüssel hat seinen eigenen Namensraum.
- Keine serverübergreifende Koordination: mehrere Backends teilen sich dieselbe API, müssen die Keys aber untereinander abstimmen.
- Strikte Body-Identität: ein zusätzliches Leerzeichen bricht den Hash und löst einen Konflikt aus. Serialisieren Sie deterministisch.
Nächste Schritte
Weiter mit dem Fehlerhandbuch, um auf 409 IDEMPOTENCY_CONFLICT richtig zu reagieren, oder der OpenAPI-Referenz.