
In einem verteilten System wie Apache Kafka ist das "Fire-and-Forget"-Prinzip beim Senden von Daten ein Rezept für Datenverlust und inkonsistente Zustände. Wenn ein Producer eine kritische Nachricht – sei es eine Finanztransaktion, ein Sensorwert einer Windkraftanlage oder eine wichtige Logistik-Information – sendet, musst du als Entwickler oder Architekt garantieren, dass diese Nachricht den Cluster sicher und unveränderlich erreicht.
Ohne die richtigen Konfigurationen kann ein Netzwerkaussetzer oder der Ausfall eines Brokers dazu führen, dass Nachrichten entweder komplett verloren gehen oder unbemerkt dupliziert werden. Das untergräbt nicht nur die Datenintegrität, sondern kann auch zu falschen Geschäftsentscheidungen und dem Verlust von Kundenvertrauen führen. In diesem Guide tauchen wir daher in die technischen Konfigurationen ein, die für eine garantierte und verlustfreie Nachrichtenübertragung unerlässlich sind.
acks=all
und IdempotenzDie erste Stellschraube für zuverlässiges Produzieren ist die Acknowledgment-Strategie (acks
). Dieser Parameter definiert, auf wie viele Bestätigungen der Producer wartet, bevor er eine Nachricht als erfolgreich gesendet betrachtet.
acks=0
acks=0
: Der Producer sendet die Nachricht und wartet auf keinerlei Bestätigung. Maximale Performance, aber hohes Risiko für Datenverlust.
acks=1
acks=1
: Der Producer wartet auf die Bestätigung des Leader-Brokers der jeweiligen Partition. Das war der Standard bis Kafka 3.4. Die Nachricht ist sicher, solange der Leader nicht genau nach der Bestätigung, aber vor der Replikation ausfällt.
acks=all
acks=all
(oder -1
): Der Producer wartet, bis der Leader und alle In-Sync Replicas (ISRs) die Nachricht erhalten haben. Dies ist der neue Standard seit Kafka 3.4. Dies bietet die höchste Garantie gegen Datenverlust.
Für maximale Zuverlässigkeit ist acks=all
also Pflicht. Doch das allein reicht nicht aus. Was passiert, wenn der Producer eine Bestätigung aufgrund eines temporären Netzwerkproblems nicht erhält und die Nachricht erneut sendet? Ohne weitere Vorkehrungen hättest du die Nachricht doppelt im Topic.
Hier kommt die Idempotenz ins Spiel. Indem du enable.idempotence=true
(Standard seit Kafka 3.4) setzt, weist der Producer jeder Nachricht eine Sequenznummer zu. Der Broker speichert die letzte Sequenznummer pro Producer und Partition und verwirft Duplikate automatisch.
acks=all
enable.idempotence=true
Mit dieser Kombination stellst du sicher, dass Nachrichten garantiert ankommen und nicht dupliziert werden.
Diese beiden Einstellungen sind die Grundlage für eine "Exactly-Once"-Semantik auf Producer-Seite.
Wir haben acks=all
gesetzt, was bedeutet, der Producer wartet auf die Bestätigung aller In-Sync Replicas. Aber was genau bedeutet "in-sync" und wie viele Replicas brauchen wir?
Ein Replica gilt als "in-sync", wenn es nicht zu weit hinter dem Leader zurückliegt. Fällt ein Broker aus oder wird zu langsam, entfernt der Controller ihn aus der ISR-Liste für die betroffenen Partitionen. acks=all
wartet also nicht auf alle konfigurierten Replicas, sondern nur auf die, die aktuell als gesund gelten. Das ist ein cleverer Mechanismus, denn sonst würde der Ausfall eines einzigen Brokers den gesamten Schreibvorgang blockieren.
Jetzt kommt die entscheidende Frage: Was passiert, wenn zu viele Broker ausfallen und nur noch der Leader übrig ist? acks=all
wäre dann mit acks=1
identisch, und wir verlieren unsere Sicherheitsgarantie.
Um dieses Szenario zu verhindern, konfigurieren wir auf Broker- oder Topic-Ebene den Parameter min.insync.replicas
. Dieser Wert legt fest, wie viele Replicas mindestens in der ISR-Liste sein müssen, damit ein Produce-Request überhaupt akzeptiert wird.
Unsere Empfehlung für Topics:
Replication Factor: 3
min.insync.replicas
: 2
Mit dieser Konfiguration kann ein Broker ausfallen, ohne dass die Produktion beeinträchtigt wird. Fallen zwei Broker aus, stoppt der Producer die Produktion für die betroffenen Partitionen, da die Bedingung min.insync.replicas=2
nicht mehr erfüllt ist. Der Broker wirft eine NotEnoughReplicasException
. Das ist genau das, was wir wollen: Lieber die Produktion anhalten, als einen potenziellen Datenverlust zu riskieren.
Achtung: Es ist selten sinnvoll die Einstellung min.insync.replicas
auf den Replication Factor zu setzen. Wenn du einen Replication Factor von 3 hast und min.insync.replicas
auf 3 setzt, kann kein einziger Broker mehr ausfallen, ohne die Produktion zu stoppen.
Selbst mit der perfekten Konfiguration kann es zu Fehlern kommen. Der Producer könnte abstürzen oder in einen Timeout laufen (delivery.timeout.ms
), bevor er die Bestätigung vom Broker erhält. Wie gehst du damit um?
Falscher Ansatz: Stumpfes Neu-Senden Einfach die Nachricht in einem Callback erneut zu senden, ist eine schlechte Idee. Die interne Retry-Logik der Kafka-Client-Bibliothek ist weitaus intelligenter und respektiert die Idempotenz-Garantie. Ein manueller Retry hebelt diese Garantien aus und führt zu Duplikaten oder Reihenfolgeproblemen.
Bessere Ideen:
Exponential Backoff: Warte nach einem Fehler eine gewisse Zeit und versuche es erneut. Verdopple die Wartezeit bei jedem weiteren Fehlschlag. Dies kann bei temporären Problemen helfen, birgt aber die Gefahr, die Reihenfolge der Nachrichten zu verletzen und ist schwer sauber zu implementieren, ohne die Exactly-Once-Garantie zu verlieren.
Nachrichten lokal auf Platte schreiben: Wenn eine Nachricht transient ist (d.h. sie existiert nur im Speicher deines Producers), kann das Persistieren auf der lokalen Festplatte eine Notfalllösung sein. Ein separater Prozess kann dann später versuchen, diese Nachrichten erneut zu senden. Dies erhöht jedoch die Komplexität erheblich.
Mein Favorit: Abstürzen lassen! (Controlled Crash)
Das klingt kontraintuitiv, ist aber oft der sauberste und robusteste Ansatz, besonders in modernen, containerisierten Umgebungen wie Kubernetes. Wenn dein Producer Daten aus einem Quellsystem (z.B. einer Datenbank) liest, verarbeitet und nach Kafka schreibt, ist der Ansatz simpel: Wenn ein nicht behebbarer Fehler beim Senden auftritt, beende den Prozess.
Warum ist das gut? Ein Orchestrierungssystem wie Kubernetes startet den Container automatisch neu. Nach dem Neustart beginnt der Producer wieder an der letzten erfolgreich verarbeiteten Position im Quellsystem. Du verlagerst die Fehlerbehandlung von komplexem Code in robuste Infrastruktur. Es ist sauber, einfach zu dokumentieren und löst im Idealfall einen Alert im Monitoring aus, sodass das Problem transparent wird.
Es ist wichtig zu verstehen, was unsere Konfiguration leistet – und was nicht.
Garantie: Solange der Producer nicht abstürzt und kein delivery.timeout.ms
auftritt, wird die Nachricht genau einmal in einer Partition gespeichert.
Limitierung: Diese Garantie gilt nur für den Producer. Sie stellt nicht sicher, dass ein Consumer die Nachricht genau einmal verarbeitet. Für eine vollständige End-to-End-Exactly-Once-Verarbeitung (z.B. consume-process-produce) sind weitere Mechanismen notwendig.
Was, wenn mehrere Nachrichten atomar geschrieben werden müssen? Wenn du mehrere Nachrichten, möglicherweise sogar in verschiedene Topics, als eine einzige, unteilbare Einheit ("all or nothing") schreiben musst, benötigst du Producer-Transaktionen. Dies ist ein fortgeschrittenes Thema, das den Rahmen dieses Artikels sprengen würde.
Für Consume-Process-Produce-Szenarien ist Kafka Streams mit der Einstellung processing.guarantee=exactly_once_v2
oft die beste und einfachste Lösung, da es Transaktionen intern verwaltet.
Zuverlässigkeit in verteilten Systemen ist kein Zufall, sondern das Ergebnis bewusster Architekturentscheidungen und sorgfältiger Konfiguration. Um sicherzustellen, dass deine Producer keine Nachrichten verlieren, hast du gelernt, die drei wichtigsten Hebel zu bedienen:
acks=all
: Warte auf die Bestätigung aller In-Sync Replicas.
enable.idempotence=true
: Verhindere Duplikate bei internen Retries.
min.insync.replicas=2
(bei Replication Factor 3): Halte das Produzieren lieber an, als Datenverlust bei zu vielen Broker-Ausfällen zu riskieren.
Für den Fehlerfall haben wir gesehen, dass ein kontrollierter Neustart durch die Infrastruktur oft die sauberste Lösung ist. Denk daran, dass diese Garantien auf den Producer beschränkt sind. Für End-to-End-Szenarien sind weiterführende Konzepte wie Kafka Streams oder Producer-Transaktionen der nächste logische Schritt.
Offizielle Kafka-Dokumentation zum Producer: Producer Configs
Unser Deep Dive zu Transaktionen (demnächst hier im Knowledge Hub)
Unser Apache Kafka Buch für ein umfassendes Verständnis aller Konzepte.