Apache Kafka Clients richtig konfigurieren

Die Standardkonfigurationen von Kafka reichen für die Entwicklung, aber in der Produktion sind sie eine Schwachstelle. Dieser Leitfaden deckt die wesentlichen Einstellungen für Zuverlässigkeit, Performance und Betriebsstabilität ab.

Apache Kafka ist eine leistungsstarke und stabile Plattform. Allerdings sind viele der „Out-of-the-Box“-Einstellungen nicht wirklich eine solide Grundlage für die Produktion. Viele dieser Standardeinstellungen sind das Ergebnis historischer Entscheidungen und wurden aus Gründen der Abwärtskompatibilität beibehalten. In diesem Artikel geben wir dir eine Reihe von Empfehlungen, die auf unserer umfangreichen Erfahrung basieren.

Dieser Leitfaden handelt nicht von Hyper-Optimierung für Konzerne wie LinkedIn oder Netflix, bei denen Millionen von Nachrichten pro Sekunde die Norm sind. Es geht um eine Handvoll entscheidender Konfigurationsänderungen, die deinen Kafka-Cluster in Standard-Umgebungen zuverlässiger, robuster und einfacher zu betreiben machen.

Producer-Konfiguration

Mit diesen Konfigurationseinstellungen sorgst du dafür, dass deine Producer Daten zuverlässig und performant senden.

  • acks=all: (Standard seit ~Kafka 3.4) Stellt sicher, dass der Producer wartet, bis die Nachricht in alle In-Sync-Replicas geschrieben wurde.

  • enable.idempotence=true: (Standard seit ~Kafka 3.4) Stellt sicher, dass Nachrichten genau einmal gesendet werden. Überlebt sogar Broker-Neustarts.

enable.idempotence=true garantiert dir, dass die vom Producer gesendeten Nachrichten genau einmal und in der richtigen Reihenfolge übermittelt werden. Es garantiert jedoch nicht, dass ein Consumer die Nachricht genau einmal verarbeitet. Dafür musst du Transaktionen verwenden.

  • delivery.timeout.ms: (Standard: 120000 (2 Minuten)) Der Producer wird für diese Zeitspanne versuchen, die Nachricht zu senden. Nach diesem Timeout gibt der Producer auf und wirft einen Fehler.

Wenn du nach dem Timeout einfach in deinem eigenen Code einen erneuten Versuch startest, wäre es besser, wenn du einfach die delivery.timeout.ms erhöhst. Lies mehr über Zuverlässigkeit in diesem Artikel über zuverlässige Producer.

  • client.id={hostname}: (Standard: automatisch generiert) Setze diese Einstellung für besseres Monitoring und besseres Logging. Wenn du Kubernetes verwendest, setze es auf den Hostnamen des Pods.

Durchsatz-Optimierung

Für die meisten „Medium Data“-Anwendungen kannst du einen erheblichen Performance-Schub erzielen, indem du deine Nachrichten batchst und komprimierst, ohne (zu viel) Latenz zu opfern.

  • batch.size= z.B. 900000: (Standard: 16KiB) Dieser Wert ist für die meisten Anwendungsfälle zu klein. Erhöhe ihn auf bis zu 1 MB. Dies ermöglicht es deinem Producer, mehr Daten in einer einzigen Netzwerkanfrage zu senden, was den Overhead reduziert und den Durchsatz verbessert.

  • linger.ms=5, 10 oder 100: (Standard: 0ms) Diese Einstellung arbeitet Hand in Hand mit batch.size. Sie weist den Producer an, bis zu einer bestimmten Zeit (Standard: 0 ms) zu warten, bis sich ein Batch gefüllt hat, bevor er es sendet. Eine Erhöhung auf wenige Millisekunden kann den Durchsatz erheblich verbessern, ohne die Latenz stark zu beeinträchtigen.

  • compression.type=lz4 oder zstd: (Standard: none) Das Komprimieren deiner Nachrichten, bevor sie an Kafka gesendet werden, kann die Netzwerkbandbreite sparen und den Durchsatz verbessern. Mit Algorithmen wie LZ4 oder Zstd kannst du hervorragende Ergebnisse erzielen.

Weitere Einstellungen

  • transactional.id={hostname}: Diese Einstellung wird nur benötigt, wenn du Producer-Transaktionen verwendest. Die transactional.id muss für jede Producer-Instanz eindeutig, aber über Neustarts hinweg persistent sein. Wenn eine Producer-Instanz neu startet, verwendet Kafka diese ID, um zu verhindern, dass „Zombie“-Producer Transaktionen committen. Nutze in Kubernetes StatefulSets für transaktionale Producer.

