Broker-Tests

MQTT-Durchsatztest: Nachrichten pro Sekunde erklärt

So führst du einen MQTT-Durchsatztest durch: Ingress vs. Egress in msg/s, die nachhaltige Rate finden, Backpressure erkennen und msg/s in Bandbreite umrechnen.

Aktualisiert · 9 Min. Lesezeit

MQTT-Durchsatztest: Nachrichten pro Sekunde erklärt

„Nachrichten pro Sekunde“ ist die meistzitierte MQTT-Kennzahl und die am häufigsten missverstandene. Ein Broker kann 50.000 msg/s von Publishern annehmen und trotzdem nur die Hälfte zustellen – oder alle mit zwei Sekunden Verspätung. Ein sinnvoller MQTT-Durchsatztest misst die Rate, die das System dauerhaft halten kann – eingehend und ausgehend –, während die Latenz flach bleibt und nichts verloren geht, und zwar mit deinen eigenen Payloads und deiner Topic-Struktur.

Diese Anleitung definiert die Kennzahlen, zeigt Schritt für Schritt, wie du die nachhaltige Rate findest, und erklärt die Backpressure-Signale, die dir sagen, dass du darüber hinaus bist. Für das Gesamtbild aus Verbindungen und Latenz siehe MQTT-Broker-Lasttest.

Ingress, Egress und Fan-out

BegriffBedeutungGemessen wo
Ingress-RatePUBLISH-Pakete pro Sekunde, die der Broker von Publishern annimmtPublisher-Seite (gesendet / bestätigt)
Egress-RatePUBLISH-Pakete pro Sekunde, die der Broker an Subscriber zustelltSubscriber-Seite (empfangen)
Fan-out-FaktorDurchschnittliche Anzahl Subscriber pro NachrichtEgress ÷ Ingress
Nachhaltige RateHöchster Ingress, bei dem Egress mithält, die Latenz stabil ist und Fehler bei null bleibenBeide Seiten, über Minuten

Den Fan-out-Faktor vergessen viele. Ein Publisher mit 1.000 msg/s und 20 Subscribern auf dem Topic ergibt 20.000 msg/s Egress. Die Arbeit des Brokers entspricht eher dem Egress als dem Ingress – gib daher immer beides an. Für die Extremfälle siehe den eigenen Fan-out-Test und den Fan-in-Test.

Spitzen- vs. nachhaltiger Durchsatz

Jedes System kann eine Weile einen Burst oberhalb seiner echten Kapazität schlucken: Socket-Puffer, Client-Queues und Broker-Queues laufen voll und verdecken das Problem. Ein Fünf-Sekunden-Test misst diese Puffer, nicht den Broker. Die nachhaltige Rate zeigt sich erst, wenn du jede Laststufe lange genug hältst, damit die Queues einen stabilen Zustand erreichen – meist ein paar Minuten.

Die Methode ist eine Stufenrampe:

  1. Beginne mit einer Rate, die das System sicher schafft (zum Beispiel 20 % der erwarteten Spitze).
  2. Halte sie 2–5 Minuten und notiere Ingress, Egress, p99-Latenz und Fehler.
  3. Erhöhe um einen festen Schritt (zum Beispiel +25 %) und halte erneut.
  4. Stopp, wenn Egress dem Ingress nicht mehr folgt, die p99-Latenz innerhalb einer Stufe weiter steigt oder Fehler auftreten.
  5. Die letzte saubere Stufe ist deine nachhaltige Rate. Plane die Produktion mit komfortablem Abstand darunter.

Trägst du angebotene Rate gegen Latenz auf, siehst du eine flache Linie und dann ein „Knie“, an dem die Latenz nach oben abknickt. Dieses Knie ist die gesuchte Zahl – nicht die Maximalrate, die der Lastgenerator hinausschieben konnte.

Die Rampe mit emqtt-bench

emqtt-bench erzeugt hohe Raten von einer einzigen Maschine aus. Starte zuerst die Subscriber, dann die Publisher mit steigender Rate. Das Publish-Intervall -I gilt pro Client in Millisekunden, die Gesamtrate ist also Clients × 1000 / I:

