Testes de brokers

Testando MQTT em redes celulares lentas e instáveis

Emule latência, jitter e perda de pacotes de 2G, 3G e LTE com tc netem e ajuste keep-alive, tamanho de payload, QoS e sessões MQTT para IoT móvel.

Atualizado · 9 min de leitura

Testando MQTT em redes celulares lentas e instáveis

Um cliente MQTT que é sólido como pedra no Wi-Fi do escritório pode desmoronar em um modem celular: conexões que dão timeout, desconexões por keep-alive a cada poucos minutos, mensagens QoS 1 que chegam em rajadas. A correção começa por reproduzir as condições celulares na sua mesa — latência, jitter, perda de pacotes e banda limitada — e depois ajustar keep-alive, tamanho de payload, QoS e sessões até o cliente aguentar.

Este guia mostra como emular links 2G, 3G e LTE com tc netem, quais configurações de MQTT testar em cada perfil e um script que mede entrega e latência para você comparar configurações.

O que torna o celular diferente

Links celulares não são só "Wi-Fi lento". Eles falham de maneiras específicas que atingem a conexão TCP de longa duração do MQTT:

  • RTT alto e variável. Toda troca MQTT que espera resposta — CONNACK, SUBACK, PUBACK, PINGRESP — paga o RTT inteiro, e o jitter a torna imprevisível.
  • Perdas em rajada. Handovers e sinal fraco derrubam vários pacotes seguidos. O TCP os retransmite, então o MQTT vê travamentos de segundos em vez de mensagens perdidas.
  • Atraso para acordar o rádio. Depois de um período em silêncio o modem entra em um estado de rádio ocioso; o primeiro pacote seguinte espera o rádio reconectar, o que adiciona latência perceptível a essa mensagem.
  • NAT da operadora (CGNAT). Operadoras móveis colocam os dispositivos atrás de gateways NAT que esquecem fluxos ociosos. Se nada passa pela conexão por mais tempo que o timeout de inatividade do NAT, ela fica half-open: o dispositivo acha que está conectado e o broker nunca mais tem notícias dele. Veja MQTT e timeouts de NAT.
  • Dados tarifados. Cada byte de cabeçalho, keep-alive e handshake TLS custa dinheiro e bateria em uma frota de milhares de dispositivos.

Emulando links celulares com tc netem

No Linux, o tc com a disciplina de fila netem adiciona atraso, jitter, perda e limite de taxa a uma interface. Os perfis abaixo são pontos de partida aproximados para condições típicas, não medições de nenhuma rede específica — troque pelos números dos seus próprios dispositivos quando os tiver.

PerfilAtraso (um sentido)JitterPerdaTaxa
LTE bom40 ms10 ms0,1 %10 mbit
LTE fraco120 ms40 ms1 %1 mbit
3G200 ms50 ms1 %384 kbit
2G / EDGE400 ms150 ms3 %64 kbit
Zona morta1500 ms500 ms15 %16 kbit

A configuração mais limpa é rodar o cliente MQTT dentro de um container e modelar só a rede desse container, sem mexer no host:

A shaped test container
docker run --rm -it --cap-add NET_ADMIN --name cell python:3.12-slim bash

# inside the container
apt-get update && apt-get install -y iproute2 mosquitto-clients
pip install paho-mqtt
Apply and change profiles (inside the container)
# 3G-like: 200 ms ± 50 ms, 1 % loss with 25 % correlation (bursty), 384 kbit/s
tc qdisc add dev eth0 root netem delay 200ms 50ms loss 1% 25% rate 384kbit

tc qdisc change dev eth0 root netem delay 400ms 150ms loss 3% 25% rate 64kbit   # 2G
tc qdisc show dev eth0                                                         # verify
tc qdisc del dev eth0 root                                                     # back to normal

O netem modela apenas o tráfego de saída. Do lado do cliente isso basta para a maioria dos testes: o atraso é aplicado uma vez por ida e volta, e a perda de segmentos de saída dispara o mesmo comportamento de retransmissão do TCP. Se precisar modelar o downlink separadamente, coloque o netem do lado do broker de uma bridge ou use o Toxiproxy, que atrasa e limita cada direção de forma independente.