In Kubernetes solltest du StatefulSets für deine transaktionalen Producer verwenden.

  • partitioner=murmur2_random: (Standard: Murmur2Partitioner in Java, consistent_random in librdkafka) Der Partitioner bestimmt, an welche Partition eine Nachricht gesendet wird. Wenn du sowohl Java- als auch Nicht-Java-Producer verwendest, stelle bitte sicher, dass alle Producer den Partitioner auf murmur2_random setzen, um eine konsistente Partitionierung zu gewährleisten.

Wenn du sowohl Java- als auch Nicht-Java-Producer verwendest, stelle bitte sicher, dass alle Nicht-Java-Producer den Partitioner auf murmur2_random setzen, um eine konsistente Partitionierung zu gewährleisten.

Consumer-Konfiguration

Mit diesen Konfigurationseinstellungen bekommst du zuverlässige und performante Consumer für 99% der Anwendungsfälle.

  • group.id={service-name}: Alle Consumer, die zur selben group.id gehören, arbeiten als eine Einheit zusammen, um Nachrichten von einem Topic zu konsumieren. Verwende einen klaren, beschreibenden Namen für deine Consumer-Gruppe, z. B. den Namen deines Dienstes.

  • client.id={hostname}: Ähnlich wie beim Producer ist es auch beim Consumer wichtig, für Monitoring und Debugging eine aussagekräftige ID zu vergeben.

  • isolation.level=read_committed: (Standard: read_uncommitted) Stellt sicher, dass dein Consumer-Code niemals Nachrichten aus nicht abgeschlossenen oder abgebrochenen Transaktionen sieht. Es ist eine Best Practice, diese Einstellung standardmäßig auf allen Consumern zu setzen, auch wenn du derzeit keine Transaktionen verwendest.

Wenn dein Producer transaktional ist und dein Consumer mit isolation.level=read_uncommitted konfiguriert ist, kann dies zu schwerwiegenden Datenintegritätsproblemen führen, da Daten aus fehlgeschlagenen Transaktionen gelesen werden. Verwende immer read_committed.

  • auto.offset.reset=earliest: Diese Einstellung bestimmt, was zu tun ist, wenn ein Consumer startet und keinen gültigen Offset für eine Topic-Partition findet. Wenn du möchtest, dass deine Consumer alle Nachrichten von Anfang an lesen, setze es auf earliest. Wenn du nicht alle Nachrichten lesen möchtest und es in Ordnung ist, einige zu verpassen, setze es auf latest.

  • enable.auto.commit=true: (Standard: true) Handhabt automatische Offset-Commits. Deaktiviere dies nur, wenn du genau weißt, was du tust (zum Beispiel wenn du Offsets innerhalb einer Transaktion händisch committest).

  • commit.interval=5000: (Standard: 5000) Diese Einstellung bestimmt, wie oft der Offset eines Consumers an Kafka committet wird. Der Standardwert beträgt 5 Sekunden, was bedeutet, dass im Falle eines Absturzes höchstens 5 Sekunden Daten erneut verarbeitet werden. Du solltest dies nur ändern, wenn du einen sehr guten Grund dazu hast.

Wenn du eine exactly-once Verarbeitung benötigst, musst du entweder eine Consume-Process-Produce-Schleife mit Transaktionen verwenden oder Kafka Streams mit der Einstellung processing.guarantee=exactly_once_v2.

Rebalances in Kubernetes vermeiden und Kosteneffizienz steigern

Rebalances finden statt, wenn Consumer einer Consumer-Gruppe beitreten oder sie verlassen. In automatisierten Infrastrukturen wie Kubernetes werden Ausfälle von Pods von der Infrastruktur übernommen, und wir können teure Rebalances in Kafka vermeiden.

  • group.instance.id={hostname}: Stellt sicher, dass ein Consumer sich nach einem Neustart an sein vorheriges Assignment "erinnert" und somit teure Rebalances vermieden werden. Aber: Der Neustart muss innerhalb der session.timeout.ms stattfinden.

  • session.timeout.ms= z.B. 60000: (Standard: 30000) Diese Einstellung definiert die maximale Zeit, die ein Consumer nicht verfügbar sein darf, bevor er als ausgefallen betrachtet wird und die Consumer-Gruppe rebalanced wird. Erhöhe diesen Wert auf einen Wert, bei dem ein Consumer seinen Ausfall leicht erkennen, herunterfahren und den Pod neu starten kann. Konfiguriere die Einstellung nur zusammen mit group.instance.id.

group.instance.id und session.timeout.ms sollten zusammen verwendet werden und ergeben nur Sinn, wenn du ein StatefulSet für deine Consumer in Kubernetes verwendest, damit die Identität des Pods den Neustart überlebt.

  • group.protocol=consumer: (Standard: classic) Verwende die neue Generation des Consumer-Rebalance-Protokolls (lies KIP-848 für weitere Informationen). Erfordert Kafka 4.0 oder höher. Macht das Rebalancing viel schneller!