Subscriber — einer pro Publisher-Topic
docker run --rm -it emqx/emqtt-bench sub \
  -h broker.connectmq.com -p 1883 \
  -c 50 -t 'testmqtt/tput/%i' -q 1 \
  -u "$MQTT_USER" -P "$MQTT_PASS"
Publisher — Stufe 1: 50 Clients × 20 msg/s = 1.000 msg/s
docker run --rm -it emqx/emqtt-bench pub \
  -h broker.connectmq.com -p 1883 \
  -c 50 -I 50 -t 'testmqtt/tput/%i' -s 256 -q 1 \
  -u "$MQTT_USER" -P "$MQTT_PASS"

Für die nächsten Stufen senkst du -I (50 → 40 → 30 → 20 → 10 ms) oder fügst Clients hinzu. emqtt-bench gibt jede Sekunde Sende- und Empfangsraten aus; übertrage sie pro Stufe in eine Tabelle. Da hier jeder Publisher einen eigenen Subscriber hat, ist der Fan-out-Faktor 1 und Egress sollte gleich Ingress sein.

Eine Python-Sonde für Lücken und Verzögerung

Ratenzähler verbergen zweierlei: welche Nachrichten fehlen und wie spät der Rest ankam. Lass diese kleine paho-mqtt-2.x-Sonde neben dem Benchmark laufen. Sie publisht nummerierte Nachrichten mit Zeitstempel in moderater Rate und meldet jede Sekunde Lücken und Verzögerung – steigt die Verzögerung der Sonde, während der Bench läuft, ist das dein Backpressure-Signal:

tput_probe.py
import json, time, uuid
import paho.mqtt.client as mqtt

HOST, PORT = "broker.connectmq.com", 1883
TOPIC = f"testmqtt/tput/probe-{uuid.uuid4().hex[:6]}"
RATE = 100                      # Sonden-Nachrichten pro Sekunde
stats = {"n": 0, "last": -1, "gaps": 0, "lag": []}

def on_message(client, userdata, msg):
    m = json.loads(msg.payload)
    if m["i"] > stats["last"] + 1:
        stats["gaps"] += m["i"] - stats["last"] - 1
    stats["last"] = max(stats["last"], m["i"])
    stats["n"] += 1
    stats["lag"].append((time.perf_counter() - m["t"]) * 1000)

def client(name):
    c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"{name}-{uuid.uuid4().hex[:6]}")
    c.connect(HOST, PORT)
    c.loop_start()
    return c

sub = client("probe-sub")
sub.on_message = on_message
sub.subscribe(TOPIC, qos=1)
pub = client("probe-pub")
time.sleep(1)

i, next_report = 0, time.time() + 1
while True:
    pub.publish(TOPIC, json.dumps({"i": i, "t": time.perf_counter()}), qos=1)
    i += 1
    time.sleep(1 / RATE)
    if time.time() >= next_report:
        lag = sorted(stats["lag"]) or [0]
        print(f"rx {stats['n']:>5}/s  gaps {stats['gaps']}  "
              f"lag p50 {lag[len(lag)//2]:.1f} ms  p99 {lag[int(len(lag)*0.99)-1]:.1f} ms")
        stats.update(n=0, lag=[])
        next_report += 1

Ein einzelner Python-Prozess erzeugt keine zehntausende Nachrichten pro Sekunde – das ist Aufgabe des Benchmark-Tools. Die Sonde ist ein Thermometer, keine Heizung.

Backpressure erkennen

MQTT hat kein globales Flow-Control-Signal; Backpressure zeigt sich als Queues, die irgendwo wachsen. Wisse, wo du hinschauen musst:

SignalWo es entstehtBedeutung
Senderate des Publishers fällt unter das ZielInflight-Fenster des Clients voll (QoS 1/2)PUBACKs kommen langsamer zurück, als du publishst – Broker oder Strecke sind ausgelastet
Verzögerung der Sonde steigt innerhalb einer Stufe stetigBroker- oder Subscriber-QueueEgress kommt nicht hinterher; du liegst über der nachhaltigen Rate
Lücken in der Sonde bei QoS 0Broker verwirft Nachrichten für langsame ClientsQueue-Limit erreicht; der Broker wirft Last ab
Subscriber trennen mit Queue- oder Quota-FehlernLimits pro Client im BrokerSiehe Message Queue voll und langsame Consumer
CPU des Lastgenerators bei 100 %Deine TestmaschineDas Ergebnis misst den Client, nicht den Broker