Confira o perfil antes de testar o MQTT: ping -c 10 broker.connectmq.com deve mostrar o atraso adicionado, e um mosquitto_pub ainda deve passar.

Medindo a entrega em cada perfil

Este script (paho-mqtt 2.x) conecta um subscriber e um publisher, envia um lote de mensagens QoS 1 e informa quantas chegaram, o tempo de conexão e os percentis de latência ponta a ponta. Rode dentro do container modelado uma vez por perfil.

cell_test.py
import json, sys, threading, time, uuid
import paho.mqtt.client as mqtt

HOST, PORT = "broker.connectmq.com", 1883
TOPIC = f"testmqtt/cellular/{uuid.uuid4().hex[:8]}"
N, QOS, SIZE = int(sys.argv[1]), int(sys.argv[2]), int(sys.argv[3])
latencies, done = [], threading.Event()

def on_message(client, userdata, msg):
    latencies.append((time.time() - json.loads(msg.payload)["t"]) * 1000)
    if len(latencies) == N:
        done.set()

def connect(name, keepalive=60):
    ready = threading.Event()
    c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"{name}-{uuid.uuid4().hex[:6]}")
    c.on_connect = lambda *args: ready.set()
    t0 = time.time()
    c.connect(HOST, PORT, keepalive=keepalive)
    c.loop_start()
    if not ready.wait(30):
        sys.exit(f"{name}: no CONNACK within 30 s")
    print(f"{name}: connected in {(time.time() - t0) * 1000:.0f} ms")
    return c

sub = connect("sub")
sub.on_message = on_message
sub.subscribe(TOPIC, qos=QOS)
pub = connect("pub")
time.sleep(2)

pad = "x" * SIZE
for i in range(N):
    pub.publish(TOPIC, json.dumps({"i": i, "t": time.time(), "pad": pad}), qos=QOS)
    time.sleep(0.2)

done.wait(60)
latencies.sort()
pct = lambda q: latencies[min(len(latencies) - 1, int(len(latencies) * q))]
print(f"received {len(latencies)}/{N} at QoS {QOS}, {SIZE}-byte padding")
if latencies:
    print(f"p50 {pct(0.5):.0f} ms  p95 {pct(0.95):.0f} ms  max {latencies[-1]:.0f} ms")
Run it
python cell_test.py 100 1 200    # 100 messages, QoS 1, ~200-byte payloads
python cell_test.py 100 0 200    # same at QoS 0 for comparison
python cell_test.py 20 1 20000   # few large payloads

Publisher e subscriber compartilham o relógio do container, então não há erro de diferença de relógio nas latências. As duas conexões passam pela interface modelada, o que é pessimista — em produção o subscriber normalmente fica em um data center —, mas deixa bem visíveis as diferenças entre configurações. Mantenha o cliente MQTT online inscrito em testmqtt/cellular/# por uma conexão normal para ver as mensagens chegando.

Ajuste o keep-alive para a operadora, não para o laboratório

No celular, o keep-alive tem duas funções: manter viva a entrada do NAT e detectar links mortos. O intervalo de keep-alive precisa ser menor que o timeout de inatividade do NAT da operadora; caso contrário, a conexão morre em silêncio entre os pings.

Para descobrir esse timeout, conecte, fique totalmente ocioso (keep-alive maior que a janela do teste) e publique depois de pausas crescentes — 1, 2, 5, 10, 20 minutos. A primeira pausa depois da qual o publish nunca chega ao broker (e o cliente só percebe no próximo ping ou escrita) é o seu limite de NAT. Configure o keep-alive bem abaixo dele.

Teste também a falha oposta: nos perfis 2G e "zona morta", garanta que o keep-alive não cause desconexões. Se o PINGRESP frequentemente leva vários segundos, um cliente com timeouts agressivos pode derrubar conexões saudáveis. O MQTT permite que o broker espere uma vez e meia o keep-alive antes de desistir, então valores muito curtos (abaixo de 15–20 s) deixam pouca folga em links lentos.

Tamanho do payload e overhead do protocolo

