Testes de brokers

O MQTT garante a ordem das mensagens? Teste você mesmo

O que o MQTT garante sobre a ordem das mensagens por topic e QoS, onde a ordem quebra e scripts Python que detectam mensagens fora de ordem e duplicadas.

Atualizado · 8 min de leitura

O MQTT garante a ordem das mensagens? Teste você mesmo

O MQTT garante a ordem das mensagens apenas dentro de um escopo restrito: mensagens de um publisher, em um topic, com um nível de QoS são entregues a cada subscriber na ordem em que o broker as recebeu. Entre topics, entre publishers e dentro de inscrições compartilhadas não há garantia de ordem — e mesmo dentro do escopo garantido, as retransmissões do QoS 1 podem gerar duplicatas atrasadas.

Este artigo explica exatamente o que a especificação promete, onde a ordem quebra na prática e traz dois scripts Python curtos que medem mensagens fora de ordem e duplicadas no seu próprio broker.

O que a especificação diz

Tanto o MQTT 3.1.1 quanto o MQTT 5 (seção 4.6, "Message ordering") definem cada topic como um Ordered Topic por padrão. Quando um broker encaminha mensagens em um topic ordenado, ele precisa enviar os pacotes PUBLISH aos subscribers "para o mesmo topic e QoS" na ordem em que os recebeu de cada cliente. Os clientes, por sua vez, precisam reenviar mensagens não confirmadas e enviar confirmações em ordem.

A especificação também aponta a pegadinha. Com QoS 1, um publisher que envia 1, 2, 3, 4 pode fazer um subscriber ver 1, 2, 3, 2, 3, 4: após uma reconexão, as cópias retransmitidas de 2 e 3 chegam depois das originais. A cópia final de cada mensagem está em ordem, mas uma duplicata pode aparecer depois da sua sucessora. Se nunca houver mais de uma mensagem em voo por vez, isso não acontece.

CenárioOrdem garantida?
Um publisher, um topic, mesmo QoSSim, por subscriber
Um publisher, vários topicsNão — o intercalamento entre topics é indefinido
Vários publishers, um topicSó por publisher; o intercalamento entre eles é arbitrário
Mesmo publisher, QoS misturados em um topicNão — a regra vale por QoS
QoS 1 após uma reconexãoCópias finais em ordem; duplicatas podem chegar atrasadas
QoS 0 sob cargaA ordem do que chega é mantida, mas podem faltar mensagens
Grupo de inscrição compartilhadaNão — as mensagens são distribuídas entre os membros

Níveis de QoS no MQTT explica os fluxos de confirmação por trás dessas regras.

Onde a ordem quebra na prática

  • Várias mensagens em voo e reconexões. Clientes e brokers costumam permitir muitas mensagens QoS 1/2 não confirmadas ao mesmo tempo (no MQTT 5 o limite é negociado com o Receive Maximum). Se a conexão cai com várias em voo, o reenvio pode chegar depois de mensagens mais novas.
  • Inscrições compartilhadas. $share/group/topic distribui as mensagens entre consumidores. Cada consumidor pode ver a sua parte em ordem, mas o processamento roda em paralelo, então o resultado geral fica fora de ordem. Veja inscrições compartilhadas.
  • Vários publishers. Dois gateways reportando o mesmo dispositivo produzem dois fluxos ordenados de forma independente. O broker pode intercalá-los de qualquer jeito.
  • Bridges e clusters. Um bridge é só mais um cliente, então uma conexão de bridge mantém a ordem; vários caminhos entre brokers, não. Brokers em cluster geralmente preservam a ordem por publisher em um topic, mas confira a documentação do seu broker.
  • O seu próprio consumidor. A causa mais comum de todas — as mensagens chegam em ordem e depois são processadas de forma concorrente (pools de threads, handlers async, filas de workers).

Teste 1: meça a ordem no seu broker

O publisher numera as mensagens por topic e por ID de publisher. O subscriber acompanha a maior sequência vista por (topic, publisher) e reporta tudo que volta para trás, cada duplicata e tudo que falta.

order_sub.py
import os, sys, time
from collections import Counter, defaultdict
import paho.mqtt.client as mqtt

HOST = os.getenv("MQTT_HOST", "broker.connectmq.com")
RUN = sys.argv[1]                      # mesmo id de execução do publisher
QOS = int(os.getenv("MQTT_QOS", "1"))
last = defaultdict(lambda: -1)         # (topic, publisher) -> maior sequência
seen = defaultdict(set)
stats = Counter()

def on_connect(client, userdata, flags, reason_code, properties):
    client.subscribe(f"testmqtt/order/{RUN}/#", qos=QOS)

def on_message(client, userdata, msg):
    pub_id, seq = msg.payload.decode().split(":")
    key, seq = (msg.topic, pub_id), int(seq)
    stats["received"] += 1
    stats["dup_flag"] += msg.dup
    if seq in seen[key]:
        stats["duplicates"] += 1
        return
    seen[key].add(seq)
    if seq < last[key]:
        stats["out_of_order"] += 1
        print(f"OUT OF ORDER {key}: {seq} after {last[key]}")
    last[key] = max(last[key], seq)

def missing():
    return sum(last[k] + 1 - len(seen[k]) for k in seen)

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_message = on_connect, on_message
client.connect(HOST, 1883)
client.loop_start()
try:
    while True:
        time.sleep(5)
        print(dict(stats), "missing:", missing())
except KeyboardInterrupt:
    print("FINAL", dict(stats), "missing:", missing())
order_pub.py
import os, sys
import paho.mqtt.client as mqtt

