In verteilten Systemen ist die mehrfache Ausführung derselben Operation unvermeidlich. Netzwerkfehler, System-Restarts und Retry-Mechanismen führen dazu, dass Events mehrfach ankommen können. Idempotenz sorgt dafür, dass wiederholte Verarbeitung dasselbe Ergebnis liefert wie einmalige Verarbeitung - eine fundamentale Eigenschaft für zuverlässige Event-getriebene Systeme.
Duplikate entstehen in Event-Systemen aus verschiedenen Quellen, die oft außerhalb der Kontrolle der Anwendung liegen. Netzwerk-Retries sind die häufigste Ursache: Ein Producer sendet ein Event, erhält keine Bestätigung wegen eines Timeouts und sendet das Event erneut. Tatsächlich war das erste Event bereits angekommen, nur die Antwort ging verloren.
Producer-Restarts können Events duplizieren, wenn der Producer nicht genau verfolgen kann, welche Events bereits gesendet wurden. Nach einem Absturz beginnt er möglicherweise an einem früheren Punkt und sendet bereits gesendete Events erneut.
Consumer-Restarts sind besonders tückisch. Ein Consumer verarbeitet ein Event erfolgreich, stürzt aber ab, bevor er das Offset committet. Nach dem Restart verarbeitet er dasselbe Event erneut, da aus seiner Sicht die Verarbeitung nie stattgefunden hat.
| Duplikat-Quelle | Wahrscheinlichkeit | Auswirkung | Erkennbarkeit |
|---|---|---|---|
| Netzwerk-Retry | Hoch | Events kommen mehrfach an | Schwer erkennbar |
| Producer-Restart | Mittel | Batch von Events wird wiederholt | Oft erkennbar am Timestamp |
| Consumer-Restart | Hoch | Letzte Events vor Absturz | Erkennbar durch Offset-Tracking |
| Infrastructure-Fehler | Niedrig | Massive Duplikation möglich | Oft durch Monitoring erkennbar |
Die Auswirkungen können gravierend sein: Finanzielle
Transaktionen werden doppelt ausgeführt,
Benachrichtigungen mehrfach versendet,
Lagerbestände falsch berechnet. In einem
E-Commerce-System könnte ein doppelt verarbeitetes
PaymentProcessed-Event dazu führen, dass einem Kunden
versehentlich der doppelte Betrag gutgeschrieben wird.
Das Idempotent Consumer Pattern stellt sicher, dass die mehrfache Verarbeitung desselben Events dasselbe Ergebnis liefert wie die einmalige Verarbeitung. Dies erfordert, dass Consumer-Services so designed werden, dass sie “sicher wiederholbar” sind.
Natürliche Idempotenz entsteht, wenn die Geschäftslogik von Natur aus idempotent ist. Das Setzen eines Status ist beispielsweise idempotent: Einen Auftrag auf “SHIPPED” zu setzen hat dasselbe Ergebnis, egal ob es einmal oder mehrfach geschieht.
Künstliche Idempotenz muss explizit implementiert werden, wenn die Operation von Natur aus nicht idempotent ist. Das Hinzufügen von Artikeln zu einem Warenkorb ist nicht natürlich idempotent - jede Wiederholung würde weitere Artikel hinzufügen.
// Natürlich idempotent - Status-Update
public void handleOrderShipped(OrderShippedEvent event) {
Order order = orderRepository.findById(event.getOrderId());
order.setStatus(OrderStatus.SHIPPED); // Wiederholung ändert nichts
order.setShippedAt(event.getTimestamp());
orderRepository.save(order);
}
// Nicht idempotent - Geld-Transfer
public void handlePaymentReceived(PaymentReceivedEvent event) {
Account account = accountRepository.findById(event.getAccountId());
account.addBalance(event.getAmount()); // Problem: Wiederholung addiert erneut!
accountRepository.save(account);
}Event-ID-basierte Idempotenz ist der Standard-Ansatz für künstliche Idempotenz. Jedes Event erhält eine eindeutige ID, und Consumer tracken bereits verarbeitete Event-IDs. Vor der Verarbeitung wird geprüft, ob die Event-ID bereits bekannt ist.
Geschäftslogik-basierte Idempotenz nutzt natürliche Geschäftsidentifikatoren. Anstatt Event-IDs zu tracken, wird geprüft, ob das gewünschte Geschäftsergebnis bereits erreicht wurde. Eine Zahlung mit einer bestimmten Transaktions-ID wird nur einmal verarbeitet, unabhängig davon, wie oft das entsprechende Event ankommt.
Zeitbasierte Idempotenz berücksichtigt, dass manche Operationen nur für einen bestimmten Zeitraum idempotent sein müssen. Ein “täglich um 9 Uhr versendeter Newsletter” sollte pro Tag nur einmal versendet werden, aber dasselbe Event am nächsten Tag ist legitim.
Duplikaterkennung kann auf verschiedenen Ebenen implementiert werden, jeweils mit unterschiedlichen Vor- und Nachteilen. Die Wahl der Strategie beeinflusst Performance, Speicherverbrauch und Zuverlässigkeit erheblich.
In-Memory Deduplication ist die schnellste Methode. Bereits verarbeitete Event-IDs werden in einem Hash-Set oder einer ähnlichen Datenstruktur gespeichert. Der Lookup ist extrem schnell, aber der Speicher ist begrenzt und geht bei System-Restarts verloren.
Diese Strategie eignet sich für High-Frequency Events mit kurzen Zeitfenstern. Ein Chat-System könnte beispielsweise die letzten 10.000 Message-IDs in-memory halten, um kurzfristige Duplikate zu erkennen.
Database-basierte Deduplication persistiert verarbeitete Event-IDs in einer Datenbank. Dies bietet vollständige Persistenz und unbegrenzte Kapazität, verursacht aber bei jedem Event einen Database-Lookup.
| Strategie | Performance | Persistenz | Kapazität | Anwendungsfall |
|---|---|---|---|---|
| In-Memory | Sehr hoch | Nein | Begrenzt | High-frequency, kurze Zeitfenster |
| Database | Mittel | Ja | Unbegrenzt | Business-kritische Events |
| Distributed Cache | Hoch | Konfigurierbar | Hoch | Skalierte Services |
| Hybrid | Hoch | Ja | Hoch | Beste Performance + Sicherheit |
Distributed Cache Deduplication nutzt Systeme wie Redis oder Hazelcast. Dies kombiniert hohe Performance mit Persistenz-Optionen und Skalierbarkeit. Mehrere Service-Instanzen können denselben Cache teilen.
Hybrid-Ansätze kombinieren mehrere Strategien. Häufig wird ein schneller In-Memory-Cache mit einem persistenten Backup kombiniert. Der Cache dient der Performance, die Datenbank der Sicherheit.
Time-Window Deduplication beschränkt die Duplikaterkennung auf ein Zeitfenster. Anstatt alle Event-IDs seit Systembeginn zu speichern, werden nur Events der letzten Stunden oder Tage betrachtet. Dies reduziert Speicherverbrauch erheblich.
Business-Key Deduplication nutzt natürliche Geschäftsschlüssel anstatt technischer Event-IDs. Eine Bestellung mit derselben Bestellnummer wird nur einmal verarbeitet, unabhängig davon, wie viele Events dafür generiert wurden.
Probabilistic Deduplication mit Bloom Filters kann bei enormen Event-Mengen eingesetzt werden. Bloom Filter können mit hoher Wahrscheinlichkeit feststellen, ob ein Event bereits verarbeitet wurde, benötigen aber deutlich weniger Speicher als vollständige Hash-Sets.
Event-Systeme müssen verschiedene Konsistenz-Garantien bieten, je nach Geschäftsanforderungen. Diese Garantien bestimmen, welche Zusicherungen das System gegenüber Event-Verarbeitung und Duplikaten geben kann.
At-Most-Once garantiert, dass Events maximal einmal verarbeitet werden. Duplikate werden vollständig vermieden, aber Events können verloren gehen. Dies eignet sich für nicht-kritische Events wie Logging oder Monitoring.
At-Least-Once garantiert, dass Events mindestens einmal verarbeitet werden. Events gehen nie verloren, aber Duplikate sind möglich. Dies ist der Standard in den meisten Event-Systemen und erfordert idempotente Consumer.
Exactly-Once ist das heilige Gral der Event-Verarbeitung: Events werden garantiert genau einmal verarbeitet. Dies ist technisch sehr schwer zu erreichen und in reinen verteilten Systemen theoretisch unmöglich. Praktische “Exactly-Once”-Implementierungen sind meist “Effectively-Once” - sie erscheinen von außen wie Exactly-Once.
| Guarantee | Event-Verlust | Duplikate | Performance | Implementierungsaufwand |
|---|---|---|---|---|
| At-Most-Once | Möglich | Unmöglich | Hoch | Niedrig |
| At-Least-Once | Unmöglich | Möglich | Hoch | Mittel |
| Exactly-Once | Unmöglich | Unmöglich | Niedrig | Sehr hoch |
Transactional Guarantees erweitern die Konsistenz auf die gesamte Event-Verarbeitung. Eine transactionale Verarbeitung stellt sicher, dass entweder alle Seiteneffekte eines Events auftreten oder gar keine.
In einem Bestellprozess bedeutet das: Wenn ein
OrderPlaced-Event verarbeitet wird, müssen
alle resultierenden Aktionen (Lager-Reservierung,
Payment-Request, E-Mail-Versand) erfolgreich sein oder das gesamte Event
wird zurückgerollt.
Compensation-basierte Konsistenz akzeptiert, dass nicht alle Operationen transactional sein können, implementiert aber Kompensations-Mechanismen. Wenn eine Operation fehlschlägt, werden bereits ausgeführte Schritte durch Gegenbuchungen rückgängig gemacht.
Event-Sourcing Konsistenz nutzt die Event-Geschichte als Single Source of Truth. Inkonsistenzen können durch Event-Replay korrigiert werden, da die vollständige Historie verfügbar ist.
Die Wahl der Konsistenz-Strategie ist eine Geschäftsentscheidung. Finanzielle Transaktionen erfordern meist starke Konsistenz-Garantien, während Analytics oder Monitoring mit schwächeren Garantien auskommen können.
Monitoring von Duplikaten ist essentiell für den Betrieb. Systeme müssen messen können, wie häufig Duplikate auftreten, welche Services betroffen sind und ob die Deduplication korrekt funktioniert. Ein plötzlicher Anstieg von Duplikaten kann auf Infrastructure-Probleme hinweisen.
Idempotenz und Duplikaterkennung sind keine technischen Details, sondern fundamentale Designprinzipien für zuverlässige Event-getriebene Systeme. Sie ermöglichen es, robuste Services zu bauen, die auch bei Infrastruktur-Problemen korrekt funktionieren und konsistente Geschäftsergebnisse liefern.