Broker-Tests

MQTT-Latenztest: Round-Trip-Time richtig messen

So führst du einen MQTT-Latenztest durch: Round-Trip-Time mit einer Uhr messen, NTP-Fallen vermeiden, p50/p99 berichten und Broker-Regionen fair vergleichen.

Aktualisiert · 8 Min. Lesezeit

MQTT-Latenztest: Round-Trip-Time richtig messen

Ein MQTT-Latenztest misst, wie lange eine Nachricht von einem Publisher über den Broker bis zu einem Subscriber braucht. Zuverlässig gelingt das, wenn du die Round-Trip-Time (RTT) mit einer einzigen Uhr misst: Derselbe Prozess publisht eine Nachricht, empfängt sie vom Broker zurück und zieht zwei Messwerte eines monotonen Timers voneinander ab. Alles, was Zeitstempel von zwei Rechnern vergleicht, misst die Uhrendifferenz genauso stark wie das Netzwerk.

Dieser Guide liefert dir ein Latenz-Skript zum Kopieren, erklärt die Uhren-Fallen, an denen die meisten Messungen scheitern, und zeigt, wie du Perzentile berichtest und Broker oder Regionen fair vergleichst.

Was "MQTT-Latenz" eigentlich bedeutet

Mehrere verschiedene Werte werden Latenz genannt. Entscheide, welchen du brauchst, bevor du Code schreibst:

MessungVon → nachBenötigte Uhr
Loopback-RTTClient publisht → derselbe Client empfängt seine eigene NachrichtEine Uhr (einfach, zuverlässig)
Echo-RTTClient A → Broker → Echo-Client B → Broker → Client AEine Uhr auf A
One-Way-LatenzPublisher auf Host A → Subscriber auf Host BZwei synchronisierte Uhren
VerbindungszeitTCP + TLS-Handshake + CONNECT → CONNACKEine Uhr
Netzwerk-BaselineTCP-Verbindung zum Broker-PortEine Uhr

Für Kapazitätsplanung und Broker-Vergleiche reicht meist die Loopback-RTT. Sie enthält die Routing-Arbeit des Brokers in beide Richtungen und zwei Netzwerkstrecken und braucht nur einen einzigen Client.

Uhren-Fallen

Die meisten schlechten Latenzwerte kommen von der Uhr, nicht vom Broker:

  • Die Wanduhr springt. time.time() in Python oder Date.now() in JavaScript folgen der Systemuhr, die NTP jederzeit verstellen kann. Nutze für Intervalle eine monotone Uhr: time.perf_counter() in Python, performance.now() oder process.hrtime.bigint() in Node.js.
  • Zwei Rechner sind sich nie exakt einig. Selbst mit NTP weichen Hosts häufig um eine Millisekunde oder mehr ab, auf Geräten ohne gute Zeitquelle noch viel stärker. Eine One-Way-Messung erbt diesen Fehler direkt — du kannst sogar negative Latenzen bekommen.
  • RTT / 2 ist eine Schätzung. Upload- und Download-Pfad können sich unterscheiden (Mobilfunk, ADSL, überlastete Uplinks). Berichte die RTT als RTT und teile nur durch zwei, wenn du das dazusagst.
  • Timer-Auflösung auf kleinen Geräten. millis() auf einem Mikrocontroller hat 1 ms Auflösung; mittle über viele Messwerte, statt einem einzelnen Wert zu vertrauen.

Ein Loopback-RTT-Skript

Dieses Skript (paho-mqtt 2.x) abonniert ein eindeutiges Topic, publisht Sequenznummern einzeln und wartet, bis jede Nachricht zurückkommt, bevor es die nächste sendet. Dieses Closed-Loop-Design misst die Latenz eines unbelasteten Pfades ohne Warteschlangeneffekte.

mqtt_rtt.py
import os, statistics, sys, threading, time, uuid
import paho.mqtt.client as mqtt

