Pruebas de brokers

Cómo interpretar resultados de benchmarks MQTT sin engañarte

Interpreta bien los resultados de un benchmark MQTT: por qué mienten las medias, cómo la coordinated omission oculta latencia y cómo reportar los datos.

Actualizado · 8 min de lectura

Cómo interpretar resultados de benchmarks MQTT sin engañarte

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ónMediap50p99Máx.
A6 ms4 ms12 ms30 ms
B6 ms3 ms140 ms900 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:

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                   # 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 -n o 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 / enviados junto 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 datosCausa probableCómo confirmarlo
Throughput plano con CPU del broker bajaGenerador de carga saturadoRevisa la CPU del generador; repite con dos generadores
Recibidos < enviados con QoS 0Cliente o broker descartando bajo presiónRepite con QoS 1 y compara los recuentos
p99 bueno, máximo enormeUn único bloqueo (GC, failover, disco)Grafica la latencia en el tiempo, no solo un histograma
El p50 sube de forma constanteSe acumula una cola (consumidor lento)Vigila las métricas de cola / inflight del broker
Resultados que varían un 30 % entre ejecuciones idénticasEntorno compartido o ruidosoEjecuta tres veces; prueba en un broker aislado
Latencia negativa o imposiblemente bajaRelojes de máquinas distintasMide 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:

CampoEjemplo
Broker, versión, despliegueBroker X 2.x, 4 vCPU / 8 GB, un nodo
Herramienta cliente y versiónemqtt-bench / script propio con paho-mqtt 2.x
Generador de carga2 máquinas, misma región, pico de CPU 55 %
Red y transporteMisma región, TCP 1883 (o TLS 8883)
Protocolo y QoSMQTT 5, QoS 1, clean start
Carga de trabajo1.000 publishers, 1 msg/s cada uno, JSON de 512 B, topics 1:1
Duración5 min de calentamiento excluidos, 20 min medidos
ResultadosThroughput enviado/recibido, p50, p99, p99.9, máx., errores
Datos en brutoEnlace al histograma o CSV

Dos hábitos hacen los informes mucho más útiles:

  1. 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.
  2. 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.

Prueba tu topic de benchmark

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.