Wenn du eine Kafka-Version älter als 4.0 verwendest, kannst du das neue Consumer-Rebalance-Protokoll nicht nutzen. Dann musst du das alte classic-Protokoll verwenden. Aber um die Rebalancing-Geschwindigkeit zu verbessern, kannst du die Einstellung partition.assignment.strategy=CooperativeStickyAssignor setzen: (Standard: RangeAssignor, CooperativeStickyAssignor)

  • client.rack={dc-name}: Wenn du deinen Kafka-Cluster und deine Clients in einer Cloud-Umgebung mit mehreren Verfügbarkeitszonen (AZ) betreibst, kannst du die client.rack sowohl auf den Brokern als auch auf den Consumern festlegen. Dies ermöglicht es dem Consumer, es zu bevorzugen, von einer Replica in derselben AZ zu lesen, was die Datenübertragungskosten zwischen den AZs erheblich senken kann.

Topic-Konfiguration

Hier findest du die Grundkonfiguration für deine Topics.

Zuverlässigkeit und Performance

  • num partitions: Die Anzahl der Partitionen wirkt sich direkt auf die Parallelität und Skalierbarkeit deines Topics aus. Die Faustregel lautet, mindestens so viele Partitionen wie Mitglieder deiner Consumer-Gruppe zu haben. Für eine detailliertere Anleitung schaue dir unseren Artikel zu diesem Thema an.

  • replication-factor=3: (Standard: 1) Die Anzahl, wie oft die Daten einer Partition über Broker hinweg repliziert werden. 3 ist der Goldstandard für die meisten Produktionsumgebungen, da er eine hohe Verfügbarkeit zu vernünftigen Kosten bietet.

  • min.insync.replicas=2: (Standard: 1) Wie viele In-Sync-Replicas verfügbar sein müssen, damit Producer in eine Partition schreiben können. Bei einem Replikationsfaktor von 3 bedeutet ein Wert von 2, dass ein Broker ausfallen kann, ohne die Produktion zu beeinträchtigen, und ein weiterer ausfallen kann, ohne Daten zu verlieren.

Datenaufbewahrung und Lebenszyklus

  • cleanup.policy: (Standard: delete) Dies definiert, wie Kafka mit alten Nachrichten umgeht. Der Standardwert ist delete, was bedeutet, dass Nachrichten nach einer festgelegten Zeit gelöscht werden. Für Anwendungsfälle wie Change Data Capture oder Event Sourcing könntest du compact verwenden, was alte Nachrichten für denselben Key entfernt.

  • retention.hours: (Standard: 168 Stunden (7 Tage)) Diese Einstellung steuert, wie lange Kafka Nachrichten aufbewahrt, bevor sie gelöscht werden. Der Standardwert beträgt 7 Tage, aber du solltest diesen basierend auf deinen Business-Anforderungen anpassen.

Fazit

Leider sind in Kafka die Standardeinstellungen oft nicht „gut genug“ für die Produktion. Ich hoffe, dieser Artikel hat dir geholfen, die richtigen Einstellungen für deine Produktionsumgebung zu finden. Wenn du Fragen hast, kannst du uns jederzeit kontaktieren.

Über Anatoly Zelenin
Hallo, ich bin Anatoly! Ich liebe es, bei Menschen das Funkeln in den Augen zu wecken. Als Apache Kafka Experte und Buchautor bringe ich seit über einem Jahrzehnt IT zum Leben - mit Leidenschaft statt Langeweile, mit Erlebnissen statt endlosen Folien.

Weiterlesen

article-image
Kafka Training: Die 6 wichtigsten Leitprinzipien meiner Trainings

Wann bin ich ein guter Trainer? Das ist für mich vielleicht die wichtigste Frage in meiner Rolle als Apache-Kafka-Experte. In diesem Blog will ich sie mit sechs Leitprinzipien beantworten. Stimmst du mir bei allen zu?

Mehr lesen
article-image
Was ist Apache Kafka?

Was hat es eigentlich überhaupt mit Apache Kafka auf sich? Diesem Programm, das quasi jeder namhafte (deutsche) Automobilhersteller einsetzt? Diese Software, dank derer wir mit unseren Schulungen und Workshops kreuz und quer durch Europa gereist sind und viele unterschiedliche Branchen ‒ von Banken, Versicherungen, Logistik-Dienstleistern, Internet-Start-ups, Einzelhandelsketten bis hin zu Strafverfolgungsbehörden ‒ kennengelernt haben? Warum setzen so viele unterschiedliche Unternehmen Apache Kafka ein?

Mehr lesen