12. Mai 2025·7 Min. Lesezeit

Idempotente Endpunkte in Go: Schlüssel, Deduplizierungstabellen und Wiederholungsversuche

Entwerfen Sie idempotente Endpunkte in Go mit Idempotenzschlüsseln, Deduplizierungstabellen und wiederholungssicheren Handlern für Zahlungen, Importe und Webhooks.

Idempotente Endpunkte in Go: Schlüssel, Deduplizierungstabellen und Wiederholungsversuche

Warum Wiederholungen Duplikate erzeugen (und warum Idempotenz wichtig ist)

Wiederholungen passieren selbst wenn nichts „schief“ ist. Ein Client erlebt ein Timeout, während der Server noch arbeitet. Eine mobile Verbindung bricht ab und die App versucht es erneut. Ein Job-Runner erhält einen 502 und schickt automatisch dieselbe Anfrage noch einmal. Bei mindestens-einmal-Zustellung (üblich bei Queues und Webhooks) sind Duplikate normal.

Deshalb ist Idempotenz wichtig: wiederholte Anfragen sollten zum selben Endergebnis führen wie eine einzige Anfrage.

Ein paar Begriffe lassen sich leicht verwechseln:

  • Safe: der Aufruf ändert keinen Zustand (z. B. ein Lesezugriff).
  • Idempotent: mehrmaliges Aufrufen hat denselben Effekt wie einmaliges Aufrufen.
  • At-least-once: der Sender wiederholt so lange, bis die Nachricht „sitzt“, der Empfänger muss also Duplikate behandeln.

Ohne Idempotenz können Wiederholungen echten Schaden anrichten. Ein Zahlungsendpunkt kann zweimal belasten, wenn die erste Abbuchung erfolgreich war, die Antwort aber den Client nicht erreichte. Ein Import-Endpunkt kann doppelte Zeilen erzeugen, wenn ein Worker nach einem Timeout erneut versucht. Ein Webhook-Handler kann dasselbe Event zweimal verarbeiten und zwei E-Mails versenden.

Der Kernpunkt: Idempotenz ist ein API-Vertrag, keine private Implementierungsentscheidung. Clients müssen wissen, was sie erneut senden dürfen, welchen Schlüssel sie senden sollen und welche Antwort sie erhalten können, wenn ein Duplikat erkannt wird. Wenn Sie das Verhalten stillschweigend ändern, brechen Sie Retry-Logik und schaffen neue Fehlerfälle.

Idempotenz ersetzt außerdem nicht Monitoring und Abstimmung. Verfolgen Sie Duplikatraten, protokollieren Sie „Replay“-Entscheidungen und vergleichen Sie regelmäßig externe Systeme (z. B. einen Zahlungsanbieter) mit Ihrer Datenbank.

Wählen Sie Scope und Regeln für jeden Endpunkt

Bevor Sie Tabellen oder Middleware hinzufügen, klären Sie, was „dasselbe Request“ bedeutet und welches Versprechen Ihr Server bei einer Wiederholung gibt.

Die meisten Probleme tauchen bei POST auf, weil damit oft etwas erzeugt oder eine Nebenwirkung ausgelöst wird (Karte belasten, Nachricht senden, Import starten). PATCH kann ebenfalls Idempotenz benötigen, wenn es Seiteneffekte auslöst und nicht nur ein simples Feldupdate ist. GET sollte keinen Zustand verändern.

Definieren Sie den Scope: wo ein Schlüssel eindeutig ist

Wählen Sie einen Scope, der zu Ihren Geschäftsregeln passt. Zu breit blockiert legitime Arbeit. Zu eng erlaubt Duplikate.

Gängige Scopes:

  • Pro Endpoint + Kunde
  • Pro Endpoint + externes Objekt (z. B. invoice_id oder order_id)
  • Pro Endpoint + Mandant (bei Multi-Tenant-Systemen)
  • Pro Endpoint + Zahlungsmethode + Betrag (nur wenn Ihre Produktregeln das zulassen)

