Aus Linux-Magazin 12/2020

Kafka: Producer und Consumer skalieren

© Dmitry Kalinovsky, 123RF

Kafkas Consumer und Producer schaufeln gemeinsam riesige Datenmengen von einem Edge-Cluster in ein zentrales Data Warehouse. Jesse Yates erklärt, wie sich die beiden Helfer so optimieren lassen, dass der Prozess wie geschmiert läuft.

Ein rudimentäres Kafka-Ökosystem besteht aus drei Komponenten – Producern, Brokern und Consumern. Während es zahlreiche Texte über das Optimieren von Brokern gibt, gilt das zuverlässige Einrichten von Producern und Consumern als eine Art dunkle Magie.

Zwar hängen alle Systeme von lokalen Konfigurationen ab; dennoch gibt es einige Standardmetriken, auf die Sie schauen, und Knöpfe, an denen Sie drehen können. Das beschert unter Umständen bis zu zehnfache Performance-Zuwächse. Der Artikel führt durch ein an die Praxis angelehntes Beispiel, das zeigt, wie sich ein Flaschenhals in einem Stream-Processing-System erst diagnostizieren und dann beheben lässt.

Angenommen, Sie betreiben einen oder mehrere Edge-Cluster, die an geografisch verteilten Orten Daten sammeln. Die senden Sie an verschiedene Mirrors, auf denen Consumer und Producer laufen. Die Consumer lesen Daten aus den Edge-Clustern aus. Die Producer füttern die ausgelesenen Daten in einen zentralen Kafka-Cluster, wo sie dann zur Analyse in einem Data Warehouse landen (Abbildung 1).

Abbildung 1: Die in den Edge-Clustern erzeugten Daten wandern über Mirrors, auf denen Consumer und Producer sie umwandeln und in einen zentralen Kafka-Cluster einspeisen.

Abbildung 1: Die in den Edge-Clustern erzeugten Daten wandern über Mirrors, auf denen Consumer und Producer sie umwandeln und in einen zentralen Kafka-Cluster einspeisen.

In den Edge-Clustern verteilen sich viele der Topics, die die Consumer lesen sollen, auf mehr als 100 Partitionen (weil Sie in weiser Voraussicht Wachstum erwarten und zugleich ahnen, dass Partitionen generell ziemlich wenig Ressourcen brauchen). Das bedeutet, Sie spiegeln am Ende schnell mehr als 1000 Partitionen für jeden Edge-Cluster auf den zentralen Kafka-Cluster.

Bereits in diesem kleinen Datenfluss-Szenario gibt es eine Reihe von Dingen, die Sie im Falle von Problemen berücksichtigen sollten. Nicht nur müssen Sie die eingesetzten Kafka-Cluster so anpassen, dass sie mit der richtigen Anzahl an Partitionen und passenden Optimierungsparametern skalieren. Daneben müssen Sie auch die Mirrors mit Consumern und Producern skalieren und optimieren.

Es gibt bereits einige Artikel, die sich mit dem Feintuning von Kafka-Clustern beschäftigen. Der vorliegende konzentriert sich daher auf die Client-Seite, um die Producer und Consumer auf den Mirror-Servern für einen hohen Datendurchsatz, geringe Latenzen und grenzübergreifende Datenbewegungen zu optimieren. Beide Dienste laufen dabei praktischerweise jeweils auf einem Server und sind mit dem Spiegeln der Daten beschäftigt.

Edge-basierte Kafka-Cluster bringen einige nennenswerte Vorzüge mit, insbesondere dank ihrer geografischen Verteilung und den dadurch reduzierten Latenzen auf dem Weg zu den Endanwendern. Das bedeutet, Benutzerdaten lassen sich schneller in einen sicheren replizierten Store schicken, also in den zentralen Kafka-Cluster, was das Risiko eines Verlusts kritischer Daten reduziert. Damit einher geht ein Fallback für jegliche lokale Katastrophen, damit die Systeme auch bei einem Erdbeben in Kalifornien erreichbar bleiben.

