Die Fehlermodi von Streaming-Systemen sind leise: Duplikate, heiße Partitionen, nachhinkende Consumer. Design-Entscheidungen, die das verhindern, vom Partition-Key bis zum replay-sicheren Consumer.

Ich behandle jedes Kafka-Topic als Vertrag zwischen dem, der darauf produziert, und dem, der davon konsumiert, nicht nur als Namen in einer Config-Datei. Namenskonventionen sind wichtig: Der Name eines Topics sollte verraten, welches Domain-Event es trägt und wem es gehört, nicht nur, welcher Service heute zufällig darauf schreibt. Schema-Evolution muss eine explizite, versionierte Entscheidung sein statt etwas, das implizit passiert, weil ein Producer ein Feld hinzugefügt hat. Eine Schema-Registry mit zur Schreibzeit durchgesetzten Kompatibilitätsregeln fängt das ab, bevor es zur Laufzeit-Exception eines Consumers wird. Retention ist die dritte Entscheidung, die ich für jedes Topic bewusst treffe: wie lange Events leben müssen und ob dieses Topic ein transienter Message-Bus oder ein dauerhaftes Log ist, das nachgelagerte Systeme von Anfang an replayen könnten. Keine davon akzeptiere ich als Default. Es sind Entscheidungen, die einmal im Voraus getroffen werden, denn sie später zu ändern bedeutet, jeden Producer und Consumer gleichzeitig zu koordinieren.
Partitionsanzahl und die Wahl des Partition-Keys sind der Kern der Design-Arbeit, denn sie bestimmen Ordering-Garantien und wie gleichmäßig sich Last auf Consumer verteilt. Kafka garantiert Ordering nur innerhalb einer Partition, daher brauchen Events, die relativ zueinander in Reihenfolge verarbeitet werden müssen, denselben Partition-Key, typischerweise eine Entity-ID. Wählt man den Key falsch, verliert man entweder benötigtes Ordering oder, häufiger, erzeugt eine heiße Partition, bei der das Volumen eines Keys den Rest überschattet und ein einzelner Consumer zum Flaschenhals für das gesamte Topic wird. Ich dimensioniere Consumer-Groups bewusst nach Partitionsanzahl, denn eine Group kann nie mehr aktive Consumer haben als Partitionen, und zusätzliche Consumer sitzen einfach untätig herum.
At-least-once-Zustellung ist der praktische Standard für Kafka, was Duplikate über einen ausreichend langen Zeitraum zur Gewissheit macht, sei es durch Producer-Retries oder Consumer-Rebalances. Ich entwerfe Consumer idempotent, statt zu versuchen, Exactly-once-Zustellung auf Broker-Ebene zu garantieren: Jeder Handler erzeugt denselben Endzustand, egal ob ein Event einmal oder zweimal verarbeitet wird, üblicherweise über einen Idempotenz-Key, der vor einem Seiteneffekt gegen einen Store geprüft wird. Genau diese Eigenschaft macht auch Replay sicher: Muss ich ein Topic je von einem früheren Offset neu verarbeiten, um einen Bug zu beheben, machen idempotente Consumer daraus ein Nicht-Ereignis statt eines Risikos für Datenkorruption.
Im Tagesgeschäft beobachte ich zwei Hebel: Retention und Consumer-Lag. Die Retention-Strategie (zeitbasiert, größenbasiert oder komprimiert für Topics, die aktuellen Zustand statt eines Event-Logs repräsentieren) wirkt sich direkt auf Speicherkosten und darauf aus, wie weit ein Replay zurückreichen kann. Consumer-Lag ist das mit Abstand nützlichste Gesundheitssignal, das ein Streaming-System liefert: Eine Consumer-Group, die hinter ihre produzierten Offsets zurückfällt, ist die früheste Warnung vor einem nachgelagerten Engpass, lange bevor von außen irgendetwas kaputt aussieht.