Beispiel: Für einen „Create payment“-Endpunkt machen Sie den Schlüssel pro Kunde eindeutig. Für „Webhook-Event ingest“ scope'n Sie ihn auf die Event-ID des Zahlungsanbieters (globale Einzigartigkeit seitens des Providers).

Entscheiden Sie, was Sie bei Duplikaten wiedergeben

Wenn ein Duplikat eintrifft, geben Sie dasselbe Ergebnis zurück wie beim ersten erfolgreichen Versuch. Praktisch heißt das, denselben HTTP-Statuscode und dieselbe Antwort zurückzuspielen (oder zumindest dieselbe Ressourcen-ID und denselben Zustand).

Clients bauen darauf. Gelingt der erste Versuch, aber das Netzwerk fiel aus, sollte die Wiederholung keine zweite Abbuchung oder einen zweiten Import-Job erzeugen.

Wählen Sie ein Aufbewahrungsfenster

Schlüssel sollten verfallen. Bewahren Sie sie lange genug auf, um realistische Wiederholungen und verzögerte Jobs abzudecken.

  • Zahlungen: 24 bis 72 Stunden sind üblich.
  • Importe: eine Woche kann sinnvoll sein, wenn Nutzer später erneut versuchen.
  • Webhooks: stimmen Sie sich an der Retry-Policy des Providers ab.

Definieren Sie „dasselbe Request": expliziter Schlüssel vs. Body-Hash

Ein expliziter Idempotenzschlüssel (Header oder Feld) ist in der Regel die sauberste Regel.

Ein Body-Hash kann als Backstop dienen, bricht aber leicht bei harmlosen Änderungen (Feldreihenfolge, Whitespace, Zeitstempel). Wenn Sie Hashing verwenden, normalisieren Sie die Eingabe und legen Sie genau fest, welche Felder einbezogen werden.

Idempotenzschlüssel: wie sie praktisch funktionieren

Ein Idempotenzschlüssel ist ein einfacher Vertrag zwischen Client und Server: „Wenn du diesen Schlüssel wieder siehst, behandle die Anfrage als dieselbe.“ Er ist eines der praktischsten Mittel für wiederholungssichere APIs.

Der Schlüssel kann von beiden Seiten kommen, aber für die meisten APIs sollte er vom Client generiert werden. Der Client weiß, wann er dieselbe Aktion wiederholt, und kann denselben Schlüssel über mehrere Versuche hinweg wiederverwenden. Server-generierte Schlüssel helfen, wenn Sie zunächst eine „Draft“-Ressource erstellen (z. B. einen Import-Job) und Clients dann durch Referenzierung dieser Job-ID erneut versuchen lassen, aber sie helfen nicht beim allerersten Request.

Verwenden Sie einen zufälligen, nicht erratbaren String. Zielen Sie auf mindestens 128 Bit Zufälligkeit (z. B. 32 Hex-Zeichen oder eine UUID). Bauen Sie Schlüssel nicht aus Zeitstempeln oder Nutzer-IDs.

Auf dem Server speichern Sie den Schlüssel mit genügend Kontext, um Missbrauch zu erkennen und das ursprüngliche Ergebnis wiederzugeben:

  • Wer den Aufruf gemacht hat (Account- oder Nutzer-ID)
  • Welcher Endpoint oder welche Operation gemeint ist
  • Ein Hash der wichtigen Request-Felder
  • Aktueller Status (in_progress, succeeded, failed)
  • Die Antwort zum Replizieren (Statuscode und Body)

Ein Schlüssel sollte gescoped sein, typischerweise pro Nutzer (oder pro API-Token) plus Endpoint. Wenn derselbe Schlüssel mit einer anderen Nutzlast wiederverwendet wird, lehnen Sie ihn mit einem klaren Fehler ab. Das verhindert versehentliche Kollisionen, bei denen ein fehlerhafter Client einen neuen Zahlungsbetrag mit einem alten Schlüssel sendet.

Beim Replay geben Sie dasselbe Ergebnis wie beim ersten erfolgreichen Versuch zurück. Das heißt: denselben HTTP-Statuscode und denselben Antwort-Body, nicht ein frisches Lesen, das sich inzwischen verändert haben könnte.