Allerdings müssen die Daten noch immer an einem zentralen Ort landen, um sie zu analysieren. Das bedeutet auch, dass jede Anfrage an die Edge-Cluster über einen Mirror läuft, was eine Latenz von ungefähr 100 Millisekunden hinzufügt. Es ist zudem wahrscheinlich, dass so ein Setup nicht von vornherein reibungslos funktioniert. Wenn Sie durch die Logs blättern, stoßen Sie dann zum Beispiel auf Consumer, die sich ungewöhnlich verhalten (Listing 1) – Zeit für etwas Engineering!

Listing 1

Consumer-Log

[...]
2019-06-28 20:24:43 INFO  [KafkaMirror-7] o.a.k.c.FetchSessionHandler:438 - [Consumer clientId=consumer-1, groupId=jesseyates.kafka.mirror] Error sending fetch request (sessionId=INVALID, epoch=INITIAL) to node 3: org.apache.kafka.common.errors.DisconnectException.
[...]

Die Logfiles der Client-Anwendungen stammen zum Beispiel von Log4j (»/Pfad/zu/log4j.properties«) oder Logback (»/Pfad/zu/logback.xml«) und lassen sich auf Stdout oder in Dateien umleiten. Wenn Sie nun beim Logging das DEBUG-Level aktivieren (das klappt bei Log4j über »log4j.rootLogger=DEBUG, stdout« und bei Logback über »<root level=”DEBUG”>«), erhalten Sie zum Beispiel die Ausgabe aus Listing 2. Was die Consumer an dieser Stelle sagen möchten, ist: “Ich habe im erwarteten Zeitraum keine Antwort erhalten, also gebe ich jetzt auf und versuche es später noch einmal.”

Listing 2

DEBUG-Ausgabe

[...]
2019-06-27 20:43:06 DEBUG [KafkaMirror-11] o.a.k.c.c.i.Fetcher:244 - [Consumer clientId=consumer-1, groupId=jesseyates.kafka.mirror] Fetch READ_UNCOMMITTED at offset 26974 for partition source_topic-7 returned fetch data (error=NONE, highWaterMark=26974, lastStableOffset = -1, logStartOffset = 0, abortedTransactions = null, recordsSizeInBytes=0)
[...]

Consumer optimieren

Es gibt nun verschiedene Möglichkeiten, Abhilfe zu schaffen. Dabei hilft ein Blick auf die Konfigurationseinstellungen. Sie bringen mehrere Parameter mit, an denen Sie drehen können [1].

Die dabei anfallenden Metriken auf der Client-Seite (sowohl bei den Consumern als auch bei den Producern) liegen als MBeans (siehe Kasten “MBeans”) vor. Sie tragen zum Beispiel das Präfix »kafka.consumer:type=consumer-fetch-manager-metrics,client-id=([-.w]+)« oder am Ende ein »topic=([-.w]+)« für Topic-basierte Metriken.

MBeans

Über MBeans trackt Java Metriken, die es dann über JMX [4] anbietet. Werkzeuge zum Sammeln von Metriken wie Prometheus’ »jmx_exporter« [5] bereiten die Daten dann auf und stellen sie auch externen Metriksystemen zur Verfügung. Es ist aber auch möglich, sich über gewöhnliche Client-Tools aus dem JDK, etwa JConsole [6], mit einer laufenden Anwendung zu verbinden (wenn diese eine passende JMX-Konfiguration anbietet), um sich MBeans über JMX manuell anzusehen. Über die wichtigsten MBeans klärt ein Confluent-Dokument [7] auf.

Zu den ersten Adressen für Optimierer zählt die Option »default.api.timeout.ms«, die in alten Client-Versionen (vor Version 2.0) sämtliche Connection-Timeouts kontrollierte. Es genügt womöglich schon, diesen Wert zu erhöhen.

Eine weitere veränderbare Option wäre »session.timeout.ms«. Tatsächlich starten Broker ein Rebalancing, wenn sich die Consumer in einem bestimmten Zeitrahmen nicht melden. Den Zeitraum bis zum Start des Rebalancings legt »session.timeout.ms« fest.