Bei QoS 1 und 2 ist das Inflight-Fenster der entscheidende Begrenzer: Ein Client darf nur eine bestimmte Zahl unbestätigter Nachrichten offen haben (paho max_inflight_messages_set(), in MQTT 5 Receive Maximum). Mit 20 Nachrichten in flight und 50 ms Round Trip landet eine Verbindung bei etwa 400 msg/s, egal wie schnell der Broker ist. Deshalb sehen Durchsatztests über weite Strecken schlechter aus als in derselben Region, und deshalb heben mehr Verbindungen – nicht eine schnellere Schleife – die Obergrenze an. Receive Maximum und Flow Control erklärt den Mechanismus.

Von msg/s zur Bandbreite

Durchsatz ist auch ein Netzwerkbudget. Ein MQTT-PUBLISH kostet ungefähr: festen Header (2–5 Bytes) + Topic-Länge + 2 Bytes + Packet Identifier (2 Bytes bei QoS > 0) + MQTT-5-Properties + Payload. Dazu kommen TCP/IP-Header und gegebenenfalls TLS-Record-Overhead. QoS 1 fügt in Gegenrichtung ein 4-Byte-PUBACK hinzu.

Rechenbeispiel: 256-Byte-Payload, 20-Byte-Topic, QoS 1 → etwa 285 Bytes pro PUBLISH vor TCP/IP. Bei 5.000 msg/s Egress sind das rund 1,4 MB/s bzw. etwa 11–12 Mbit/s MQTT-Traffic vor IP- und TLS-Overhead. Bei kleinen Nachrichten dominiert der Overhead, daher erhöht das Bündeln mehrerer Messwerte in einer Nachricht den effektiven Durchsatz oft stärker als jedes Broker-Tuning. Der Paketgrößen-Rechner rechnet das für dein konkretes Topic und deinen Payload aus.

Das Ergebnis dokumentieren

Ein Durchsatzergebnis ist nur wiederverwendbar, wenn es seine Bedingungen nennt. Notiere mindestens: Broker-Version und -Konfiguration, QoS, Payload-Größe, Anzahl Topics, Fan-out-Faktor, Anzahl Publisher- und Subscriber-Verbindungen, TLS an oder aus, Dauer pro Stufe und Region des Lastgenerators. Berichte dann die nachhaltigen Ingress- und Egress-Raten mit ihrer p99-Latenz, nicht die Spitze. Wenn du später Läufe vergleichst, hilft Benchmark-Ergebnisse interpretieren bei den typischen Fallen.

Teste vor der Rampe deine Topics mit dem Online-MQTT-Client, um Zugangsdaten und ACLs zu bestätigen – ein Berechtigungsfehler unter Last sieht genau wie ein Durchsatzproblem aus.

Ein Durchsatztest ist die Suche nach dem Knie, kein Wettrennen um die größte Zahl. Steigere in Stufen, halte jede Stufe lange genug, beobachte Verzögerung und Lücken statt nur Zähler und berichte Egress zusammen mit Ingress – mit dieser Zahl kannst du Kapazität planen.

Häufig gestellte Fragen

Was ist ein guter MQTT-Durchsatzwert?

Es gibt keinen universellen Wert: Er hängt von Payload-Größe, QoS, Fan-out, TLS, Persistenz und Hardware ab. Aussagekräftig ist die höchste Rate, die dein Broker mit deinem eigenen Traffic-Muster hält, während die Latenz flach bleibt und nichts verloren geht.

Warum ist meine Empfangsrate niedriger als die Senderate?

Entweder stauen sich Nachrichten irgendwo (Inflight-Fenster, Broker-Queues, Client-Puffer) oder sie werden verworfen. Wächst die Lücke mit der Zeit, liegst du über der nachhaltigen Rate. Bei QoS 0 bedeutet eine wachsende Lücke meist Verluste.

Beeinflusst QoS den MQTT-Durchsatz?

Ja. QoS 1 fügt pro Nachricht und Hop ein PUBACK hinzu, QoS 2 drei zusätzliche Pakete, und beide werden durch das Inflight-Fenster begrenzt. Rechne bei höherem QoS mit niedrigeren Spitzenraten, besonders über Verbindungen mit hoher Latenz.