Idempotenz und Schlüssel
Was ist Idempotenz
Idempotenz ist eine Eigenschaft einer Operation, bei der eine Wiederholung mit derselben ID den endgültigen Effekt nicht ändert. In verteilten Systemen ist dies der primäre Weg, um das Ergebnis trotz Retrays, doppelten Nachrichten und Timeouts „genau einer Verarbeitung“ gleichzusetzen.
Die Grundidee: Jede potenziell wiederholbare Operation muss mit einem Schlüssel gekennzeichnet werden, an dem das System erkennt, dass „es bereits getan wurde“ und das Ergebnis nicht mehr als einmal anwendet.
Wo es wichtig ist
Zahlungen und Salden: Abbuchungen/Gutschriften unter 'operation _ id'.
Buchungen/Kontingente/Limits: das gleiche Slot/Ressource.
Webhooks/Benachrichtigungen: Die wiederholte Lieferung sollte den Effekt nicht duplizieren.
Import/Migrationen: Dateien/Pakete erneut ausführen.
Stream-Processing: Takes vom Broker/CDC.
Schlüsseltypen und deren Umfang
1. Betriebsschlüssel - ID eines bestimmten Geschäftsvorgangsversuchs
Beispiele: 'idempotency _ key' (HTTP), 'operation _ id' (RPC).
Bereich: Service/Aggregat; in der Deduplizierungstabelle gespeichert.
2. Event key - eindeutige ID des Ereignisses/der Nachricht
Beispiele: 'event _ id' (UUID),'(producer_id, sequence)'.
Bereich: Verbraucher/Verbrauchergruppe; schützt die Projektionen.
3. Geschäftsschlüssel - der natürliche Schlüssel des Fachgebiets
Beispiele: „payment _ id“, „invoice _ number“, „(user_id, Tag)“.
Bereich: Aggregat; wird in Eindeutigkeits-/Versionsprüfungen angewendet.
TTL und Aufbewahrungsrichtlinie
TTL-Schlüssel ≥ mögliches Wiederholungsfenster: Log-Retention + Netzwerk-/Prozessverzögerungen.
Für kritische Domains (Zahlungen) TTL - Tage/Wochen; für Telemetrie - Stunden.
Reinigen Sie die Dedup-Tabellen mit Background-Jobs; für Audit - Archivieren.
Schlüsselspeicher (Deduplizierung)
Transaktions-DB (empfohlen): zuverlässige Upsert/Unique-Indizes, gemeinsame Transaktion mit Wirkung.
KV/Redis: schnell, bequem für kurze TTL, aber ohne gemeinsame Transaktion mit OLTP - Vorsicht.
State Store des Stream-Prozessors: lokal + Changelog im Broker; gut in Flink/KStreams.
- idempotency_keys
`consumer_id` (или `service`), `op_id` (PK на пару), `applied_at`, `ttl_expires_at`, `result_hash`/`response_status` (опц.) .
Indizes:'(consumer_id, op_id)'- einzigartig.
Grundlegende Implementierungstechniken
1) Effekt + Fortschritt Transaktion
Erfassung des Ergebnisses und Erfassung des Lese-/Positionsfortschritts - in einer Transaktion.
pseudo begin tx if not exists(select 1 from idempotency_keys where consumer=:c and op_id=:id) then
-- apply effect atomically (upsert/merge/increment)
apply_effect(...)
insert into idempotency_keys(consumer, op_id, applied_at)
values(:c,:id, now)
end if
-- record reading progress (offset/position)
upsert offsets set pos=:pos where consumer=:c commit
2) Optimistic Concurrency (Aggregatversion)
Schützt vor Doppelwirkung bei Rennen:sql update account set balance = balance +:delta,
version = version + 1 where id=:account_id and version=:expected_version;
-- if 0 rows are updated → retry/conflict
3) Idempotente sinks (upsert/merge)
Einmal aufladen:sql insert into bonuses(user_id, op_id, amount)
values(:u,:op,:amt)
on conflict (user_id, op_id) do nothing;
Idempotenz in Protokollen
HTTP/REST
Überschrift „Idempotency-Key: <uuid 'hash>“.
Der Server speichert einen Schlüsseleintrag und gibt dieselbe Antwort (oder Code' 409 '/' 422 'bei Invarianzkonflikt) zurück.
Für „unsichere“ POSTs ist ein 'Idempotency-Key' + nachhaltiger Timeout/Retray-Policy Pflicht.
gRPC/RPC
Metadaten 'idempotency _ key', 'request _ id' + deadline.
Server-Implementierung - wie in REST: Deduplizierungstabelle in einer Transaktion.
Broker/Streaming (Kafka/NATS/Pulsar)
Produzent: stabiler 'event _ id '/idempotenter Produzent (wo unterstützt).
Consumer: Dedup durch'(consumer_id, event_id) 'und/oder durch die Geschäftsversion der Einheit.
Separater DLQ für nicht-idempotente/beschädigte Nachrichten.
Webhooks und externe Partner
Fordern Sie' Idempotency-Key '/' event _ id 'im Vertrag an; Nachlieferung muss sicher sein.
Speichern Sie' notification _ id 'und Sendestatus; Bei Retrae - nicht duplizieren.
Schlüsselentwurf
Determinität: Retrays müssen denselben Schlüssel senden (im Voraus auf dem Client/Orchestrator generieren).
Bereich: Form 'op _ id' als' service: aggregate: id: purpose'.
Kollisionen: Verwenden Sie UUIDv7/ULID oder Hash von Geschäftsparametern (bei Bedarf mit Salz).
Hierarchie: Die allgemeine „operation _ id“ an der Front wird → in alle Unteroperationen übersetzt (idempotente Kette).
UX und Produktaspekte
Eine wiederholte Schlüsselabfrage sollte das gleiche Ergebnis (einschließlich Körper/Status) oder ein explizites „bereits erfüllt“ zurückgeben.
Zeigen Sie dem Benutzer den Status „Vorgang wird bearbeitet/abgeschlossen“ an, anstatt erneut „auf Glück“ zu versuchen.
Für lange Operationen - Polling nach Schlüssel ('GET/operations/{ op _ id}').
Beobachtbarkeit
Loggen Sie' op _ id', 'event _ id', 'trace _ id', Ergebnis: 'APPLIED '/' ALREADY _ APPLIED'.
Metriken: Wiederholungsrate, Größe der Dedup-Tabellen, Transaktionszeiten, Versionskonflikte, DLQ-Rate.
Trace: Der Schlüssel muss durch ein Team → ein Ereignis → eine Projektion → eine externe Herausforderung gehen.
Sicherheit und Compliance
Speichern Sie PII nicht in Schlüsseln; key - ID, nicht payload.
Verschlüsseln Sie sensible Felder in Deduplizierungsdatensätzen mit langer TTL.
Aufbewahrungspolitik: TTL und Archive; Recht auf Vergessenwerden - durch Krypto-Löschung von Antworten/Metadaten (wenn sie PII enthalten).
Testen
1. Duplikate: Führen Sie eine einzelne Nachricht/Anfrage 2-5 Mal durch - der Effekt ist genau einer.
2. Tropfen zwischen den Schritten: vor/nach der Aufzeichnung des Effekts, vor/nach der Fixierung des Offsets.
3. Neustart/Rebalance der Verbraucher: keine doppelte Anwendung.
4. Wettbewerb: Parallele Abfragen mit einem 'op _ id' → einen Effekt, der zweite ist 'ALREADY _ APPLIED/409'.
5. Langlebige Schlüssel: Überprüfen Sie den Ablauf von TTL und Wiederholungen nach der Wiederherstellung.
Antimuster
Zufälliger neuer Schlüssel für jeden Rückzug: Das System erkennt keine Wiederholungen.
Zwei separate Commits: zuerst der Effekt, dann der Offset - der Fall zwischen ihnen dupliziert den Effekt.
Vertrauen Sie nur dem Broker: Kein Deduplex im Sync/Aggregat.
Keine Aggregatversion: Ein wiederholtes Ereignis ändert den Zustand ein zweites Mal.
Fat Keys: Der Schlüssel umfasst Geschäftsfelder/PII → Lecks und komplexe Indizes.
Keine wiederholbaren Antworten: Der Kunde kann nicht sicher zurücktreten.
Beispiele
POST-Zahlung
Kunde: „POST/Zahlungen“ + „Idempotency-Key: k-789“.
Server: Transaktion - erstellt 'payment' und einen Eintrag in 'idempotency _ keys'.
Wiederholung: Gibt den gleichen '201 '/Körper zurück; wenn der Invarianzkonflikt „409“ ist.
Bonusguthaben (sink)
sql insert into credits(user_id, op_id, amount, created_at)
values(:u,:op,:amt, now)
on conflict (user_id, op_id) do nothing;
Projektion aus Ereignissen
Consumer speichert 'seen (event_id)' und 'version' der Einheit; repeat - Ignoriere/idempotent upsert.
Der Lesefortschritt wird in derselben Transaktion erfasst wie die Aktualisierung der Projektion.
Checkliste für die Produktion
- Für alle unsicheren Vorgänge ist ein idempotenter Schlüssel und sein Sichtbarkeitsbereich definiert.
- Es gibt Deduplizierungstabellen mit TTL und eindeutigen Indizes.
- Der Effekt und der Fortschritt des Lesens sind atomar.
- Im Write-Modell ist ein optimistischer Wettbewerb enthalten (Version/sequence).
- API-Verträge erfassen den 'Idempotency-Key '/' operation _ id' und das Wiederholungsverhalten.
- Metriken und Protokolle enthalten 'op _ id '/' event _ id '/' trace _ id'.
- Dubletten-, Sturz- und Renntests - in CI.
- Die TTL/Archiv-Richtlinie und die PII-Sicherheit werden eingehalten.
FAQ
Wie unterscheidet sich „Idempotency-Key“ von „Request-Id“?
„Request-Id“ - Nachverfolgung; 'Idempotency-Key' ist die semantische Kennung der Operation, die bei Wiederholungen gleich sein muss.
Ist Idempotenz ohne DB möglich?
Für ein kurzes Fenster - ja (Redis/In-Process-Cache), aber ohne eine gemeinsame Transaktion steigt das Risiko von Takes. In kritischen Domänen - besser in einer einzigen DB-Transaktion.
Was tun mit externen Partnern?
Verhandeln Sie Schlüssel und wiederholbare Antworten. Wenn der Partner nicht unterstützt - wickeln Sie den Anruf in Ihre idempotente Schicht ein und speichern Sie „bereits angewendet“.
Wie wählt man TTL?
Summieren Sie die maximalen Verzögerungen: Log-Retention + Worst-Case Netzwerk/Rebalance + Puffer. Füge einen Vorrat hinzu (× 2).
Summe
Idempotenz ist die Disziplin der Schlüssel, Transaktionen und Versionen. Stabile Operations-IDs + atomare Fixierung des Effekts und des Lesefortschritts + idempotente Sinks/Projektionen ergeben „genau einen Effekt“ ohne die Magie der Transportebene. Machen Sie die Schlüssel deterministisch, die TTL realistisch und die Tests bösartig. Dann werden Retrays und Duplikate zur Routine und nicht zu Vorfällen.