Broker-Tests

MQTT-Benchmark-Ergebnisse richtig lesen, ohne dich selbst zu täuschen

MQTT-Benchmarks richtig auswerten: warum Durchschnittswerte lügen, wie Coordinated Omission Latenz versteckt, wie du Engpässe erkennst und Zahlen berichtest.

Aktualisiert · 8 Min. Lesezeit

MQTT-Benchmark-Ergebnisse richtig lesen, ohne dich selbst zu täuschen

Ein MQTT-Benchmark liefert schnell Zahlen. Zahlen zu liefern, denen du vertrauen kannst, ist schwieriger. Die meisten irreführenden Ergebnisse entstehen durch dieselben wenigen Fehler: Durchschnittswerte zitieren, den Lastgenerator statt des Brokers messen, Coordinated Omission ignorieren und Läufe vergleichen, die nicht gleich aufgesetzt waren.

Dieser Guide geht jede Falle durch, zeigt, wie du sie in deinen eigenen Daten erkennst, und endet mit einer Report-Vorlage, die Ergebnisse reproduzierbar macht. Er setzt voraus, dass du bereits einen Test gefahren hast – falls nicht, beginne mit MQTT-Broker-Lasttests.

Durchschnittswerte verstecken die langsamen Nachrichten

Latenz ist nie normalverteilt. Ein Broker unter Last liefert die meisten Nachrichten in wenigen Millisekunden aus und einen kleinen Teil in Hunderten von Millisekunden – während einer Garbage-Collection-Pause, eines Disk-Flushs oder einer Retransmission. Der Mittelwert vermischt beide Gruppen zu einer Zahl, die keine von beiden beschreibt.

LaufMittelwertp50p99Max
A6 ms4 ms12 ms30 ms
B6 ms3 ms140 ms900 ms

(Beispielwerte.) Beide Läufe haben denselben Durchschnitt, aber in Lauf B kommt jede hundertste Nachricht mehr als eine Zehntelsekunde zu spät an. Bei einem Gerät, das einmal pro Sekunde publiziert, ist das etwa alle zwei Minuten eine spürbare Verzögerung.

Regeln, die dich ehrlich halten:

  • Berichte Perzentile: p50 (typisch), p99 (der Tail), Max (der schlimmste beobachtete Fall). Ergänze p99.9 nur, wenn du genug Samples hast.
  • Mittle niemals Perzentile über Läufe oder Clients. Der Durchschnitt von zehn p99-Werten ist nicht das p99 der kombinierten Daten – führe stattdessen die Rohdaten oder Histogramme zusammen.
  • Prüfe die Stichprobengröße. Das p99 von 100 Nachrichten ist eine einzige Nachricht. Sammle Tausende pro Lauf.

Coordinated Omission

Coordinated Omission ist der häufigste Grund, warum ein Benchmark besser aussieht als die Realität. Sie tritt auf, wenn der Lastgenerator auf eine Antwort wartet, bevor er die nächste Anfrage sendet. Hängt der Broker 500 ms, zeichnet ein synchroner Publisher eine langsame Nachricht auf – und schickt die 49 anderen, die eine echte Flotte in dieser halben Sekunde gesendet hätte, einfach nicht. Diese Nachrichten, die alle hohe Latenz gehabt hätten, tauchen in den Daten nie auf.

Echte Geräte koordinieren sich nicht mit dem Broker. Ein Sensor publiziert nach seinem eigenen Takt, egal ob das letzte PUBACK angekommen ist oder nicht. Um das nachzubilden, miss die Latenz ab dem geplanten Sendezeitpunkt, nicht ab dem Moment, in dem die Nachricht tatsächlich an den Socket übergeben wurde:

intended_time.py — latency without coordinated omission
import json, statistics, time, uuid
import paho.mqtt.client as mqtt

HOST, PORT = "broker.connectmq.com", 1883
TOPIC = f"testmqtt/bench-{uuid.uuid4().hex[:8]}/latency"
N, RATE = 20000, 500                   # Nachrichten, Nachrichten pro Sekunde
samples = []

def on_message(client, userdata, msg):
    intended = json.loads(msg.payload)["t"]
    samples.append((time.perf_counter() - intended) * 1000)

sub = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
sub.on_message = on_message
sub.connect(HOST, PORT)
sub.subscribe(TOPIC, qos=1)
sub.loop_start()

pub = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
pub.max_queued_messages_set(0)         # 0 = unbegrenzte Queue im Client
pub.connect(HOST, PORT)
pub.loop_start()
time.sleep(1)