Deduplizierungstabellen in PostgreSQL: ein simples, zuverlässiges Muster

Eine dedizierte Deduplizierungstabelle ist eine der einfachsten Möglichkeiten, Idempotenz zu implementieren. Die erste Anfrage legt eine Zeile für den Idempotenzschlüssel an. Jede Wiederholung liest dieselbe Zeile und gibt das gespeicherte Ergebnis zurück.

Was zu speichern ist

Halten Sie die Tabelle klein und fokussiert. Eine übliche Struktur:

  • key: der Idempotenzschlüssel (text)
  • owner: wem der Schlüssel gehört (user_id, account_id oder API-Client-ID)
  • request_hash: ein Hash der wichtigen Request-Felder
  • response: die finale Antwort-Payload (oft JSON) oder ein Verweis auf ein gespeichertes Ergebnis
  • created_at: wann der Schlüssel erstmals gesehen wurde

Die Unique-Constraint ist der Kern dieses Musters. Erzwingen Sie Eindeutigkeit auf (owner, key), damit ein Client keine Duplikate erzeugt und zwei unterschiedliche Clients nicht kollidieren.

Speichern Sie außerdem einen request_hash, damit Sie Missbrauch erkennen können. Kommt eine Wiederholung mit demselben Schlüssel, aber einem anderen Hash, geben Sie einen Fehler statt zwei unterschiedliche Operationen zu vermischen.

Aufbewahrung und Indizierung

Deduplizierungszeilen sollten nicht ewig leben. Bewahren Sie sie lange genug für reale Wiederholungen auf und bereinigen Sie dann.

Für Geschwindigkeit unter Last:

  • Unique-Index auf (owner, key) für schnelles Insert oder Lookup
  • Optionaler Index auf created_at, um Cleanup günstig zu machen

Wenn die Antwort groß ist, speichern Sie einen Verweis (z. B. eine Result-ID) und halten Sie die vollständige Payload anderswo. Das reduziert Tabellenwachstum und behält konsistentes Retry-Verhalten.

Schritt-für-Schritt: ein wiederholungssicherer Handler-Flow in Go

Webhooks sicher verarbeiten
Deduplizieren Sie eingehende Events anhand der Provider-Event-ID, bevor Sie Seiteneffekte auslösen.
Webhooks verarbeiten

Ein wiederholungssicherer Handler braucht zwei Dinge: eine stabile Möglichkeit, „dieselbe Anfrage erneut“ zu identifizieren, und einen dauerhaften Ort, um das erste Ergebnis zu speichern, damit Sie es wiedergeben können.

Ein praktischer Ablauf für Zahlungen, Importe und Webhook-Ingestion:

  1. Validieren Sie die Anfrage und leiten Sie dann drei Werte ab: einen Idempotenzschlüssel (aus Header oder Client-Feld), einen Owner (Tenant- oder Nutzer-ID) und einen Request-Hash (Hash der wichtigen Felder).

  2. Starten Sie eine Datenbanktransaktion und versuchen Sie, einen Deduplizierungs-Eintrag anzulegen. Machen Sie ihn einzigartig auf (owner, key). Speichern Sie request_hash, Status (started, completed) und Platzhalter für die Antwort.

  3. Wenn der Insert einen Konflikt erzeugt, laden Sie die bestehende Zeile. Ist sie completed, geben Sie die gespeicherte Antwort zurück. Ist sie started, warten Sie entweder kurz (einfaches Polling) oder geben Sie 409/202 zurück, damit der Client später erneut versucht.

  4. Nur wenn Sie den Deduplizierungs-Eintrag erfolgreich "beanspruchen", führen Sie die Geschäftslogik einmal aus. Schreiben Sie Seiteneffekte wenn möglich innerhalb derselben Transaktion. Persistieren Sie das Geschäftsergebnis plus die HTTP-Antwort (Statuscode und Body).

  5. Committen Sie und protokollieren Sie Schlüssel und Owner, damit der Support Duplikate nachvollziehen kann.