Ein vermehrtes Rebalancing erkennen Sie, indem Sie die Zugangsrate (»join-rate«) unter den Consumern studieren, denn solche Joins finden in der Regel vor Rebalancing-Aktionen statt. Liegt die Join-Rate sehr hoch, lässt das auf viele Rebalancing-Aktionen schließen. In diesem Fall kann es helfen, den Timeout-Wert zu erhöhen.

Die Option »request.timeout.ms« bestimmt (ab Version 2.0), wie lange ein Consumer auf die Beantwortung seiner Anfragen wartet. Logs sind ein guter Ort, um zu prüfen, ob es viele gescheiterte Versuche gibt, eine Antwort zu erhalten. Zu den Metriken, die sich in diesem Zusammenhang beobachten lassen, gehört auf dem Broker der in der ersten Zeile von Listing 3 gezeigte Log-Eintrag zum Fetch-Status. Für die Consumer verraten die Parameter »fetch-latency-avg« und »fetch-latency-max«, welche Latenzen beim Abholen der Daten entstehen.

Listing 3

Broker: Gescheiterte Fetch Requests und das “Fegefeuer”

kafka.server:type=BrokerTopicMetrics,name=FailedFetchRequestsPerSec
[...]
kafka.server:type=DelayedOperationPurgatory,delayedOperation=Fetch,name=PurgatorySize

Der Eintrag »fetch.max.wait.ms« regelt dagegen, wie lange der Server Daten sammelt, bevor er eine Antwort zurückliefert, wenn »fetch.min.bytes« (siehe nächster Punkt) noch nicht erfüllt ist. Auch hier geben wieder konkrete Metriken weitere Hinweise. Auf dem Broker signalisiert erneut der Eintrag aus der ersten Zeile von Listing 3 die gescheiterten Fetch-Anfragen pro Sekunde. Der Broker-Wert aus der letzten Zeile des Listings zeigt hingegen die im “Fegefeuer” (Purgatory) wartenden Fetch-Anfragen. Auf dem Client erhellen auch hier die Werte »fetch-latency-avg« und »fetch-latency-max« die Lage.

Bleibt noch »fetch.min.bytes«: Dieser Wert bestimmt die minimale Menge an Daten, die der Server auf eine Anfrage hin zurückliefern sollte. Solange er diese Datenmenge noch nicht beisammen hat, wartet der Server bis zu »fetch.max.wait.ms«, bevor er nicht mehr blockiert.

Die Consumer-Metriken, die auf dem Client eine Rolle spielen (sowohl Consumer- als auch Topic-bezogen), sind »fetch-size-avg« and »fetch-size-max«. Sie bestimmen über die Größenverteilung in Bytes. Die Zahl an Einträgen (Records), die eine Anfrage zurückliefert, regelt schließlich »records-per-request-avg«. Auch ein Blick auf »fetch-latency-avg« und »fetch-latency-max« ist hier sinnvoll, damit an dieser Stelle keine unerwarteten Latenzen entstehen.

Diese Konfigurationen interagieren auf interessante Weise miteinander. Fordern Sie zum Beispiel den Server auf, Daten zum Erfüllen der Anfrage zu sammeln (über »fetch.max.wait.ms«) und setzen zugleich ein kurzes Timeout, sorgt das für häufigere Wiederholungsanfragen, aber potenziell auch für einen besseren Datendurchsatz und gesparte Bandbreite bei den hochfrequentierten Topics und Partitionen. Cluster-Betreiber im Cloud-Umfeld sparen so Kosten für die Bandbreite für ausgehenden Datenverkehr – und mit der Zeit erhebliche Summen an Geld.

Auch bedenkenswert: Einstellungen, die für geografisch in der Nähe gelegene Edge-Cluster funktionieren, scheitern vermutlich, sobald Sie entferntere Cluster einbinden: Dort müssen Sie mit zusätzlichen 50 bis 100 Millisekunden Roundtrip-Verzögerung rechnen. Die Standardeinstellung von 50-Millisekunden-Timeouts für das Warten auf Antworten bedeutet in diesem Fall, dass die Verbindungen überall schnell scheitern.

Traurigerweise gibt es keine magischen Parameter, die bei den oben genannten Einstellungen stets funktionieren. Vielmehr bilden diese Settings und die mit ihnen verbundenen Metriken eine gute Ausgangsbasis für Experimente und bescheren Ihnen ein besseres Verständnis, wenn Sie die Kafka-Dokumentation lesen [2].

