Wie eine einzige fehlerhafte Nachricht die Pricing-Pipeline lahmlegte
von Sergej Subkov
Unsere Pricing-Pipeline verarbeitete plötzlich keine neuen Daten mehr. Der Consumer Lag stieg, nachgelagerte Systeme arbeiteten mit veralteten Preisen, und jeder Neustart brachte exakt dasselbe Ergebnis: Die Anwendung lief an, stürzte ab und blieb wieder stehen.
Die Ursache war keine komplexe Infrastrukturstörung. Es war eine einzige fehlerhafte JSON-Nachricht.
Ein Record blockierte die gesamte Partition
Unsere Kafka-Streams-Anwendung verarbeitete Preis- und Artikeldaten im E-Commerce-Umfeld. Innerhalb einer Kafka-Partition werden Nachrichten in Reihenfolge gelesen. Genau diese Garantie wurde uns in diesem Moment zum Verhängnis.
Der Deserialisierer konnte einen Record nicht lesen, weil zwei JSON-Objekte fehlerhaft aneinandergehängt worden waren. Die Verarbeitung brach ab. Nach dem Neustart traf die Anwendung erneut auf denselben Offset und scheiterte wieder.
Alle nachfolgenden Nachrichten dieser Partition blieben blockiert.
Für genau dieses Muster gibt es in der Kafka-Welt einen eigenen Begriff: Poison Pill — eine Nachricht, die ein Consumer nicht verarbeiten kann und an der er bei jedem neuen Versuch erneut scheitert.
Ein kleiner Fehler im Producer hatte damit eine geschäftskritische Pipeline faktisch zum Stillstand gebracht.
Die akute Lösung
Der Stacktrace zeigte uns Topic, Partition und Offset. Mit dem kafka-console-consumer lasen wir den betroffenen Record direkt aus Kafka.
Die Rohdaten bestätigten den Verdacht sofort: Das JSON war ungültig und konnte auf Consumer-Seite nicht sinnvoll repariert werden.
Zwei parallel Aufgaben ließen sich daraus folgern:
- Wir sicherten den fehlerhaften Record und übersprangen ihn kontrolliert, damit die Pipeline ihren Rückstand aufholen konnte.
- Wir behoben den Fehler im Producer, damit keine weiteren ungültigen Nachrichten erzeugt wurden.
Wenige Stunden später war der Consumer Lag wieder weg.
Doch damit war nur der Vorfall gelöst, nicht die zugrunde liegende Fehlerklasse.
Die eigentliche Lösung: eine Dead Letter Queue
Uns war klar: Fehlerhafte Nachrichten lassen sich in einer verteilten Systemlandschaft nie vollständig verhindern.
Producer ändern sich. Schemas entwickeln sich weiter. Teams deployen unabhängig voneinander. Irgendwann wird die nächste Poison Pill eintreffen — ein Record, den ein Consumer nicht verarbeiten kann.
Die entscheidende Frage war deshalb nicht mehr:
Wie verhindern wir jede fehlerhafte Nachricht?
Sondern:
Wie verhindern wir, dass eine fehlerhafte Nachricht die gesamte Verarbeitung stoppt?
Damit verschob sich der Fokus von Fehlervermeidung hin zu Resilienz: Die Pipeline sollte mit fehlerhaften Daten rechnen und trotzdem weiterlaufen.
Unsere Antwort war eine Dead Letter Queue.
Bei Deserialisierungsfehlern fing ein eigener DeserializationExceptionHandler den fehlerhaften Record ab. Statt die Verarbeitung zu beenden, schrieb er die Rohdaten zusammen mit Topic, Partition, Offset und Fehlerdetails in ein separates Fehler-Topic.
Danach konnte Kafka Streams mit dem nächsten Record fortfahren.
properties.put(
StreamsConfig.DEFAULT_DESERIALIZATION_EXCEPTION_HANDLER_CLASS_CONFIG,
DlqDeserializationExceptionHandler.class
);Das Ziel war nicht, Fehler zu verschlucken. Im Gegenteil: Sie sollten sichtbar und behandelbar werden, ohne gleichzeitig den gesamten Datenfluss zu blockieren.
Was sich danach änderte
Seit Einführung der Dead Letter Queue führte ein einzelner ungültiger Record nicht mehr automatisch zu einem vollständigen Pipeline-Stillstand.
Die fehlerhafte Nachricht landete im Fehler-Topic. Dort konnten wir sofort sehen:
- aus welchem Topic sie kam,
- in welcher Partition sie lag,
- an welchem Offset sie verarbeitet wurde,
- und warum sie fehlgeschlagen war.
Das verkürzte die Diagnose erheblich. Statt nachts Logs zu durchsuchen und Records manuell aus Kafka zu lesen, hatten wir alle relevanten Informationen an einem Ort.
Auch die Zusammenarbeit mit Producer-Teams wurde einfacher. Wir mussten nicht mehr vermuten, welche Änderung den Fehler verursacht hatte. Wir konnten den konkreten Record und die konkrete Exception zeigen.
Die wichtigste Lektion
Eine Dead Letter Queue ist kein Freifahrtschein, fehlerhafte Daten einfach zu ignorieren.
Gerade bei Pricing kann ein übersprungener Record bedeuten, dass ein Produkt vorübergehend mit einem veralteten Preis weiterläuft. Deshalb braucht es Alerts, klare Zuständigkeiten und einen Prozess für die spätere Wiederverarbeitung.
Aber eine einzelne kaputte Nachricht darf nicht zusätzlich alle nachfolgenden gültigen Preisänderungen blockieren.
Genau das war für uns der entscheidende Unterschied.
Der kurzfristige Fix brachte die Pipeline wieder zum Laufen.
Die Dead Letter Queue sorgte dafür, dass derselbe Fehlertyp nie wieder die gesamte Verarbeitung lahmlegen konnte.
Fazit
Eine einzige fehlerhafte Nachricht hatte ausgereicht, um eine geschäftskritische Kafka-Streams-Pipeline zu stoppen.
Die nachhaltige Lösung bestand nicht darin, auf fehlerfreie Daten zu hoffen. Sie bestand darin, den Fehlerfall bewusst zu gestalten.
Seitdem werden problematische Records isoliert, analysiert und später nachbearbeitet, während gültige Nachrichten weiterfließen.
Gute Infrastruktur verhindert nicht jeden Fehler.
Sie sorgt dafür, dass ein kleiner Fehler nicht zum großen Ausfall wird.