Pruebas de brokers

Prueba de latencia MQTT: mide el tiempo de ida y vuelta correctamente

Cómo hacer una prueba de latencia MQTT: mide el tiempo de ida y vuelta con un solo reloj, evita errores de NTP, reporta p50/p99 y compara regiones de broker.

Actualizado · 8 min de lectura

Prueba de latencia MQTT: mide el tiempo de ida y vuelta correctamente

Una prueba de latencia MQTT mide cuánto tarda un mensaje en ir desde un publisher, a través del broker, hasta un subscriber. La forma fiable de hacerlo es medir el tiempo de ida y vuelta (RTT) con un solo reloj: el mismo proceso publica un mensaje, lo recibe de vuelta del broker y resta dos lecturas de un temporizador monotónico. Todo lo que compare marcas de tiempo de dos máquinas mide tanto la diferencia entre sus relojes como la red.

Esta guía te da un script de latencia listo para copiar, explica los errores de reloj que arruinan la mayoría de las mediciones, muestra cómo reportar percentiles y cómo comparar brokers o regiones de forma justa.

Qué significa realmente "latencia MQTT"

Varias cifras distintas reciben el nombre de latencia. Decide cuál necesitas antes de escribir código:

MediciónDesde → hastaReloj necesario
RTT en bucle (loopback)El cliente publica → el mismo cliente recibe su propio mensajeUn reloj (fácil, fiable)
RTT con ecoCliente A → broker → cliente eco B → broker → cliente AUn reloj en A
Latencia en un sentidoPublisher en el host A → subscriber en el host BDos relojes sincronizados
Tiempo de conexiónTCP + handshake TLS + CONNECT → CONNACKUn reloj
Línea base de redConexión TCP al puerto del brokerUn reloj

Para planificar capacidad y comparar brokers, el RTT en bucle suele bastar. Incluye el trabajo de enrutamiento del broker en ambos sentidos y dos recorridos de red, y no necesita más que un único cliente.

Errores de reloj

La mayoría de las malas cifras de latencia vienen del reloj, no del broker:

  • La hora de pared salta. time.time() en Python o Date.now() en JavaScript siguen el reloj del sistema, que NTP puede ajustar en cualquier momento. Usa un reloj monotónico para intervalos: time.perf_counter() en Python, performance.now() o process.hrtime.bigint() en Node.js.
  • Dos máquinas nunca coinciden exactamente. Incluso con NTP, es habitual que los hosts difieran un milisegundo o más, y mucho más en dispositivos sin una buena fuente de tiempo. Una medición en un sentido hereda ese error directamente: incluso puedes obtener latencias negativas.
  • RTT / 2 es una estimación. Los caminos de subida y bajada pueden ser distintos (redes móviles, ADSL, enlaces de subida saturados). Reporta el RTT como RTT y divide entre dos solo cuando lo indiques.
  • Resolución del temporizador en dispositivos pequeños. millis() en un microcontrolador tiene una resolución de 1 ms; promedia muchas muestras en lugar de fiarte de una sola lectura.

Un script de RTT en bucle

Este script (paho-mqtt 2.x) se suscribe a un topic único, publica números de secuencia de uno en uno y espera a que cada mensaje vuelva antes de enviar el siguiente. Ese diseño de bucle cerrado mide la latencia de un camino en reposo, sin efectos de colas.

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)  # deja el camino en reposo entre sondas

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")
Ejecutarlo
pip install "paho-mqtt>=2"
python mqtt_rtt.py 1000
MQTT_PORT=8883 python mqtt_rtt.py 1000   # requiere client.tls_set() para TLS
MQTT_QOS=0 python mqtt_rtt.py 1000

Los primeros WARMUP mensajes se descartan porque pagan las cachés frías y el calentamiento de la conexión. Si necesitas probar con TLS, añade client.tls_set() antes de connect() y usa el puerto 8883: el handshake ocurre una sola vez, así que afecta al tiempo de conexión, no al RTT por mensaje.

Antes de usar el script, vale la pena una comprobación rápida en el cliente MQTT online: suscríbete a testmqtt/latency/#, publica un mensaje y confirma que vuelve. Si eso funciona, el script también funcionará.

Separa la red del broker

Un RTT de 40 ms no te dice nada hasta que sabes cuánto corresponde a la red. Mide el tiempo de conexión TCP simple al puerto del broker como línea base:

Línea base TCP (incluye DNS en la primera llamada)
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')"

Una conexión TCP equivale a un viaje de ida y vuelta por la red. Si tu RTT MQTT en bucle con QoS 0 se acerca a esa línea base, el broker apenas añade nada y el resto de la latencia es distancia. Si es mucho mayor, revisa el broker, el cliente o el camino: solución de problemas de latencia alta repasa las causas habituales.