Trial and Error

Sie haben also nun die Timeouts optimiert und sichergestellt, dass sie zumindest 1 Byte abholen. Trotzdem zeigt das Log noch Fehlermeldungen an. Das genaue Problem zu entdecken, kann eine Herausforderung sein. Weil womöglich mehr als 100 Consumer-Instanzen als Team arbeiten, kann ein einzelnes schwarzes Schaf bereits zu andauernden Rebalancing-Stürmen führen.

Vereinfachen Sie das Problem, indem Sie die Menge der Instanzen reduzieren und nur einige der Topics auswählen, die Sie spiegeln wollen. Irgendwann landen Sie wahrscheinlich bei einer Menge von Topics, mit der die Consumer plötzlich funktionieren. Hurra, Magie! Skalieren Sie das Setup dann wieder hoch, starten die Ausfälle einmal mehr. Mist!

Müll sammeln

Haben Sie einmal daran gedacht, die Garbage Collection zu überprüfen? Ich wette, Sie finden heraus, dass die Consumer eine Stop-the-World-Garbage-Collection (STW) betreiben, nahe bei oder über die Timeouts hinaus. Einer, zwei oder mehrere der Mirrors sammeln sich gerade zu Tode: Jedes Mal, wenn die Consumer die Verbindung zu den Topics auf den Edge-Clustern trennen, erzeugen sie dabei einen Haufen weiterer Objekte, die dann Druck auf die Garbage Collection ausüben.

Selbst wenn ein Mirror die Arbeit zunächst aufnimmt, kann er schnell Müllobjekte anhäufen und in einem Garbage-Collection-Strudel landen, von dem er sich nie erholt. Das ist besonders frustrierend, weil es 10 oder 20 Minuten lang so aussieht, als würde der Mirror gut laufen, bis er dann plötzlich schlagartig seine Arbeit einstellt.

Um die Müllsammlung zu bewältigen, erweist es sich dann als mehr als ausreichend, Javas Garbage Collection über folgende Kommandozeilenparameter anzupassen:

-server -XX:+UseParallelGC -XX:ParallelGCThreads=4

Zwar kommt hier nicht der beliebte G1GC zum Einsatz, aber für eine einfache Mirror-Anwendung brauchen Sie keine komplexe Garbage Collection. Die meisten Objekte sind hochgradig flüchtig, der Rest klein und sehr langlebig. Das passt tatsächlich gut zu Javas traditionellem Garbage Collector.

Unausgewogene Consumer

Consumer können zudem unausgewogen (unbalanced) sein. Dabei besitzen einige Instanzen in der Consumer-Gruppe viele Partitionen, andere gar keine. Wie kann das sein? Sollte Kafka nicht eigentlich die Partitionen gleichmäßig auf die Consumer verteilen?

Nach einem schnellen Lesen der Dokumentation könnten Sie auf die Idee kommen, dass Kafka die Partitionen der Topics ausgewogen über die Consumer hinweg verteilt. Das tut die Software auch – sofern alle Topics dieselbe Zahl an Partitionen aufweisen. Mindestens ab Kafka 2.1 aufwärts (die letzte stabile Version, die ich getestet habe) bestimmt jedoch das Topic mit der niedrigsten Anzahl an Partitionen über die Zahl der sogenannten Buckets, die Kafka dann über die Nodes verteilt.

Buckets legen fest, wie die Consumer die Partitionen eines Topics zuweisen. Gibt es für ein Topic 100 Partitionen und stehen diesen zehn Consumer-Instanzen gegenüber, erzeugt Kafka zehn Buckets mit je zehn Partitionen des Topics.

Das wird zum Problem, wenn es zum Beispiel zwei Topics gibt, wobei sich Topic A auf 10 Partitionen verteilt, während Topic B 100 Partitionen belegt. In diesem Fall würden Sie vielleicht versuchen, aus den 10 Consumern 100 zu machen, um die 110 Partitionen möglichst gleichmäßig aufzuteilen. Die Erwartung wäre, dass 90 Consumer auf eine Partition zugreifen und 10 Consumer auf zwei Partitionen (das entspräche den 110 Partitionen der Topics A und B).