Ein minimales Tabellenmuster:

create table idempotency_keys (
  owner_id text not null,
  idem_key text not null,
  request_hash text not null,
  status text not null,
  response_code int,
  response_body jsonb,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),
  primary key (owner_id, idem_key)
);

Beispiel: Ein "Create payout"-Endpunkt timeouts, nachdem die Belastung durchgeführt wurde. Der Client wiederholt mit demselben Schlüssel. Ihr Handler trifft auf den Konflikt, sieht einen completed-Eintrag und gibt die ursprüngliche Payout-ID zurück, ohne erneut zu belasten.

Zahlungen: genau einmal belasten, selbst bei Timeouts

Bei Zahlungen wird Idempotenz unverzichtbar. Netzwerke fallen aus, mobile Apps wiederholen und Gateways timen manchmal aus, nachdem sie bereits die Belastung erstellt haben.

Eine praktische Regel: Der Idempotenzschlüssel schützt die Erstellung der Belastung, und die Provider-ID (charge/intent ID) wird danach zur Quelle der Wahrheit. Sobald Sie eine Provider-ID gespeichert haben, erstellen Sie für dieselbe Anfrage keine neue Belastung.

Ein Muster, das Wiederholungen und Gateway-Ungewissheiten abdeckt:

  • Lesen und validieren Sie den Idempotenzschlüssel.
  • Erzeugen oder holen Sie in einer DB-Transaktion eine Payment-Zeile, keyed by (merchant_id, idempotency_key). Hat sie bereits eine provider_id, geben Sie das gespeicherte Ergebnis zurück.
  • Falls keine provider_id existiert, rufen Sie das Gateway auf, um einen PaymentIntent/Charge zu erstellen.
  • Schlägt der Gateway-Aufruf fehl oder timeoutt, speichern Sie den Status „pending“ und geben eine konsistente Antwort zurück, die dem Client sagt, dass ein erneuter Versuch sicher ist.

Der entscheidende Punkt ist, wie Sie Timeouts behandeln: gehen Sie nicht von einem Fehlschlag aus. Markieren Sie die Zahlung als pending und bestätigen Sie später beim Provider (oder via Webhook), sobald Sie die Provider-ID haben.

Fehlerantworten sollten vorhersehbar sein. Clients bauen ihre Retry-Logik auf dem auf, was Sie zurückgeben, also halten Sie Statuscodes und Fehlerformate stabil.

Importe und Batch-Endpunkte: deduplizieren ohne Fortschritt zu verlieren

Bauen Sie Ihre nächste App
Verwandeln Sie Ihr API-Vertrag in ein funktionierendes internes Tool oder Kundenportal ohne großen Boilerplate-Aufwand.
Loslegen

Bei Importen schaden Duplikate am meisten. Ein Nutzer lädt eine CSV hoch, Ihr Server läuft bei 95 % in ein Timeout und der Nutzer versucht es erneut. Ohne Plan erzeugen Sie entweder doppelte Zeilen oder zwingen den Nutzer zum Neustart.

Bei Batch-Arbeit denken Sie in zwei Ebenen: dem Import-Job und den Items darin. Job-Level-Idempotenz verhindert, dass dieselbe Anfrage mehrere Jobs erstellt. Item-Level-Idempotenz verhindert, dass dieselbe Zeile mehrfach angewendet wird.

Ein Job-Level-Muster ist, für jede Import-Anfrage einen Idempotenzschlüssel zu verlangen (oder ihn aus einem stabilen Request-Hash plus Nutzer-ID abzuleiten). Speichern Sie ihn mit einem import_job-Datensatz und geben Sie bei Wiederholungen dieselbe Job-ID zurück. Der Handler sollte sagen können: „Ich kenne diesen Job, hier ist sein aktueller Zustand“, statt „fang von vorne an“.