HOST = os.getenv("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.getenv("MQTT_PORT", "1883"))
N = int(sys.argv[1]) if len(sys.argv) > 1 else 500
QOS = int(os.getenv("MQTT_QOS", "1"))
WARMUP = 20
TOPIC = f"testmqtt/latency/{uuid.uuid4().hex[:8]}"

ready, got = threading.Event(), threading.Event()
state = {"expected": -1, "t0": 0.0}
samples, lost = [], 0

def on_connect(client, userdata, flags, reason_code, properties):
    client.subscribe(TOPIC, qos=QOS)

def on_subscribe(client, userdata, mid, reason_code_list, properties):
    ready.set()

def on_message(client, userdata, msg):
    if int(msg.payload) == state["expected"]:
        rtt = (time.perf_counter() - state["t0"]) * 1000
        if state["expected"] >= WARMUP:
            samples.append(rtt)
        got.set()

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
if os.getenv("MQTT_USER"):
    client.username_pw_set(os.getenv("MQTT_USER"), os.getenv("MQTT_PASS"))
client.on_connect, client.on_subscribe, client.on_message = on_connect, on_subscribe, on_message
client.connect(HOST, PORT, keepalive=30)
client.loop_start()
ready.wait(10)

for i in range(N + WARMUP):
    got.clear()
    state["expected"], state["t0"] = i, time.perf_counter()
    client.publish(TOPIC, str(i), qos=QOS)
    if not got.wait(5):
        lost += 1
    time.sleep(0.02)  # Pfad zwischen den Messungen unbelastet lassen

client.loop_stop()
q = statistics.quantiles(samples, n=100)
print(f"{HOST}:{PORT} QoS {QOS}  samples {len(samples)}  lost {lost}")
print(f"min {min(samples):.1f}  p50 {q[49]:.1f}  p95 {q[94]:.1f}  "
      f"p99 {q[98]:.1f}  max {max(samples):.1f} ms")
Ausführen
pip install "paho-mqtt>=2"
python mqtt_rtt.py 1000
MQTT_PORT=8883 python mqtt_rtt.py 1000   # braucht client.tls_set() für TLS
MQTT_QOS=0 python mqtt_rtt.py 1000

Die ersten WARMUP Nachrichten werden verworfen, weil sie für kalte Caches und das Aufwärmen der Verbindung bezahlen. Wenn du mit TLS testen willst, füge vor connect() ein client.tls_set() ein und nutze Port 8883 — der Handshake passiert einmal, beeinflusst also die Verbindungszeit, nicht die RTT pro Nachricht.

Vor dem Skript lohnt sich ein kurzer Check im Online-MQTT-Client: testmqtt/latency/# abonnieren, eine Nachricht publishen und prüfen, dass sie zurückkommt. Funktioniert das, funktioniert auch das Skript.

Netzwerk und Broker trennen

Eine RTT von 40 ms sagt nichts aus, solange du nicht weißt, wie viel davon Netzwerk ist. Miss als Baseline die reine TCP-Verbindungszeit zum Broker-Port:

TCP-Baseline (erster Aufruf enthält DNS)
python3 -c "import socket,time
for _ in range(5):
    t=time.perf_counter(); socket.create_connection(('broker.connectmq.com',1883)).close()
    print(f'{(time.perf_counter()-t)*1000:.1f} ms')"

Ein TCP-Connect entspricht einem Netzwerk-Round-Trip. Liegt deine MQTT-Loopback-RTT bei QoS 0 nahe an dieser Baseline, fügt der Broker kaum etwas hinzu und der Rest der Latenz ist Entfernung. Liegt sie deutlich darüber, schau dir Broker, Client oder Pfad an — Fehlersuche bei hoher Latenz geht die üblichen Ursachen durch.

Licht legt in Glasfaser etwa 200 km pro Millisekunde zurück, ein 2.000 km entfernter Broker kostet also mindestens ~20 ms RTT, bevor überhaupt Software läuft. Kein Broker-Tuning schlägt die Physik; wähle eine Region nahe an deinen Geräten.

Perzentile statt Durchschnitt

Latenzverteilungen haben lange Ausläufer. Ein Mittelwert von 12 ms kann ein p99 von 300 ms verstecken, und genau das p99 lässt Geräte in Timeouts laufen und Dashboards ruckeln. Berichte immer mindestens:

  • min — die vom Netzwerk gesetzte Untergrenze; sie sollte zwischen Läufen stabil sein.
  • p50 — das typische Erlebnis.
  • p95 / p99 — der Ausläufer; vergleiche diese Werte zwischen Konfigurationen.
  • max und lost — einzelne Ausreißer und Timeouts sind Signale, kein Rauschen.

Sammle genug Messwerte für das Perzentil, das du berichtest. Bei 100 Messwerten ist das p99 praktisch eine einzige Nachricht; peile mindestens 1.000 Messwerte an, bevor du ein p99 nennst.

Was die Latenz verändert

FaktorWirkung auf die RTT
QoS 0 → QoS 1Die Zustellung wartet nicht auf das PUBACK, aber Persistenz im Broker und Inflight-Limits können unter Last Zeit kosten
QoS 2Vier-Paket-Handshake pro Hop; spürbar langsamer, siehe Benchmark QoS 0 vs 1 vs 2
TLSHandshake-Kosten beim Verbinden; geringe Verschlüsselungskosten pro Nachricht
WebSocketsZusätzliches Framing und oft ein Proxy-Hop
Payload-GrößeGrößere Payloads brauchen auf langsamen Leitungen länger
Broker-LastWarteschlangen wachsen, die Tail-Latenz steigt zuerst
Client-CallbacksLangsame on_message-Handler verzögern jede folgende Nachricht

Um zu sehen, wie sich die Latenz unter Last verhält, lass dieses Skript laufen, während ein Lastgenerator Traffic erzeugt — die Methode findest du unter MQTT-Broker-Lasttests. Die RTT im Leerlauf ist dein Best Case; die RTT unter Last ist die Zahl, mit der du planst.

Broker und Regionen fair vergleichen

Ein Vergleich ist nur fair, wenn alles außer dem Broker identisch ist:

  1. Miss von derselben Maschine und demselben Netzwerk, idealerweise einer VM in der Region, in der deine Geräte oder dein Backend sitzen.
  2. Verwende für jedes Ziel dieselbe QoS, dieselbe Payload, denselben Port und dieselbe TLS-Einstellung.
  3. Miss die Ziele abwechselnd (A, B, A, B) statt erst alles von A und dann alles von B, damit ein kurzer Netzwerkaussetzer beide trifft.
  4. Halte min, p50, p99 und lost jedes Ziels zusammen mit der TCP-Baseline in einer Tabelle fest.
  5. Wiederhole die Messung zu einer anderen Tageszeit, bevor du Schlüsse ziehst.

Für Multi-Region-Entscheidungen startest du in jeder Region mit Nutzern eine kleine VM und lässt dasselbe Skript gegen jeden Broker-Endpunkt laufen. Die resultierende Matrix aus p50/p99 pro (Client-Region, Broker-Region) ist die ehrlichste Entscheidungsgrundlage, die du bekommst.

Checkliste für den Latenztest

  • Nutze für die RTT eine monotone Uhr und einen einzigen Prozess.
  • Verwirf Warm-up-Nachrichten; miss die Verbindungszeit separat.
  • Sammle über 1.000 Messwerte und berichte min, p50, p95, p99, max und lost.
  • Halte neben jedem MQTT-Ergebnis eine TCP-Baseline fest.
  • Teste im Leerlauf und unter Last, aus dem Netzwerk, das deine Geräte wirklich nutzen.
  • Lege das Skript in dein Repository und führe es nach Broker- oder Netzwerkänderungen erneut aus.

Round-Trip im Browser prüfen

Ein vertrauenswürdiger MQTT-Latenztest ist vor allem eine Frage der Disziplin: eine Uhr, genug Messwerte, Perzentile statt Durchschnitt und eine Baseline, die zeigt, welcher Teil der Zahl zum Netzwerk gehört. Wenn das stimmt, beantwortet ein 15-Minuten-Skript Fragen, die Hersteller-Benchmarks nie beantworten.

Häufig gestellte Fragen

Wie messe ich MQTT-Latenz ohne synchronisierte Uhren?

Miss die Round-Trip-Time mit einer einzigen Uhr: Ein Client publisht auf ein Topic, das er selbst abonniert hat (oder an einen Echo-Client, der die Nachricht zurückschickt), und derselbe Prozess stempelt Senden und Empfangen mit einer monotonen Uhr.

Ist die MQTT-Round-Trip-Time das Doppelte der One-Way-Latenz?

Nur ungefähr. RTT / 2 setzt voraus, dass beide Richtungen symmetrisch sind, was bei Mobilfunk oder asymmetrischen Leitungen oft nicht stimmt. Für echte One-Way-Werte brauchst du Uhren, die deutlich genauer synchronisiert sind als die gemessene Latenz.

Warum ist meine erste MQTT-Nachricht viel langsamer als der Rest?

Die ersten Nachrichten bezahlen DNS-Auflösung, TCP- und TLS-Aufwärmphase, den Aufbau der Subscription und kalte Caches. Verwirf einen Warm-up-Block, bevor du Messwerte aufzeichnest, und miss die Verbindungszeit separat.