Tatsächlich erzeugt Kafka aber 10 Consumer mit jeweils 11 Partitionen und 90 Consumer, die nichts zu tun haben. Das wäre dieselbe Verteilung wie am Anfang – nur, dass Kafka jetzt zusätzlich die untätigen Consumer mitverwalten muss. Auch hier hilft eine kleine Änderung in der Client-seitigen Consumer-Konfiguration (Listing 4).

Listing 4

Angepasste Consumer-Konfiguration

partitioner.class = org.apache.kafka.clients.producer.RoundRobinPartitioner

Damit verteilt die Consumer-Gruppe selbst die Partitionen im Round-Robin-Verfahren innerhalb der gesamten Consumer-Gruppe und stellt die erwartete Verteilung her. Das erlaubt es, die Last auszubalancieren und den allgemeinen Datendurchsatz zu verbessern.

Producer optimieren

Jetzt, wo die Consumer auf dem Mirror ihre Daten flink holen, ist es an der Zeit, sich der Producer-Seite des Mirrors zuzuwenden. Diese soll die abgeholten Daten so schnell wie möglich in den zentralen Kafka-Cluster schieben. Wer sie optimiert, gehört schon zu einer Minderheit der Nutzer – in der Regel arbeiten die standardmäßig eingestellten Konfigurationen gut genug für die meisten Producer.

Um mehr über die Interna der Producer zu erfahren, lohnt sich der Blick in einen Vortrag von Jiangjie Qin auf Linked.in [3]. Er erklärt nicht nur, wie Producer unter der Haube arbeiten, sondern gibt auch einige erstklassige Tuning-Empfehlungen.

Dennoch bevorzuge ich persönlich eine etwas empirischere Herangehensweise auf der Basis dessen, was die Clients in Form ihrer Metriken erzählen. Diese lassen sich zum Optimieren des persönlichen Einsatzfalls heranziehen.

Zurück zu den Wurzeln

Zunächst gilt es zu verstehen, warum Producer langsam arbeiten. Die erste Frage lautet also: “Ist es Kafka, oder liegt es an mir?” Womöglich liegt es an Kafka. Der Blick auf ein paar Kafka-Metriken kann klären, ob der Cluster glücklich oder überlastet ist.

Dazu gehört etwa die Network Handler Idle Time. Sie misst die untätige Zeit, in der ein Thread Pool keine Netzwerkanfragen beantworten muss, um sie dann an den Request-Handler-Pool weiterzuleiten. Der zugehörige Eintrag taucht in den Metriken so auf, wie in der ersten Zeile von Listing 5 gezeigt. Der Wert sollte nicht unter 60 Prozent liegen und im Durchschnitt über 80 Prozent.

Listing 5

Idle Times

kafka.network:type=SocketServer,name=NetworkProcessorAvgIdlePercent
kafka.server:type=KafkaRequestHandlerPool,name=RequestHandlerAvgIdlePercent

Ganz ähnlich verhält es sich mit der Request Handler Idle Time, die erfasst, wann der Thread-Pool Auszeiten nimmt, statt Daten auf die Festplatte zu schreiben oder deren Leseanfragen zu beantworten. Auch hier sollte die Dauer der Untätigkeit (Idle Time) nicht unter 60 Prozent liegen, sondern konstant über 70 Prozent. Das erkennen Sie über den Eintrag aus der letzten Zeile von Listing 5.

Daneben hilft mitunter auch ein Blick auf die Festplattenauslastung. Liegt sie sehr hoch, befindet sich der Cluster womöglich an der Kapazitätsgrenze und kann keine Daten mehr aufnehmen. Auch eine hohe CPU-Last deutet manchmal Probleme an.

Wenn alle Metriken passen, sollte sie nicht allzu hoch ausfallen – es sei denn, es laufen noch weitere Prozesse auf dem Server. Kafka verwendet viele Thread-Pools. Nebenprozesse lösen unter Umständen Streit um die CPU-Zeit aus und verlangsamen den Broker. Generell sei davon abgeraten, neben dem Broker weitere (insbesondere CPU-intensive) Prozesse laufen zu lassen.