Für Item-Level-Deduplizierung verlassen Sie sich auf einen natürlichen Schlüssel, der bereits in den Daten existiert. Jede Zeile könnte z. B. eine external_id aus dem Quellsystem enthalten oder eine stabile Kombination wie (account_id, email). Erzwingen Sie das mit einem Unique-Constraint in PostgreSQL und nutzen Sie Upsert-Verhalten, sodass Wiederholungen keine Duplikate erzeugen.

Bevor Sie ausliefern, legen Sie fest, was ein Replay macht, wenn eine Zeile bereits existiert. Halten Sie es explizit: überspringen, bestimmte Felder updaten oder fehlschlagen. Vermeiden Sie „merge“, es sei denn, Sie haben sehr klare Regeln.

Partial Success ist normal. Statt eines einzigen großen „ok“ oder „failed“ speichern Sie pro Zeile das Ergebnis im Job: Zeilennummer, natürlicher Schlüssel, Status (created, updated, skipped, error) und eine Fehlermeldung. Bei einer Wiederholung können Sie sicher neu ausführen und gleichzeitig dieselben Ergebnisse für bereits abgeschlossene Zeilen beibehalten.

Um Importe wiederanstartbar zu machen, fügen Sie Checkpoints hinzu. Verarbeiten Sie in Seiten (z. B. 500 Zeilen), speichern Sie den letzten verarbeiteten Cursor (Zeilenindex oder Quellcursor) und aktualisieren Sie ihn nach jedem Commit der Seite. Stürzt der Prozess ab, setzt der nächste Versuch am letzten Checkpoint fort.

Webhook-Ingestion: deduplizieren, validieren, dann sicher verarbeiten

Importe wiederholungsfreundlich machen
Erstellen Sie wiederanlaufbare Import-Jobs, die bei Wiederholungen dieselbe Job-ID zurückgeben.
Jetzt erstellen

Webhook-Sender wiederholen. Sie senden Events außerdem möglicherweise außer Reihenfolge. Wenn Ihr Handler bei jeder Zustellung Zustand ändert, erzeugen Sie früher oder später doppelte Datensätze, doppelte E-Mails oder doppelte Abbuchungen.

Beginnen Sie damit, den besten Deduplizierungsschlüssel zu wählen. Gibt der Provider eine eindeutige Event-ID, nutzen Sie diese. Verwenden Sie nur dann einen Hash des Payloads, wenn keine Event-ID vorhanden ist.

Sicherheit kommt zuerst: Verifizieren Sie die Signatur, bevor Sie etwas annehmen. Wenn die Signatur fehlschlägt, lehnen Sie die Anfrage ab und schreiben keinen Deduplizierungseintrag. Ansonsten könnte ein Angreifer eine Event-ID „reservieren“ und echte Events blockieren.

Ein sicherer Ablauf bei Wiederholungen:

  • Verifizieren Sie Signatur und Grundform (erforderliche Header, Event-ID).
  • Inserten Sie die Event-ID in eine Deduplizierungstabelle mit Unique-Constraint.
  • Schlägt der Insert wegen Duplikats fehl, geben Sie sofort 200 zurück.
  • Speichern Sie das rohe Payload (und Header), wenn es für Audit und Debugging nützlich ist.
  • Enqueuen Sie die Verarbeitung und antworten Sie schnell mit 200.

Schnelle Bestätigung ist wichtig, weil viele Provider kurze Timeouts haben. Tun Sie in der Anfrage das kleinstmögliche, zuverlässige: verifizieren, deduplizieren, persistieren. Verarbeiten Sie dann asynchron (Worker, Queue, Background-Job). Können Sie nicht asynchron arbeiten, halten Sie die Verarbeitung idempotent, indem Sie interne Seiteneffekte ebenfalls an dieselbe Event-ID koppeln.

Out-of-Order-Delivery ist normal. Gehen Sie nicht davon aus, dass „created“ vor „updated“ kommt. Bevorzugen Sie Upserts nach externer Objekt-ID und verfolgen Sie den zuletzt verarbeiteten Event-Timestamp oder eine Version.