start = time.perf_counter()
for i in range(N):
    intended = start + i / RATE        # der Takt, dem ein echtes Gerät folgen würde
    delay = intended - time.perf_counter()
    if delay > 0:
        time.sleep(delay)
    pub.publish(TOPIC, json.dumps({"i": i, "t": intended}), qos=1)

time.sleep(5)
q = statistics.quantiles(samples, n=1000)   # 999 Schnittpunkte
print(f"received {len(samples)}/{N}")
print(f"p50 {q[499]:.1f} ms  p99 {q[989]:.1f} ms  p99.9 {q[998]:.1f} ms  max {max(samples):.1f} ms")

Gerät der Publisher in Verzug – weil Broker oder Netzwerk gebremst haben –, trägt jede verspätete Nachricht diese Verzögerung in ihrer Messung. Vergleiche die Ausgabe mit einer Variante, die time.perf_counter() beim Publish stempelt: Ist das p99 mit geplantem Zeitpunkt deutlich höher, hat der Broker gehangen und die naive Messung hat es versteckt.

Misst du den Broker oder den Client?

Eine Benchmark-Maschine, der CPU, Sockets oder Bandbreite ausgehen, erzeugt ein Plateau, das genau wie ein Broker-Limit aussieht. Bevor du dem Broker die Schuld gibst, prüfe den Lastgenerator:

  • CPU des Generators. Hängt ein Kern bei 100 % (typisch bei Single-Thread-Clients, etwa einer einzigen paho-Netzwerkschleife mit Hunderten Nachrichten pro Sekunde), liegt die Grenze bei dir. Verteile die Clients auf mehrere Prozesse oder Maschinen.
  • Plateaus bei runden Zahlen. Verbindungszahlen, die bei 1.024, 4.096 oder um 28.000–64.000 stehen bleiben, deuten meist auf File-Descriptor- oder Ephemeral-Port-Limits auf dem Client-Host hin. Erhöhe ulimit -n oder nutze weitere Quell-IPs.
  • Netzwerkinterface. Multipliziere die Nachrichtenrate mit der Größe auf der Leitung (der Paketgrößen-Rechner liefert die exakten Bytes pro PUBLISH) und vergleiche mit der Link-Kapazität – inklusive TLS-Overhead.
  • Der Zwei-Maschinen-Test. Fahre dieselbe Last von zwei Generatoren mit jeweils halber Rate. Steigt der Gesamtdurchsatz, war der einzelne Generator der Engpass.

Die Broker-Seite erzählt den Rest: Erfasse CPU, Speicher, Verbindungszahl und Nachrichtenraten (zum Beispiel über $SYS-Topics oder Prometheus) neben den Client-Zahlen. Ein Broker bei 30 % CPU mit festgefahrenem Durchsatz wartet auf etwas anderes.

Uhren, Warm-up und andere stille Verzerrungen

  • Clock Skew. Laufen Publisher und Subscriber auf verschiedenen Maschinen, enthält die End-to-End-Latenz die Differenz ihrer Uhren. NTP hält Uhren bestenfalls auf wenige Millisekunden genau – oft schlechter. Miss mit Publisher und Subscriber in einem Prozess oder nutze Round-Trips. Mehr dazu in MQTT-Probleme mit Uhren und Zeitstempeln.
  • Warm-up. Die ersten Sekunden enthalten TCP- und TLS-Handshakes, JIT-Kompilierung und Cache-Misses. Verwirf eine Warm-up-Phase oder berichte sie separat.
  • Zu kurze Läufe. Ein 30-Sekunden-Lauf verpasst periodische Ereignisse wie Persistenz-Snapshots oder Log-Rotation. Halte jede Laststufe minutenlang; nutze für Stunden einen Soak-Test.
  • Verlorene Nachrichten. Perzentile, die nur aus angekommenen Nachrichten berechnet werden, sehen großartig aus, wenn die langsamen verworfen wurden. Berichte immer empfangen / gesendet neben der Latenz.
  • Unterschiedliche QoS. QoS 0, 1 und 2 haben unterschiedlich viele Pakete und sehr unterschiedliche Kosten; vergleiche Ergebnisse über QoS-Stufen hinweg nie, als wäre es derselbe Test. Siehe QoS 0 vs 1 vs 2 Benchmark.

Ein schlechtes Ergebnis erkennen