Kompression und Trödelzeit

Trotz der Optimierungen an Kafka hat der Cluster (in einer glücklichen Wendung für diesen Optimierungsartikel) trotz aller bisherigen Anstrengungen weiterhin nichts zu tun. Also optimieren Sie noch die Producer selbst.

Ein naheliegender erster Startpunkt kann der »compression.type« für den Producer sein. Die Kompression auf der Produzentenseite einzuschalten, sollten Sie ernsthaft in Erwägung ziehen – insbesondere, wenn Extra-CPU-Kapazitäten zur Verfügung stehen. Die Kompression beim Producer hilft Kafka, Daten schneller zu speichern, weil der Broker die Daten direkt aus dem Socket auf den Massenspeicher schreibt. Kann der Producer die Kompression vorab erledigen, macht das die komplette Pipeline aus Schreiben, Speichern und Lesen viel effizienter.

Ab Kafka 2.x aufwärts kann sich auch der Einsatz der Zstd-Kompression lohnen. Sie zeigte in einigen Tests deutliche Vorteile gegenüber den Alternativen. Die Kompressionsrate liegt nahe an der von Gzip, der CPU-Overhead liegt aber nicht höher als bei LZ4. Tests mit meinen eigenen Daten verrieten allerdings, dass Zstd sich mitunter mit bereits komprimierten Formaten schwertut. Es hängt also vom Use Case ab; ein Test schafft hier Klarheit.

Die nächste Sache, die Sie sich ansehen sollten, sind die Stapel (Batches). Der am einfachsten zu optimierende Konfigurationsparameter heißt »linger.ms« (grob übersetzt: Trödelzeitraum) und regelt die Stapelbildung über die Zeit. Wer hier die Latenz anpasst, kann den Datendurchsatz steigern, indem er den Overhead zusätzlicher Netzwerkaufrufe beseitigt. Dazu hilft der Blick auf den Wert »record-queue-time-avg«. Der verrät, wie lange ein Stapel durchschnittlich im Sendepuffer wartet, also wie lange es dauert, einen Stapel zu füllen.

Befindet sich der Wert permanent unter dem von »linger.ms«, dann füllen Sie Ihre Batch-Größen aus – gut so. Schaffen es die Producer hingegen nicht, lässt sich die Latenz erhöhen, indem Sie den »linger.ms«-Wert steigern, was den Datendurchsatz verbessert. Ein Hinweis an dieser Stelle: Kafka wartet standardmäßig potenziell eher nicht auf Batches, sondern tendiert zum Produzieren mit geringen Latenzen, auch wenn das ein Risiko vermehrter Remote Procedure Calls (RPC) nach sich zieht. Ich halte 5 Millisekunden als »linger.ms«-Wert für gut verträglich.

Unglücklicherweise bringen auch das Aktivieren der Kompression und das Optimieren von »linger.ms« noch nicht den erforderlichen Datendurchsatz. Zeit also, noch tiefer zu graben.

Tiefer graben

Steigt der Kafka-Admin tiefer in die Optimierung der Producer ein, beeinflussen sich die Konfigurationseinstellungen gegenseitig stärker und wirken sich zum Teil nicht linear und manchmal auf unerwartete Weise auf die Performance aus. Es zahlt sich daher aus, die verschiedenen Parameter nicht nur geduldig, sondern auch systematisch miteinander zu kombinieren. Sie sollten den ursprünglichen Flaschenhals stets im Auge behalten, während Sie die Menge an Records optimieren, die der Producer verarbeitet. Die nächste Frage lautet daher: Wie groß sind die Einträge (Records), und erzeugen Sie “gute” Stapel (Batches)?

Der Parameter »batch.size« bestimmt die Zahl der Einträge in einem Stapel und legt dessen Größe in Bytes fest. Er legt auch die Zahl an Bytes fest, nach denen der Producer eine Anfrage an die Broker schickt, unabhängig vom »linger.ms«-Wert. An die Broker geschickte Anfragen enthalten dabei mehrere Batches, einen für jede Partition.