Das Speichern roher Payloads hilft, wenn ein Kunde sagt „wir haben das Update nie bekommen“. Sie können die Verarbeitung vom gespeicherten Body erneut ausführen, nachdem Sie einen Bug behoben haben, ohne den Provider um ein erneutes Senden zu bitten.

Concurrency: korrekt bleiben bei parallelen Requests

Wiederholungen werden kompliziert, wenn zwei Requests mit demselben Idempotenzschlüssel gleichzeitig ankommen. Wenn beide Handler die Arbeit „ausführen“ bevor einer das Ergebnis speichert, können Sie trotzdem doppelt belasten, doppelt importieren oder doppelt enqueuen.

Der einfachste Koordinationspunkt ist die Datenbanktransaktion. Machen Sie den ersten Schritt zum „Schlüssel beanspruchen“ und lassen Sie die Datenbank entscheiden, wer gewinnt. Gängige Optionen:

  • Einzigartiges Insert in eine Deduplizierungstabelle (die DB entscheidet den Gewinner)
  • SELECT ... FOR UPDATE nachdem die Deduplizierungszeile erstellt (oder gefunden) wurde
  • Transaktionslevel-Advisory-Locks, gehashed auf den Idempotenzschlüssel
  • Unique-Constraints auf dem Geschäftsdatensatz als finale Absicherung

Für langlaufende Arbeit vermeiden Sie, einen Row-Lock zu halten, während Sie externe Systeme aufrufen oder Minuten dauernde Importe laufen lassen. Speichern Sie stattdessen einen kleinen State-Machine-Zustand in der Deduplizierungszeile, sodass andere Anfragen schnell aussteigen können.

Eine praktische Menge an Zuständen:

  • in_progress mit started_at
  • completed mit gecachtem Response
  • failed mit einem Fehlercode (optional, abhängig von Ihrer Retry-Policy)
  • expires_at (für Cleanup)

Beispiel: Zwei App-Instanzen erhalten dieselbe Zahlungsanfrage. Instanz A insertet den Schlüssel und markiert in_progress, dann ruft sie den Provider auf. Instanz B trifft den Konfliktpfad, liest die Deduplizierungszeile, sieht in_progress und gibt eine schnelle „wird noch bearbeitet“-Antwort zurück (oder wartet kurz und prüft erneut). Wenn A fertig ist, updated sie die Zeile auf completed und speichert den Response-Body, sodass spätere Wiederholungen genau dieselbe Ausgabe bekommen.

Häufige Fehler, die Idempotenz brechen

Zahlungen nur einmal belasten
Erstellen Sie einen Zahlungsablauf, der doppelte Abbuchungen mit Idempotenzschlüsseln und gespeicherten Provider-IDs vermeidet.
Zahlungen einrichten

Die meisten Idempotenz-Bugs sind keine Fragen zu komplizierten Locks. Es sind „fast richtige“ Entscheidungen, die bei Wiederholungen, Timeouts oder zwei Nutzern mit ähnlichen Aktionen versagen.

Eine häufige Falle ist, den Idempotenzschlüssel als global einzigartig zu behandeln. Wenn Sie ihn nicht scopen (nach Nutzer, Konto oder Endpoint), können zwei verschiedene Clients kollidieren und einer erhält das Ergebnis des anderen.

Ein weiteres Problem ist, denselben Schlüssel mit unterschiedlichem Request-Body zu akzeptieren. War der erste Aufruf für $10 und die Wiederholung für $100, sollten Sie nicht stillschweigend das erste Ergebnis zurückgeben. Speichern Sie einen Request-Hash (oder wichtige Felder), vergleichen Sie beim Replay und geben Sie einen klaren Konfliktfehler zurück.

Clients sind außerdem verwirrt, wenn Replays eine andere Antwortform oder einen anderen Statuscode liefern. Gab der erste Aufruf 201 mit einem JSON-Body zurück, sollte das Replay denselben Body und denselben Statuscode liefern. Änderungen im Replay-Verhalten zwingen Clients zum Raten.