La luz en la fibra recorre aproximadamente 200 km por milisegundo, así que un broker a 2.000 km cuesta al menos ~20 ms de RTT antes de que se ejecute ningún software. Ningún ajuste del broker vence a la física: elige una región cercana a tus dispositivos.

Reporta percentiles, no promedios

Las distribuciones de latencia tienen colas largas. Una media de 12 ms puede ocultar un p99 de 300 ms, y el p99 es lo que provoca timeouts en los dispositivos y hace que los dashboards se entrecorten. Reporta siempre al menos:

  • min: el suelo que fija la red; debe ser estable entre ejecuciones.
  • p50: la experiencia típica.
  • p95 / p99: la cola; compara estos valores entre configuraciones.
  • max y lost: los valores atípicos aislados y los timeouts son señales, no ruido.

Recoge suficientes muestras para el percentil que reportas. Con 100 muestras, el p99 es en la práctica un solo mensaje; apunta a al menos 1.000 muestras antes de citar un p99.

Qué cambia la latencia

FactorEfecto en el RTT
QoS 0 → QoS 1El PUBACK no retrasa la entrega, pero la persistencia del broker y los límites de inflight pueden añadir tiempo bajo carga
QoS 2Handshake de cuatro paquetes por salto; notablemente más lento, ver benchmark QoS 0 vs 1 vs 2
TLSCoste del handshake al conectar; pequeño coste de cifrado por mensaje
WebSocketsEncapsulado adicional y, a menudo, un salto por un proxy
Tamaño del payloadLos payloads grandes tardan más en enlaces lentos
Carga del brokerLas colas crecen y la latencia de cola sube primero
Callbacks del clienteUn on_message lento retrasa todos los mensajes posteriores

Para ver cómo se comporta la latencia bajo carga, ejecuta este script mientras un generador de carga envía tráfico; el método se explica en pruebas de carga de brokers MQTT. El RTT en reposo es tu mejor caso; el RTT bajo carga es el que usas para planificar.

Comparar brokers y regiones de forma justa

Una comparación solo es justa cuando todo, salvo el broker, es idéntico:

  1. Ejecuta desde la misma máquina y red, idealmente una VM en la región donde están tus dispositivos o tu backend.
  2. Usa el mismo QoS, payload, puerto y configuración TLS para cada destino.
  3. Ejecuta los destinos intercalados (A, B, A, B) en lugar de todo A y luego todo B, para que un fallo temporal de red afecte a ambos.
  4. Registra min, p50, p99 y lost de cada uno, junto con la línea base TCP, en una sola tabla.
  5. Repite a otra hora del día antes de sacar conclusiones.

Para decisiones multirregión, crea una VM pequeña en cada región donde tengas usuarios y ejecuta el mismo script contra cada endpoint de broker. La matriz resultante de p50/p99 por (región del cliente, región del broker) es el dato más honesto que vas a obtener.

Checklist de la prueba de latencia

  • Usa un reloj monotónico y un único proceso para el RTT.
  • Descarta los mensajes de calentamiento; mide el tiempo de conexión por separado.
  • Recoge más de 1.000 muestras y reporta min, p50, p95, p99, max y lost.
  • Registra una línea base TCP junto a cada resultado MQTT.
  • Prueba en reposo y bajo carga, desde la red que usan realmente tus dispositivos.
  • Guarda el script en tu repositorio y vuelve a ejecutarlo tras cambios en el broker o la red.

Comprueba el ida y vuelta en tu navegador

Una prueba de latencia MQTT fiable es sobre todo cuestión de disciplina: un solo reloj, suficientes muestras, percentiles en lugar de promedios y una línea base que te diga qué parte de la cifra pertenece a la red. Si haces bien eso, un script de 15 minutos responde preguntas que los benchmarks de los fabricantes nunca responderán.

Preguntas frecuentes

¿Cómo mido la latencia MQTT sin relojes sincronizados?

Mide el tiempo de ida y vuelta con un solo reloj: un cliente publica en un topic al que también está suscrito (o en un cliente eco que lo republica), y el mismo proceso marca el envío y la recepción con un reloj monotónico.

¿El tiempo de ida y vuelta MQTT es el doble de la latencia en un sentido?

Solo aproximadamente. RTT / 2 supone que ambos sentidos son simétricos, lo que a menudo no ocurre en redes móviles o asimétricas. Para cifras reales en un sentido necesitas relojes sincronizados muy por debajo de la latencia que mides.

¿Por qué mi primer mensaje MQTT es mucho más lento que el resto?

Los primeros mensajes pagan la resolución DNS, el calentamiento de TCP y TLS, la creación de la suscripción y las cachés frías. Descarta un lote de calentamiento antes de registrar muestras y mide el tiempo de conexión por separado.