In diesem Bereich gibt es einige Dinge, die Sie überprüfen können. Wie viele Einträge gibt es pro Batch, und wie groß wird ein Batch? Hierzu lohnt ein Blick auf die MBeans der »kafka-producer«.

Bytes pro Stapel

Die Parameter »batch-size-[avg|max]« liefern einen guten Eindruck davon, wie sich die Bytes über die Stapel verteilen. Die Einträge »record-size-[avg|max]« verraten etwas über die Größe jedes Eintrags. Teilen Sie Erstere durch Letztere, erhalten Sie so die ungefähre Menge an Einträgen pro Stapel.

Die Zahl vergleichen Sie nun mit der »batch.size«-Konfiguration und bestimmen so in etwa, wie viele Einträge den Producer passieren. Diesen Wert sollten Sie auch mit der »record-send-rate« abgleichen (die Zahl der Einträge pro Sekunde), die der Producer verrät.

Erzeugen die Producer zu viele verschiedene Topics, kann das die Qualität der Kompression beeinflussen:Kafka komprimiert weniger gut über viele Topics hinweg. Wollen Sie sich nicht allein auf Kafka verlassen, können Sie die Anwendung anpassen, um Batches aggressiver in Richtung der Topics zu optimieren. Dabei hilft ein Blick auf die »bytes-per-topic«-Parameter der Producer. Das ist allerdings eine fortgeschrittene Taktik – die sollten Sie erst nach Benchmarking-Anstrengungen versuchen, und wenn andere Maßnahmen scheitern.

Batch-Größen und I/O

Es verwirrt womöglich, dass Sie gelegentlich sehr große Nachrichten sehen – Sie haben aber den »record-size-max«-Parameter überprüft, oder? Das liegt daran, dass die »max.request.size«-Konfiguration die maximale Größe eines Batches (in Bytes) limitiert und damit implizit auch die Zahl der Stapel mit Einträgen.

Wie steht es um die Zeit, die Producer auf Ein- und Ausgaben warten? Die »io-wait-ratio«-Metriken verraten mehr darüber. Warten die Producer über die I/O-Threads noch auf Daten von oder für Broker, oder arbeiten sie bereits mit den Daten?

Puffergrößen

Nicht zuletzt können Sie dafür sorgen, dass die Producer den Client-Buffer nicht füllen. Jeder Producer verwendet einen ziemlich großen Puffer, um Daten zu sammeln, zu verpacken und dann an die Broker zu senden. In der Praxis war das nie ein Problem, aber es zahlt sich oft aus, methodisch vorzugehen. In diesem Fall ist der »buffer-available-bytes«-Parameter Ihr Freund. An ihm erkennen Sie, wenn die »buffer.memory«-Größe nicht ausreicht, weil die Größen der Records und die Batch-Einstellungen sie erschöpfen.

Fazit

An diesem Punkt haben Sie hoffentlich genügend Hinweise an der Hand, um Client-Instanzen zu skalieren. Sie kennen die wichtigsten Optimierungsschalter, einige Richtlinien, um die Garbage Collection anzupassen, und den netten Round-Robin-Trick, um Consumer-Gruppen auszubalancieren, wenn die Consumer auf verschieden partitionierte Topics treffen.

Für langsame Producer wenden Sie Standardoptimierungen für die Kompression und den Trödelzeitraum an oder steigen in die Untiefen der Producer-Konfiguration ein, um herauszufinden, was wirklich mit Einträgen und Stapeln passiert. Die Hoffnung ist jedenfalls, dass die Tipps aus diesem Artikel Ihnen über die rohen Informationen im Kafka-Handbuch hinaus weiterhelfen. (kki)

Der Autor

Jesse Yates (http://jesseyates.com) arbeitet als Staff Engineer bei Tesla und leitet die Entwicklung des Echtzeit-Stream-Processing im Data-Platform-Team. Vor Tesla gründete Yates ein Startup für Echtzeit-IoT-Daten, half Salesforce beim Einrichten von Apache HBase und beriet Projekte. Er trägt Code zu Apache HBase bei und sitzt im Projektmanagementkomitee von Apache Phoenix.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 6 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben