Un benchmark MQTT produce números rápidamente. Producir números en los que puedas confiar es más difícil. La mayoría de los resultados engañosos provienen de los mismos pocos errores: citar medias, medir el generador de carga en lugar del broker, ignorar la coordinated omission y comparar ejecuciones que no se configuraron igual.
Esta guía recorre cada trampa, muestra cómo detectarla en tus propios datos y termina con una plantilla de informe que hace que los resultados sean reproducibles. Suponemos que ya has ejecutado una prueba; si no, empieza por pruebas de carga de un broker MQTT.
Las medias ocultan los mensajes lentos
La latencia nunca sigue una distribución normal. Un broker bajo carga entrega la mayoría de los mensajes en pocos milisegundos y una pequeña fracción en cientos de milisegundos: durante una pausa del garbage collector, un volcado a disco o una retransmisión. La media mezcla ambos grupos en un número que no describe a ninguno.
| Ejecución | Media | p50 | p99 | Máx. |
|---|---|---|---|---|
| A | 6 ms | 4 ms | 12 ms | 30 ms |
| B | 6 ms | 3 ms | 140 ms | 900 ms |
(Valores ilustrativos.) Ambas ejecuciones tienen la misma media, pero en la B uno de cada cien mensajes llega con más de una décima de segundo de retraso. Con un dispositivo que publica una vez por segundo, eso es un retraso visible aproximadamente cada dos minutos.
Reglas que te mantienen honesto:
- Reporta percentiles: p50 (lo típico), p99 (la cola), máximo (el peor caso observado). Añade p99.9 solo si tienes suficientes muestras.
- Nunca promedies percentiles entre ejecuciones o clientes. La media de diez valores p99 no es el p99 de los datos combinados: combina las muestras en bruto o los histogramas.
- Revisa el tamaño de la muestra. El p99 de 100 mensajes es un único mensaje. Recoge miles por ejecución.
Coordinated omission
La coordinated omission es la razón más común por la que un benchmark parece mejor que la realidad. Ocurre cuando el generador de carga espera una respuesta antes de enviar la siguiente petición. Si el broker se bloquea 500 ms, un publisher síncrono registra un mensaje lento y, sencillamente, no envía los otros 49 que una flota real habría enviado en ese medio segundo. Esos mensajes, que habrían tenido una latencia alta, nunca aparecen en los datos.
Los dispositivos reales no se coordinan con el broker. Un sensor publica según su propio calendario, haya llegado o no el último PUBACK. Para reproducirlo, mide la latencia desde el momento de envío previsto, no desde el momento en que el mensaje se entregó realmente al socket:
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 # mensajes, mensajes por segundo
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 = cola ilimitada en el cliente
pub.connect(HOST, PORT)
pub.loop_start()
time.sleep(1)
start = time.perf_counter()
for i in range(N):
intended = start + i / RATE # el calendario que seguiría un dispositivo real
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 puntos de corte
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")Si el publisher se retrasa respecto al calendario —porque el broker o la red frenaron—, cada mensaje tardío incluye ese retraso en su medición. Compara la salida con una versión que marque time.perf_counter() en el momento de publicar: si el p99 con tiempo previsto es mucho mayor, el broker se estaba bloqueando y la medición ingenua lo ocultaba.
¿Mides el broker o el cliente?
Una máquina de benchmark que se queda sin CPU, sockets o ancho de banda produce una meseta que parece exactamente un límite del broker. Antes de culpar al broker, revisa el generador de carga:
- CPU del generador. Si un núcleo está al 100 % (habitual con clientes de un solo hilo, como un único bucle de red de paho gestionando cientos de mensajes por segundo), el techo es tuyo. Reparte los clientes entre procesos o máquinas.
- Mesetas en números redondos. Conexiones que se detienen en 1.024, 4.096 o en torno a 28.000–64.000 suelen indicar límites de descriptores de archivo o de puertos efímeros en el host cliente. Sube
ulimit -no añade IPs de origen. - Interfaz de red. Multiplica la tasa de mensajes por el tamaño en el cable (la calculadora de tamaño de paquete te da los bytes exactos por PUBLISH) y compáralo con la capacidad del enlace, incluida la sobrecarga de TLS.
- La prueba de dos máquinas. Lanza la misma carga desde dos generadores a la mitad de ritmo cada uno. Si el throughput total sube, el generador único era el cuello de botella.
El lado del broker cuenta el resto de la historia: registra su CPU, memoria, número de conexiones y tasas de mensajes (por ejemplo desde los topics $SYS o Prometheus) junto a los números del cliente. Un broker al 30 % de CPU con una gráfica de throughput estancada está esperando otra cosa.
Relojes, calentamiento y otras distorsiones silenciosas
- Desfase de reloj. Si publisher y subscriber se ejecutan en máquinas distintas, la latencia extremo a extremo incluye la diferencia entre sus relojes. NTP mantiene los relojes a unos pocos milisegundos en el mejor caso, a menudo peor. Mide con publisher y subscriber en un mismo proceso o usa ida y vuelta. Más en problemas de reloj y timestamps en MQTT.
- Calentamiento. Los primeros segundos incluyen handshakes TCP y TLS, compilación JIT y fallos de caché. Descarta un periodo de calentamiento o repórtalo aparte.
- Ejecuciones demasiado cortas. Una ejecución de 30 segundos puede perderse eventos periódicos como snapshots de persistencia o rotación de logs. Mantén cada nivel de carga durante minutos; usa una prueba de resistencia (soak test) durante horas.
- Mensajes perdidos. Los percentiles calculados solo con los mensajes que llegaron se ven estupendos si se descartaron los lentos. Reporta siempre
recibidos / enviadosjunto a la latencia. - QoS distinto. QoS 0, 1 y 2 tienen diferente número de paquetes y costes muy distintos; nunca compares resultados entre niveles de QoS como si fueran la misma prueba. Consulta el benchmark QoS 0 vs 1 vs 2.
Cómo detectar un mal resultado
| Síntoma en los datos | Causa probable | Cómo confirmarlo |
|---|---|---|
| Throughput plano con CPU del broker baja | Generador de carga saturado | Revisa la CPU del generador; repite con dos generadores |
| Recibidos < enviados con QoS 0 | Cliente o broker descartando bajo presión | Repite con QoS 1 y compara los recuentos |
| p99 bueno, máximo enorme | Un único bloqueo (GC, failover, disco) | Grafica la latencia en el tiempo, no solo un histograma |
| El p50 sube de forma constante | Se acumula una cola (consumidor lento) | Vigila las métricas de cola / inflight del broker |
| Resultados que varían un 30 % entre ejecuciones idénticas | Entorno compartido o ruidoso | Ejecuta tres veces; prueba en un broker aislado |
| Latencia negativa o imposiblemente baja | Relojes de máquinas distintas | Mide en un solo proceso |
Cómo reportar resultados de benchmarks MQTT
Un número sin su configuración no se puede reproducir ni comparar. Reporta como mínimo esto:
| Campo | Ejemplo |
|---|---|
| Broker, versión, despliegue | Broker X 2.x, 4 vCPU / 8 GB, un nodo |
| Herramienta cliente y versión | emqtt-bench / script propio con paho-mqtt 2.x |
| Generador de carga | 2 máquinas, misma región, pico de CPU 55 % |
| Red y transporte | Misma región, TCP 1883 (o TLS 8883) |
| Protocolo y QoS | MQTT 5, QoS 1, clean start |
| Carga de trabajo | 1.000 publishers, 1 msg/s cada uno, JSON de 512 B, topics 1:1 |
| Duración | 5 min de calentamiento excluidos, 20 min medidos |
| Resultados | Throughput enviado/recibido, p50, p99, p99.9, máx., errores |
| Datos en bruto | Enlace al histograma o CSV |
Dos hábitos hacen los informes mucho más útiles:
- Grafica la latencia en el tiempo. Un histograma oculta cuándo ocurrieron los mensajes lentos. Una serie temporal muestra si la cola es una propiedad constante o unos pocos eventos.
- Muestra el punto de saturación. En lugar de un único número, ejecuta niveles de carga crecientes y reporta la tasa a la que el p99 supera tu objetivo (por ejemplo 50 ms). Esa tasa es tu capacidad utilizable; el throughput máximo absoluto no lo es.
Una comprobación rápida antes de cada ejecución
Antes de lanzar un benchmark largo, conéctate con el cliente MQTT online, suscríbete a tu topic de prueba y publica a mano unos cuantos mensajes. Si un mensaje tarda visiblemente o la suscripción no recibe nada, arregla eso primero: una prueba de carga no te dirá más.
Los benchmarks son tan buenos como las preguntas que hay detrás. Mide desde el tiempo de envío previsto, vigila el generador de carga, reporta percentiles junto con los recuentos de entrega y la configuración completa, y tus números MQTT sobrevivirán a la próxima persona que intente reproducirlos, incluido tu yo del futuro.
Preguntas frecuentes
¿Por qué la latencia media de un benchmark MQTT es engañosa?
Las distribuciones de latencia son asimétricas: la mayoría de los mensajes son rápidos y unos pocos muy lentos. La media mezcla ambos grupos y oculta la cola lenta que notan los dispositivos y los usuarios. Reporta p50, p99 y máximo.
¿Qué es la coordinated omission en un benchmark MQTT?
Ocurre cuando el generador de carga espera una respuesta lenta antes de enviar el siguiente mensaje. El bloqueo se mide una sola vez en lugar de en cada mensaje que debería haberse enviado durante ese tiempo, así que la herramienta infravalora la latencia justo cuando el broker sufre.
¿Cuántos mensajes necesito para un p99 fiable?
Al menos varios miles de muestras por ejecución. Con 100 mensajes, el p99 es una sola muestra. Apunta a 10.000 o más para el p99 y a 100.000 o más si quieres citar el p99.9.



