Idempotenz ist das Einzige, das zwischen euch und einer doppelten Rückerstattung steht
Zahlungssysteme werden wiederholt aufgerufen. Netzwerke brechen mittendrin ab. Telefone wechseln. Der Unterschied zwischen einem sauberen Buchungskonto und einem Kundenbetreuungs-Alptraum ist, ob eure Endpunkte ehrlich idempotent sind.
Das erste Mal, dass wir eine doppelte Rückerstattung in der Produktion sahen, war vor fast zehn Jahren. Ein mobiler Client schickte eine Rückerstattungsanfrage. Der Server verarbeitete sie. Die 200-Antwort kam nie am Telefon an — der Nutzer war in einen Tunnel gegangen.
Das Telefon versuchte es erneut, wie Telefone es tun. Der Server verarbeitete sie wieder. Der Kunde bekam zwei Rückerstattungen. Der Händler bemerkte es drei Monate später. Das Gespräch war unangenehm.
Der Fix dauerte zehn Minuten. Die Prävention erfordert Nachdenken bei jedem einzelnen Entwurf eines Schreib-Endpunkts. Die meisten Teams bauen eine echte Idempotenzschicht, nachdem sie das erste Mal erwischt wurden. Manche werden zweimal erwischt.
Was idempotent wirklich bedeutet
Ein Endpunkt ist idempotent, wenn ihn zweimal mit derselben Eingabe aufzurufen denselben Effekt erzeugt wie einmal aufzurufen. Nicht dieselbe Antwort — denselben Effekt auf die Welt. Dieser Unterschied ist wichtig.
Ein GET /balance-Endpunkt ist zufällig idempotent, weil Lesen nichts ändert. Ein POST /refund-Endpunkt ist nur idempotent, wenn ihr es so macht. Es gibt kein Sprachfeature, das das für euch erledigt.
Das Standardmuster ist der Idempotenz-Schlüssel: Der Client generiert einen eindeutigen Bezeichner pro logischer Operation und sendet ihn als Header. Der Server zeichnet den Schlüssel auf, wenn er ihn zum ersten Mal sieht, speichert das Ergebnis — und bei jedem erneuten Versuch mit demselben Schlüssel gibt er das gespeicherte Ergebnis zurück, anstatt neu auszuführen.
Wo die Implementierung subtil wird
Das Muster klingt einfach. Drei Stellen, an denen es üblicherweise falsch läuft.
Den Schlüssel nach der Arbeit speichern, nicht davor. Die naive Implementierung schreibt die Operation, dann speichert den Idempotenz-Schlüssel. Wenn der Server zwischen diesen zwei Schreibvorgängen abstürzt, führt der nächste Versuch erneut aus. Den Schlüssel zuerst speichern — in derselben Transaktion wie die Operation — oder eine Datenbank verwenden, die garantiert, dass die beiden Schreibvorgänge atomar sind.
Die Antwort speichern, aber nicht den Operationszustand. Ein Wiederholungsversuch muss dieselbe Antwort zurückgeben, muss aber auch wissen, ob die ursprüngliche Operation tatsächlich abgeschlossen wurde oder noch läuft. Nur die Antwort zu speichern verliert den In-Flight-Zustand, und ein langsames Original plus ein schneller Wiederholungsversuch können in eine Race Condition geraten. Der Idempotenz-Datensatz braucht drei Zustände: in-progress, complete-with-response, failed.
Die Lebensdauer des Schlüssels nicht begrenzen. Idempotenz-Schlüssel leben im naiven Design ewig, und die Tabelle wächst ohne Begrenzung. Sie begrenzen — ein 24-Stunden-Fenster ist für die meisten Zahlungsanwendungsfälle großzügig — und die Begrenzung dokumentieren, damit Clients wissen, dass sie für Wiederholungsversuche nach diesem Fenster neue Schlüssel verwenden müssen.
Wofür Clients verantwortlich sind
Der Server kann alles richtig machen und trotzdem doppelte Schreibvorgänge erhalten, wenn der Client bei jedem Versuch mit einem neuen Schlüssel wiederholt. Der Vertrag muss sich auf die Client-Seite erstrecken.
Der Client generiert eine UUID pro logischer Operation, nicht pro HTTP-Versuch. Dieselbe logische Rückerstattung = derselbe Schlüssel, egal wie viele Netzwerkversuche nötig sind, um sie zu liefern. Den Schlüssel lokal speichern, bis die Operation entweder abgeschlossen ist oder die Begrenzung erreicht.
Das ist der Teil, der in mobilen SDKs überraschend oft übersehen wird — weil die Netzwerkschicht als etwas behandelt wird, das transparent wiederholt wird. Idempotenz-Schlüssel sickern aus der Netzwerkschicht in die Anwendungsschicht. Sie sind kein Transport-Anliegen; sie sind ein Domain-Anliegen.
Wann man anfangen soll
Jetzt. Vor der ersten doppelten Rückerstattung. Wir haben Teams gesehen, die sagten "wir fügen es vor dem Launch hinzu" und dann den Termin aus Launch-Gründen verschoben haben; die Idempotenz-Arbeit rutscht durch, der erste Vorfall lehrt sie, warum sie wichtig war — und die Bereinigung ist chaotisch, weil es jetzt Datensätze in der Produktion gibt, die ohne Schlüssel erstellt wurden.
Es ist eine der günstigeren Gewohnheiten, mit denen man anfangen kann — und eine der teureren, die man nachzurüsten hat.
Wir möchten Ihre Gedanken hören.
unser CTO Kyrylo Osadchuk wird innerhalb von 24 Stunden antworten. Kein Verkaufstrichter.
Start a conversation
We want to hear your thoughts
Re: Idempotenz ist das Einzige, das zwischen euch und einer doppelten Rückerstattung steht