HOST = os.getenv("MQTT_HOST", "broker.connectmq.com")
RUN, N = sys.argv[1], int(sys.argv[2]) if len(sys.argv) > 2 else 10000
PUB_ID = os.getenv("PUB_ID", "p1")
TOPICS = int(os.getenv("TOPICS", "4"))
QOS = int(os.getenv("MQTT_QOS", "1"))

pub = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
if os.getenv("MQTT_USER"):
    pub.username_pw_set(os.getenv("MQTT_USER"), os.getenv("MQTT_PASS"))
pub.max_inflight_messages_set(int(os.getenv("INFLIGHT", "20")))
pub.connect(HOST, 1883)
pub.loop_start()

seq, infos = [0] * TOPICS, []
for i in range(N):
    t = i % TOPICS
    infos.append(pub.publish(f"testmqtt/order/{RUN}/t{t}", f"{PUB_ID}:{seq[t]}", qos=QOS))
    seq[t] += 1
for info in infos:
    info.wait_for_publish(timeout=60)
pub.disconnect()
pub.loop_stop()
print(f"published {N} messages on {TOPICS} topics as {PUB_ID}")
Rodar a linha de base
pip install "paho-mqtt>=2"
python order_sub.py run42            # terminal 1, inicie primeiro
python order_pub.py run42 10000      # terminal 2

Em um broker saudável e uma rede estável, a linha de base deve terminar com out_of_order: 0, duplicates: 0 e missing: 0. Antes dos scripts, você pode acompanhar o fluxo ao vivo inscrevendo-se em testmqtt/order/# no cliente MQTT online enquanto o publisher roda.

Teste 2: provoque os casos extremos

Mude uma variável por vez e compare com a linha de base:

ExperimentoComoO que você deve ver
Dois publishersRode order_pub.py duas vezes em paralelo com PUB_ID=p1 e PUB_ID=p2O fluxo de cada publisher continua ordenado; eles se intercalam
QoS 0 sob cargaMQTT_QOS=0, N maior, vários publishersPossivelmente missing > 0, mas nada fora de ordem
Queda de redeBloqueie a porta 1883 do publisher por alguns segundos no meio da execução (veja QoS com queda de rede)Duplicatas e contagem de dup_flag; reenvios atrasados possíveis com sessão persistente
Uma em vooINFLIGHT=1 mais uma queda de redeDuplicatas possíveis, mas nenhuma mensagem depois da sua sucessora
Inscrição compartilhadaInscreva duas cópias de order_sub.py em $share/g/testmqtt/order/run42/#Cada cópia vê lacunas — o outro membro recebeu essas mensagens

Para o teste com inscrição compartilhada, troque a chamada de subscribe pelo filtro $share/...; você pode verificar se um filtro casa com seus topics no topic matcher. Duplicatas não são bug do broker — é o QoS 1 fazendo o seu trabalho; mensagens duplicadas mostra como tornar os consumidores idempotentes.

Não perca a ordem no seu próprio código

A maioria dos relatos de "o MQTT bagunçou a ordem das minhas mensagens" vem do consumidor. No Node.js, um handler de mensagens async não espera a mensagem anterior:

Fora de ordem: os handlers se sobrepõem
client.on("message", async (topic, payload) => {
  await saveToDb(topic, payload); // a mensagem 2 pode terminar antes da 1
});

Encadeie o trabalho por topic para manter a ordem por topic e, ainda assim, processar topics diferentes em paralelo:

Ordenado por topic
const chains = new Map();
client.on("message", (topic, payload) => {
  const prev = chains.get(topic) ?? Promise.resolve();
  const next = prev.then(() => saveToDb(topic, payload)).catch(console.error);
  chains.set(topic, next);
});

O mesmo vale para pools de threads em Python ou Java e para filas de workers depois do broker: particione o trabalho por topic ou ID de dispositivo, nunca em round-robin.

Projetando para a ordem

  • Coloque eventos que precisam ficar em ordem em um único topic, a partir de um único publisher — por exemplo site/device42/events, e não um topic por tipo de evento.
  • Adicione um número de sequência ou timestamp de origem ao payload, para que os consumidores possam descartar atualizações antigas ("o último a escrever vence") ou reordenar janelas pequenas.
  • Torne os handlers idempotentes, porque o QoS 1 vai entregar duplicatas.
  • Trate inscrições compartilhadas como fora de ordem por definição; particione por dispositivo se a ordem importar.
  • Use mensagens retidas para o "estado atual", em que só o valor mais recente conta e a ordem importa menos.

A garantia de ordem do MQTT é real, mas estreita: um publisher, um topic, um QoS. Dez minutos com um teste de números de sequência mostram como o seu broker e a sua rede se comportam, e as variantes mostram exatamente onde o seu design precisa assumir — normalmente com um número de sequência no payload e um consumidor que processa cada topic em ordem.

Perguntas frequentes

O MQTT garante a ordem das mensagens?

Em parte. A especificação exige que o broker entregue as mensagens de um mesmo publisher, no mesmo topic e com o mesmo QoS, na ordem em que as recebeu. Não há garantia de ordem entre topics, entre publishers diferentes nem entre membros de uma inscrição compartilhada.

Mensagens QoS 1 podem chegar fora de ordem?

Sim, de um jeito específico: depois de uma reconexão, uma mensagem QoS 1 retransmitida pode chegar depois de mensagens posteriores, por exemplo 1, 2, 3, 2, 3, 4. As cópias finais estão em ordem, mas duplicatas podem aparecer atrasadas. Manter uma única mensagem em voo por vez evita isso.

Como manter a ordem das mensagens MQTT entre vários topics?

O MQTT não faz isso por você. Coloque eventos relacionados em um único topic a partir de um único publisher, ou adicione um número de sequência ou timestamp ao payload e deixe o consumidor reordenar ou descartar mensagens antigas.