Em um link de 64 kbit/s, uma mensagem de 20 KB ocupa o uplink por alguns segundos, e tudo que vem atrás espera. No perfil 2G, compare payloads pequenos e grandes com o script e você verá a latência p95 subir com o tamanho.

  • Mantenha os payloads compactos. Chaves JSON curtas ou formatos binários como CBOR e Protobuf ajudam. A calculadora de tamanho de pacote mostra quanto o tamanho do topic e as propriedades somam por mensagem.
  • Agrupe com critério. Uma mensagem com dez leituras tem menos overhead que dez mensagens, mas uma conexão perdida no meio do lote dói mais. Teste as duas abordagens.
  • Conte o TLS. Um handshake TLS completo custa várias idas e voltas e alguns kilobytes. Em um link de 400 ms isso significa segundos por conexão, então reconectar com frequência sai caro. Meça o tempo de conexão com TLS na porta 8883 em cada perfil e veja o overhead de banda do MQTT para os números por trás disso.
  • Use topic aliases do MQTT 5 se topics longos são enviados repetidamente, depois de confirmar que seu broker os suporta.

QoS e sessões em links com perda

ConfiguraçãoComportamento no celularTeste a rodar
QoS 0Menos bytes; o que estiver em trânsito quando a conexão cai se perdeCorte o link no meio do lote e conte as perdas
QoS 1Um PUBACK extra por mensagem; reenviada após reconectar, duplicatas possíveisMesmo corte, procurando duplicatas
QoS 2Quatro pacotes por mensagem, cada um pagando o RTTCompare o throughput com QoS 1 no 2G
Sessão persistenteAssinaturas e mensagens na fila sobrevivem às reconexõesDesconecte por 2 minutos, reconecte e conte a fila

Para a maior parte da telemetria celular, QoS 1 com processamento idempotente no receptor é a escolha pragmática. Combine com uma sessão persistente — clean_start=False e um session expiry interval no MQTT 5 — para que o dispositivo não precise assinar de novo e as mensagens enfileiradas para ele sobrevivam a uma queda curta. Detalhes em sessões persistentes no MQTT.

Teste reconexões explicitamente nos perfis ruins: derrube o link (tc qdisc change dev eth0 root netem loss 100%), espere, restaure e verifique que o cliente reconecta com backoff em vez de martelar a rede, e que as mensagens QoS 1 publicadas durante a queda chegam quando o link volta.

Checklist de testes celulares

  1. Faça primeiro um smoke test em uma rede normal e depois aplique cada perfil netem.
  2. Registre por perfil o tempo de conexão (com e sem TLS), a contagem de entregas e a latência p50/p95.
  3. Descubra o timeout de inatividade do NAT da operadora com testes de ociosidade seguidos de publish; configure o keep-alive abaixo dele.
  4. Corte o link por 30 s e por 5 min; verifique o backoff de reconexão, a retomada de sessão e a reentrega QoS 1.
  5. Compare tamanhos de payload e agrupamento no perfil 2G.
  6. Repita os casos críticos em hardware real com o SIM de produção.

Redes celulares sempre serão mais lentas e menos previsíveis que o laboratório. Emulá-las a cada mudança transforma "funciona no escritório" em "funciona em um caminhão em movimento", e os números dessas execuções dão uma base sólida para suas decisões de keep-alive, QoS e payload.

Perguntas frequentes

Qual keep-alive usar para MQTT em rede celular?

Curto o bastante para ficar abaixo do timeout de inatividade do NAT da operadora e longo o bastante para não desperdiçar bateria e dados. Muitas implantações ficam entre 60 e 300 segundos, mas os timeouts de NAT variam por operadora: meça com o seu SIM o tempo ocioso depois do qual a conexão morre silenciosamente e configure o keep-alive bem abaixo disso.

Perda de pacotes causa perda de mensagens MQTT?

Não diretamente. O MQTT roda sobre TCP, que retransmite segmentos perdidos, então a perda aparece como picos de latência e travamentos. Mensagens se perdem quando a conexão cai: mensagens QoS 0 em trânsito somem, enquanto QoS 1 e 2 são reenviadas após a reconexão se a sessão persistir.

Posso emular condições celulares no macOS ou no Windows?

No macOS, o Network Link Conditioner da Apple aplica perfis de latência, perda e banda ao sistema inteiro. Em qualquer sistema você pode rodar o cliente dentro de um container Linux com tc netem, ou colocar o Toxiproxy entre cliente e broker para latência e limite de banda.