Um benchmark MQTT gera números rapidamente. Gerar números em que você possa confiar é mais difícil. A maioria dos resultados enganosos vem dos mesmos poucos erros: citar médias, medir o gerador de carga em vez do broker, ignorar a coordinated omission e comparar execuções que não foram configuradas da mesma forma.
Este guia percorre cada armadilha, mostra como detectá-la nos seus próprios dados e termina com um modelo de relatório que torna os resultados reproduzíveis. Ele parte do princípio de que você já executou um teste — se não, comece por teste de carga de broker MQTT.
Médias escondem as mensagens lentas
Latência nunca tem distribuição normal. Um broker sob carga entrega a maioria das mensagens em poucos milissegundos e uma pequena fração em centenas de milissegundos — durante uma pausa do garbage collector, uma gravação em disco ou uma retransmissão. A média mistura os dois grupos em um número que não descreve nenhum deles.
| Execução | Média | p50 | p99 | Máx. |
|---|---|---|---|---|
| A | 6 ms | 4 ms | 12 ms | 30 ms |
| B | 6 ms | 3 ms | 140 ms | 900 ms |
(Valores ilustrativos.) As duas execuções têm a mesma média, mas na B uma em cada cem mensagens chega com mais de um décimo de segundo de atraso. Com um dispositivo publicando uma vez por segundo, isso é um atraso visível mais ou menos a cada dois minutos.
Regras que mantêm você honesto:
- Relate percentis: p50 (típico), p99 (a cauda), máximo (o pior caso observado). Inclua p99.9 só quando tiver amostras suficientes.
- Nunca tire a média de percentis entre execuções ou clientes. A média de dez valores p99 não é o p99 dos dados combinados — junte as amostras brutas ou os histogramas.
- Confira o tamanho da amostra. O p99 de 100 mensagens é uma única mensagem. Colete milhares por execução.
Coordinated omission
A coordinated omission é o motivo mais comum para um benchmark parecer melhor do que a realidade. Ela acontece quando o gerador de carga espera uma resposta antes de enviar a próxima requisição. Se o broker trava por 500 ms, um publisher síncrono registra uma mensagem lenta — e simplesmente não envia as outras 49 que uma frota real teria enviado nesse meio segundo. Essas mensagens, que teriam latência alta, nunca aparecem nos dados.
Dispositivos reais não se coordenam com o broker. Um sensor publica no próprio ritmo, tenha o último PUBACK chegado ou não. Para reproduzir isso, meça a latência a partir do horário de envio planejado, e não do momento em que a mensagem foi de fato entregue ao 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 # mensagens, mensagens 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 = fila ilimitada no cliente
pub.connect(HOST, PORT)
pub.loop_start()
time.sleep(1)
start = time.perf_counter()
for i in range(N):
intended = start + i / RATE # o cronograma que um dispositivo real seguiria
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 pontos 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")Se o publisher atrasa em relação ao cronograma — porque o broker ou a rede seguraram —, cada mensagem atrasada carrega esse atraso na medição. Compare a saída com uma versão que marca time.perf_counter() no momento do publish: se o p99 com horário planejado for muito maior, o broker estava travando e a medição ingênua escondia isso.
Você está medindo o broker ou o cliente?
Uma máquina de benchmark que fica sem CPU, sockets ou banda de rede produz um platô idêntico a um limite do broker. Antes de culpar o broker, verifique o gerador de carga:
- CPU do gerador. Se um núcleo fica em 100 % (comum em clientes single-thread, como um único loop de rede do paho lidando com centenas de mensagens por segundo), o teto é seu. Distribua os clientes entre processos ou máquinas.
- Platôs em números redondos. Contagens de conexão que param em 1.024, 4.096 ou por volta de 28.000–64.000 geralmente indicam limite de descritores de arquivo ou de portas efêmeras no host cliente. Aumente o
ulimit -nou adicione IPs de origem. - Interface de rede. Multiplique a taxa de mensagens pelo tamanho no fio (a calculadora de tamanho de pacote dá os bytes exatos por PUBLISH) e compare com a capacidade do link — incluindo o overhead do TLS.
- O teste com duas máquinas. Rode a mesma carga a partir de dois geradores, cada um com metade da taxa. Se o throughput total subir, o gerador único era o gargalo.
O lado do broker conta o resto da história: registre CPU, memória, número de conexões e taxas de mensagens (por exemplo pelos topics $SYS ou pelo Prometheus) ao lado dos números do cliente. Um broker a 30 % de CPU com o gráfico de throughput travado está esperando outra coisa.
Relógios, aquecimento e outras distorções silenciosas
- Diferença de relógio. Se publisher e subscriber rodam em máquinas diferentes, a latência fim a fim inclui a diferença entre os relógios. O NTP mantém os relógios a poucos milissegundos no melhor caso — muitas vezes pior. Meça com publisher e subscriber em um único processo ou use ida e volta. Mais em problemas de relógio e timestamp no MQTT.
- Aquecimento. Os primeiros segundos incluem handshakes TCP e TLS, compilação JIT e cache misses. Descarte um período de aquecimento ou relate-o separadamente.
- Execuções curtas demais. Uma execução de 30 segundos pode perder eventos periódicos como snapshots de persistência ou rotação de logs. Mantenha cada nível de carga por minutos; use um soak test por horas.
- Mensagens perdidas. Percentis calculados só com as mensagens que chegaram ficam ótimos se as lentas foram descartadas. Relate sempre
recebidas / enviadasjunto com a latência. - QoS diferente. QoS 0, 1 e 2 têm número de pacotes e custos muito diferentes; nunca compare resultados entre níveis de QoS como se fossem o mesmo teste. Veja o benchmark QoS 0 vs 1 vs 2.
Como identificar um resultado ruim
| Sintoma nos dados | Causa provável | Como confirmar |
|---|---|---|
| Throughput estável com CPU do broker baixa | Gerador de carga saturado | Verifique a CPU do gerador; repita com dois geradores |
| Recebidas < enviadas em QoS 0 | Cliente ou broker descartando sob pressão | Repita em QoS 1 e compare as contagens |
| p99 bom, máximo enorme | Uma única trava (GC, failover, disco) | Plote a latência ao longo do tempo, não só um histograma |
| p50 sobe continuamente durante a execução | Fila se acumulando (consumidor lento) | Observe métricas de fila / inflight do broker |
| Resultados variam 30 % entre execuções idênticas | Ambiente compartilhado ou ruidoso | Rode três vezes; teste em um broker isolado |
| Latência negativa ou impossivelmente baixa | Relógios de máquinas diferentes | Meça em um único processo |
Como relatar resultados de benchmark MQTT
Um número sem a configuração não pode ser reproduzido nem comparado. Relate pelo menos isto:
| Campo | Exemplo |
|---|---|
| Broker, versão, implantação | Broker X 2.x, 4 vCPU / 8 GB, nó único |
| Ferramenta cliente e versão | emqtt-bench / script próprio com paho-mqtt 2.x |
| Gerador de carga | 2 máquinas, mesma região, pico de CPU 55 % |
| Rede e transporte | Mesma região, TCP 1883 (ou TLS 8883) |
| Protocolo e QoS | MQTT 5, QoS 1, clean start |
| Carga | 1.000 publishers, 1 msg/s cada, JSON de 512 B, topics 1:1 |
| Duração | 5 min de aquecimento excluídos, 20 min medidos |
| Resultados | Throughput enviado/recebido, p50, p99, p99.9, máx., erros |
| Dados brutos | Link para o histograma ou CSV |
Dois hábitos tornam os relatórios muito mais úteis:
- Plote a latência ao longo do tempo. Um histograma esconde quando as mensagens lentas aconteceram. Uma série temporal mostra se a cauda é uma propriedade constante ou alguns poucos eventos.
- Mostre o ponto de saturação. Em vez de um único número, rode níveis de carga crescentes e relate a taxa em que o p99 ultrapassa sua meta (por exemplo 50 ms). Essa taxa é a sua capacidade utilizável; o throughput máximo absoluto não é.
Uma verificação rápida antes de cada execução
Antes de iniciar um benchmark longo, conecte-se com o cliente MQTT online, assine o seu topic de teste e publique algumas mensagens manualmente. Se uma mensagem demora visivelmente ou a assinatura não recebe nada, resolva isso primeiro — um teste de carga não vai dizer mais do que isso.
Teste rápido do seu topic de benchmark
Benchmarks são tão bons quanto as perguntas por trás deles. Meça a partir do horário de envio planejado, fique de olho no gerador de carga, relate percentis junto com as contagens de entrega e a configuração completa, e seus números MQTT vão resistir à próxima pessoa que tentar reproduzi-los — inclusive você no futuro.
Perguntas frequentes
Por que a latência média de um benchmark MQTT é enganosa?
Distribuições de latência são assimétricas: a maioria das mensagens é rápida e algumas são muito lentas. A média mistura os dois grupos e esconde a cauda lenta que dispositivos e usuários realmente percebem. Relate p50, p99 e máximo.
O que é coordinated omission em um benchmark MQTT?
Acontece quando o gerador de carga espera uma resposta lenta antes de enviar a próxima mensagem. A trava é medida uma vez, em vez de em cada mensagem que deveria ter sido enviada durante ela, então a ferramenta subestima a latência justamente quando o broker está com problemas.
Quantas mensagens preciso para um p99 confiável?
Pelo menos alguns milhares de amostras por execução. Com 100 mensagens, o p99 é uma única amostra. Mire em 10.000 ou mais para o p99 e 100.000 ou mais se quiser citar o p99.9.