Symptom in den DatenWahrscheinliche UrsacheSo bestätigst du es
Durchsatz flach, Broker-CPU niedrigLastgenerator ausgelastetGenerator-CPU prüfen; mit zwei Generatoren wiederholen
Empfangen < gesendet bei QoS 0Client oder Broker verwirft unter DruckMit QoS 1 wiederholen, Zählungen vergleichen
p99 gut, Max riesigEinzelner Hänger (GC, Failover, Disk)Latenz über die Zeit plotten, nicht nur als Histogramm
p50 steigt während des Laufs stetigQueue staut sich (Slow Consumer)Queue-/Inflight-Metriken des Brokers beobachten
Ergebnisse schwanken 30 % zwischen identischen LäufenGeteilte oder laute UmgebungDreimal laufen lassen; auf isoliertem Broker testen
Latenz negativ oder unmöglich niedrigUhren verschiedener MaschinenIn einem Prozess messen

So berichtest du MQTT-Benchmark-Ergebnisse

Eine Zahl ohne ihr Setup lässt sich weder reproduzieren noch vergleichen. Berichte mindestens Folgendes:

FeldBeispiel
Broker, Version, DeploymentBroker X 2.x, 4 vCPU / 8 GB, Single Node
Client-Tool und Versionemqtt-bench / eigenes paho-mqtt-2.x-Skript
Lastgenerator2 Maschinen, gleiche Region, CPU-Spitze 55 %
Netzwerk und TransportGleiche Region, TCP 1883 (oder TLS 8883)
Protokoll und QoSMQTT 5, QoS 1, Clean Start
Workload1.000 Publisher, je 1 msg/s, 512 B JSON, 1:1-Topics
Dauer5 Min. Warm-up ausgeschlossen, 20 Min. gemessen
ErgebnisseDurchsatz gesendet/empfangen, p50, p99, p99.9, Max, Fehler
RohdatenLink zu Histogramm oder CSV

Zwei Gewohnheiten machen Reports deutlich nützlicher:

  1. Plotte die Latenz über die Zeit. Ein Histogramm verbirgt, wann die langsamen Nachrichten auftraten. Eine Zeitreihe zeigt, ob der Tail eine konstante Eigenschaft ist oder aus wenigen Ereignissen besteht.
  2. Zeige den Sättigungspunkt. Statt einer einzelnen Zahl fährst du steigende Laststufen und berichtest die Rate, bei der p99 dein Ziel überschreitet (zum Beispiel 50 ms). Diese Rate ist deine nutzbare Kapazität – der absolute Maximaldurchsatz ist es nicht.

Ein kurzer Plausibilitätscheck vor jedem Lauf

Bevor du einen langen Benchmark startest, verbinde dich mit dem Online-MQTT-Client, abonniere dein Test-Topic und publiziere ein paar Nachrichten von Hand. Dauert eine Nachricht sichtbar lange oder empfängt das Abo nichts, behebe das zuerst – ein Lasttest wird dir nicht mehr verraten.

Benchmark-Topic im Browser prüfen

Benchmarks sind nur so gut wie die Fragen dahinter. Miss ab dem geplanten Sendezeitpunkt, behalte den Lastgenerator im Blick, berichte Perzentile zusammen mit Zustellzahlen und dem vollständigen Setup – dann halten deine MQTT-Zahlen auch der nächsten Person stand, die sie reproduzieren will, einschließlich deines zukünftigen Ichs.

Häufig gestellte Fragen

Warum ist die durchschnittliche Latenz eines MQTT-Benchmarks irreführend?

Latenzverteilungen sind schief: Die meisten Nachrichten sind schnell, einige wenige sehr langsam. Der Mittelwert vermischt beides und versteckt genau den langsamen Tail, den Geräte und Nutzer bemerken. Berichte stattdessen p50, p99 und Maximum.

Was ist Coordinated Omission bei einem MQTT-Benchmark?

Sie entsteht, wenn der Lastgenerator auf eine langsame Antwort wartet, bevor er die nächste Nachricht sendet. Der Hänger wird nur einmal gemessen statt für jede Nachricht, die währenddessen hätte gesendet werden sollen – das Tool unterschätzt die Latenz also genau dann, wenn der Broker kämpft.

Wie viele Nachrichten brauche ich für ein verlässliches p99?

Mindestens einige Tausend Samples pro Lauf. Bei 100 Nachrichten ist p99 ein einziges Sample. Ziele auf 10.000 oder mehr für p99 und 100.000 oder mehr, wenn du p99.9 angeben willst.