Fehler, die häufig Duplikate verursachen:

  • Sich nur auf eine In-Memory-Map oder einen Cache verlassen und Deduplizierungszustand beim Neustart verlieren.
  • Einen Schlüssel ohne Scope verwenden (Cross-User oder Cross-Endpoint-Kollisionen).
  • Payload-Mismatches für denselben Schlüssel nicht validieren.
  • Die Seiteneffekte zuerst ausführen (belasten, einfügen, publishen) und die Deduplizierungszeile danach schreiben.
  • Bei jeder Wiederholung eine neue generierte ID zurückgeben statt das ursprüngliche Ergebnis zu replayen.

Ein Cache kann Lesezugriffe beschleunigen, aber die Quelle der Wahrheit sollte dauerhaft sein (normalerweise PostgreSQL). Ansonsten können Wiederholungen nach Deploys Duplikate erzeugen.

Planen Sie außerdem Cleanup. Speichern Sie nicht ewig jeden Schlüssel, sonst wachsen Tabellen und Indizes und werden langsamer. Setzen Sie ein Aufbewahrungsfenster basierend auf realem Retry-Verhalten, löschen Sie alte Zeilen und halten Sie den Unique-Index klein.

Schnelle Checkliste und nächste Schritte

Behandeln Sie Idempotenz als Teil Ihres API-Vertrags. Jeder Endpunkt, der von einem Client, einer Queue oder einem Gateway wiederholt werden könnte, braucht eine klare Regel dafür, was „dasselbe Request“ bedeutet und wie ein „gleiches Ergebnis“ aussieht.

Eine Checkliste vor dem Release:

  • Für jeden wiederholbaren Endpunkt: Ist der Idempotenz-Scope definiert (pro Nutzer, pro Konto, pro Bestellung, pro externes Event) und dokumentiert?
  • Wird Deduplizierung von der Datenbank erzwungen (Unique-Constraint auf Idempotenzschlüssel und Scope) und nicht nur im Code geprüft?
  • Geben Sie beim Replay denselben Statuscode und Antwort-Body zurück (oder eine dokumentierte stabile Teilmenge), nicht ein frisches Objekt oder einen neuen Zeitstempel?
  • Bei Zahlungen: Gehen Sie mit unbekannten Ergebnissen sicher um (Timeout nach Submit, Gateway meldet „processing“) ohne doppelt zu belasten?
  • Machen Logs und Metriken deutlich, wann eine Anfrage erstmalig gesehen wurde vs. wann sie ein Replay war?

Wenn irgendein Punkt ein „vielleicht“ ist, beheben Sie ihn jetzt. Die meisten Fehler treten unter Last auf: parallele Wiederholungen, langsame Netzwerke und partielle Ausfälle.

Wenn Sie interne Tools oder Kunden-Apps mit AppMaster (appmaster.io) bauen, hilft es, Idempotenzschlüssel und die PostgreSQL-Deduplizierungstabelle früh zu entwerfen. So bleibt Ihr Retry-Verhalten konsistent, auch wenn die Plattform den generierten Go-Backend-Code bei geänderten Anforderungen neu erzeugt.

FAQ

Warum erzeugen Wiederholungen doppelte Abbuchungen oder doppelte Einträge, obwohl meine API korrekt ist?

Wiederholungen sind normal, weil Netzwerke und Clients auf gewöhnliche Weise ausfallen. Eine Anfrage kann auf dem Server erfolgreich sein, aber die Antwort erreicht den Client nicht; der Client versucht es erneut und Sie führen dieselbe Arbeit zweimal aus, sofern der Server das ursprüngliche Ergebnis nicht wiedergeben kann.

Was sollte ich als Idempotenzschlüssel verwenden und wer sollte ihn erzeugen?

Senden Sie denselben Schlüssel bei jeder Wiederholung derselben Aktion. Erzeugen Sie ihn auf der Client-Seite als zufälligen, nicht vorhersagbaren String (z. B. eine UUID) und verwenden Sie ihn nicht für eine andere Aktion erneut.

Wie sollte ich Idempotenzschlüssel scopen, damit sie nicht zwischen Nutzern oder Mandanten kollidieren?

Scopes sollten zu Ihrer Geschäftslogik passen, typischerweise pro Endpoint plus eine Identität des Aufrufers wie Nutzer-, Konto- oder Mandanten-ID oder API-Token. So verhindern Sie, dass zwei verschiedene Kunden versehentlich denselben Schlüssel verwenden und gegenseitig Ergebnisse erhalten.

Was sollte meine API zurückgeben, wenn sie eine doppelte Anfrage mit demselben Schlüssel erhält?

Geben Sie dasselbe Ergebnis zurück wie beim ersten erfolgreichen Versuch. Praktisch heißt das: Replizieren Sie denselben HTTP-Statuscode und dieselbe Antwort, oder zumindest dieselbe Ressourcen-ID und denselben Zustand, sodass Clients sicher erneut anfragen können, ohne eine zweite Nebenwirkung auszulösen.

Was passiert, wenn der Client versehentlich denselben Idempotenzschlüssel mit einer anderen Anfrage-Nutzlast wiederverwendet?

Verwerfen Sie den Versuch mit einem klaren Konfliktfehler statt zu raten. Speichern und vergleichen Sie einen Hash der wichtigen Request-Felder; wenn der Schlüssel passt, aber die Nutzlast anders ist, sollten Sie schnell mit einem Fehler antworten, um zwei unterschiedliche Operationen unter einem Schlüssel zu vermeiden.

Wie lange sollte ich Idempotenzschlüssel in meiner Datenbank aufbewahren?

Bewahren Sie Schlüssel lange genug auf, um realistische Wiederholungen abzudecken, und löschen Sie sie dann. Übliche Defaults: 24–72 Stunden für Zahlungen, etwa eine Woche für Importe. Bei Webhooks richten Sie sich am Retry-Verhalten des Senders aus, damit späte Wiederholungen ebenfalls dedupliziert werden.

Was ist das einfachste PostgreSQL-Schema-Muster für Idempotenz?

Eine dedizierte Deduplizierungstabelle funktioniert gut, weil die Datenbank eine eindeutige Einschränkung erzwingen und Neustarts überleben kann. Speichern Sie Scope, Schlüssel, einen Request-Hash, einen Status und die Antwort zum Replizieren, und machen Sie (owner, key) einzigartig, sodass nur eine Anfrage „gewinnt“.

Wie gehe ich mit zwei identischen Anfragen um, die zur gleichen Zeit ankommen?

Reservieren Sie den Schlüssel innerhalb einer Datenbanktransaktion, und führen Sie die Seiteneffekte nur aus, wenn Sie ihn erfolgreich beansprucht haben. Trifft parallel eine zweite Anfrage ein, sollte sie die Unique-Constraint auslösen, den in_progress- oder completed-Status sehen und stattdessen eine Warte-/Replay-Antwort zurückgeben.

Wie verhindere ich doppelte Abbuchungen, wenn das Zahlungs-Gateway ein Timeout meldet?

Behandeln Sie Timeouts als „unbekannt“, nicht als „fehlgeschlagen“. Speichern Sie einen Pending-Zustand und, falls vorhanden, die Provider-ID als Quelle der Wahrheit, sodass Wiederholungen dasselbe Zahlungsergebnis zurückgeben, anstatt eine neue Abbuchung anzustoßen.

Wie mache ich Importe wiederholungs-sicher, ohne dass Nutzer neu starten müssen oder Duplikate entstehen?

Deduplizieren Sie auf zwei Ebenen: Job-Ebene und Item-Ebene. Geben Sie bei Wiederholungen dieselbe Import-Job-ID zurück, und erzwingen Sie für Zeilen einen natürlichen Schlüssel (z. B. external_id oder (account_id, email)) mit Unique-Constraints oder Upserts, damit erneute Verarbeitung keine Duplikate erzeugt.

Einfach zu starten
Erschaffe etwas Erstaunliches

Experimentieren Sie mit AppMaster mit kostenlosem Plan.
Wenn Sie fertig sind, können Sie das richtige Abonnement auswählen.

Starten