Testes de brokers

Como testar o Message Expiry Interval do MQTT 5

Como verificar o Message Expiry Interval do MQTT 5: teste mensagens em fila para clientes offline, a expiração de mensagens retidas e o intervalo restante.

Atualizado · 8 min de leitura

Como testar o Message Expiry Interval do MQTT 5

O Message Expiry Interval do MQTT 5 diz ao broker por quanto tempo uma mensagem continua útil. Se ela ainda estiver aguardando entrega quando esse tempo acabar, o broker deve descartá-la em vez de entregar dados velhos. Para testar, publique mensagens com valores de expiração diferentes enquanto um subscriber persistente está offline, espere, reconecte e verifique quais mensagens chegam e com qual intervalo restante.

Este guia mostra esse teste com mosquitto_pub / mosquitto_sub, um script Python que se autoverifica e um teste separado para mensagens retidas.

Como a expiração funciona de verdade

O publisher define a propriedade Message Expiry Interval (em segundos) em um pacote PUBLISH. A partir daí, a responsabilidade é do broker:

SituaçãoO que o broker deve fazer
Subscriber onlineEntregar imediatamente; a expiração raramente importa
Subscriber offline com sessão persistenteEnfileirar a mensagem e descartá-la quando o intervalo passar
Mensagem retidaServi-la a novos subscribers só até expirar, depois apagá-la
Mensagem encaminhada depois de esperarEnviar o intervalo restante, não o valor original
Sem propriedade de expiraçãoA mensagem nunca expira (limites globais do broker ainda podem valer)

O ponto-chave para testes: a expiração só aparece quando uma mensagem espera. Um teste com um subscriber sempre conectado não prova nada. Você precisa de uma sessão offline ou de uma mensagem retida. Para a teoria e dicas de design, veja expiração de mensagens no MQTT.

Pré-requisitos

  • Um broker com suporte a MQTT 5. Confira o CONNACK de uma conexão MQTT 5 ou rode antes os testes gerais de recursos do MQTT 5.
  • Um subscriber que mantenha a sessão enquanto está offline: client ID fixo, Clean Start false e um Session Expiry Interval diferente de zero. Se a própria sessão expirar, a fila vai junto e o teste mede a coisa errada. Sessões persistentes explica o mecanismo.
  • QoS 1 tanto na assinatura quanto na publicação. Muitos brokers nem enfileiram mensagens QoS 0 para clientes offline.

Os exemplos usam broker.connectmq.com e topics em testmqtt/expiry/. Acrescente um sufixo aleatório para que seu teste não colida com o de outros usuários.

Teste manual rápido com a CLI do Mosquitto

Passo 1: crie uma sessão persistente e assine o topic; depois pare o subscriber com Ctrl+C.

Terminal 1 — criar a sessão e depois Ctrl+C
mosquitto_sub -h broker.connectmq.com -p 1883 -V mqttv5 \
  -i expiry-sub-7f3a -c -x 600 -q 1 \
  -t 'testmqtt/expiry/7f3a/#' -F '%t expiry=%E payload=%p'

-c desativa o Clean Start, -x 600 mantém a sessão por 10 minutos após a desconexão e %E imprime o intervalo de expiração de cada mensagem recebida.

Passo 2: com o subscriber offline, publique três mensagens:

Terminal 2 — publicar com o subscriber offline
mosquitto_pub -h broker.connectmq.com -V mqttv5 -q 1 \
  -t testmqtt/expiry/7f3a/short -m 'expires in 10s' \
  -D publish message-expiry-interval 10

mosquitto_pub -h broker.connectmq.com -V mqttv5 -q 1 \
  -t testmqtt/expiry/7f3a/long -m 'expires in 300s' \
  -D publish message-expiry-interval 300

mosquitto_pub -h broker.connectmq.com -V mqttv5 -q 1 \
  -t testmqtt/expiry/7f3a/none -m 'never expires'

Passo 3: espere uns 30 segundos e rode exatamente o comando do passo 1 de novo. Um broker correto entrega long (com expiração bem abaixo de 300) e none, e nunca entrega short.

Se short chegar, o broker ignora a expiração em mensagens enfileiradas. Se nada chegar, sua sessão não foi mantida: olhe primeiro para a sessão, não para a expiração.

Um teste em Python que se autoverifica

Teste manual serve uma vez. Para pegar regressões, use um script que valide o resultado. Este usa paho-mqtt 2.x e propriedades do MQTT 5:

expiry_test.py
import time, uuid
import paho.mqtt.client as mqtt
from paho.mqtt.properties import Properties
from paho.mqtt.packettypes import PacketTypes

HOST, PORT = "broker.connectmq.com", 1883
RUN = uuid.uuid4().hex[:8]
TOPIC = f"testmqtt/expiry/{RUN}"
SUB_ID = f"expiry-sub-{RUN}"
WAIT = 15                                  # seconds the subscriber stays offline
received = {}
session = {}

def client(cid):
    return mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=cid, protocol=mqtt.MQTTv5)

def connect_props(session_expiry):
    p = Properties(PacketTypes.CONNECT)
    p.SessionExpiryInterval = session_expiry
    return p

def on_message(c, userdata, msg):
    left = getattr(msg.properties, "MessageExpiryInterval", None)
    received[msg.topic.rsplit("/", 1)[1]] = left

# 1. Create a persistent session with a subscription, then go offline
sub = client(SUB_ID)
sub.connect(HOST, PORT, clean_start=True, properties=connect_props(600))
sub.loop_start()
sub.subscribe(f"{TOPIC}/#", qos=1)
time.sleep(1)
sub.disconnect()
sub.loop_stop()

# 2. Publish with different expiry intervals while it is offline
pub = client(f"expiry-pub-{RUN}")
pub.connect(HOST, PORT)
pub.loop_start()
for name, expiry in [("short", 5), ("long", 120), ("none", None)]:
    props = Properties(PacketTypes.PUBLISH)
    if expiry:
        props.MessageExpiryInterval = expiry
    pub.publish(f"{TOPIC}/{name}", name, qos=1, properties=props).wait_for_publish()
pub.disconnect()
pub.loop_stop()

time.sleep(WAIT)

# 3. Resume the session and collect what is left
sub = client(SUB_ID)
sub.on_message = on_message
sub.on_connect = lambda c, u, flags, rc, p: session.update(present=flags.session_present)
sub.connect(HOST, PORT, clean_start=False, properties=connect_props(600))
sub.loop_start()
time.sleep(3)
end = Properties(PacketTypes.DISCONNECT)
end.SessionExpiryInterval = 0              # clean up the session
sub.disconnect(properties=end)
sub.loop_stop()

print("session present:", session.get("present"), "received:", received)
if not session.get("present"):
    raise SystemExit("ABORT: the broker did not keep the session, expiry cannot be judged")
checks = {
    "short expired": "short" not in received,
    "long delivered": "long" in received,
    "long shows remaining time": received.get("long") is not None and received["long"] <= 120 - WAIT,
    "no-expiry delivered": "none" in received,
}
for name, ok in checks.items():
    print(("PASS " if ok else "FAIL ") + name)

Uma execução bem-sucedida imprime session present: True e um valor de long um pouco abaixo de 105. Diferenças de um segundo são normais, porque os brokers arredondam para segundos inteiros.

ABORT com session present: False significa que o broker descartou a sessão ou nunca a guardou, então a expiração não pode ser avaliada. Endpoints públicos compartilhados de teste muitas vezes não guardam sessões offline, então rode este teste contra a sua própria instância de broker. Caso contrário, verifique primeiro as configurações da sessão: testar a expiração de sessão cobre esse caso.

Testar a expiração de mensagens retidas

Mensagens retidas são o segundo lugar onde a expiração importa, e o que mais se esquece. Um status retido "dispositivo online" com 30 segundos de expiração some sozinho se o dispositivo parar de atualizá-lo.

Mensagem retida com 20 segundos de vida
mosquitto_pub -h broker.connectmq.com -V mqttv5 -q 1 -r \
  -t testmqtt/expiry/7f3a/status -m online \
  -D publish message-expiry-interval 20

# Immediately: prints the message (expiry close to 20)
mosquitto_sub -h broker.connectmq.com -V mqttv5 \
  -t testmqtt/expiry/7f3a/status -F '%r %E %p' -W 3

sleep 25

# After expiry: prints nothing and exits after the 3 s timeout
mosquitto_sub -h broker.connectmq.com -V mqttv5 \
  -t testmqtt/expiry/7f3a/status -F '%r %E %p' -W 3

Na primeira saída, %r imprime 1 para uma mensagem retida. Na segunda execução, nada deve aparecer. Se o valor antigo continuar aparecendo, o broker mantém mensagens retidas depois da expiração. Alguns brokers apagam retidas expiradas de forma preguiçosa ou por timer, então teste com margem. O comportamento de retidas em geral está em testar mensagens retidas.

Você também pode acompanhar ao vivo no cliente MQTT online: assine o topic de status antes e depois da janela de expiração e compare.

Ver mensagens expirando no navegador

Resultados comuns e o que significam

  • A mensagem de vida curta é entregue depois de expirar. O broker ignora a propriedade, ou a sessão passa por um caminho de código MQTT 3.1.1. Confirme que publisher e subscriber conectam com a versão 5 do protocolo.
  • A expiração encaminhada é igual ao valor original. O broker não desconta o tempo de espera. Isso quebra a lógica seguinte que encaminha a mensagem de novo, como bridges ou gateways.
  • As mensagens recebidas não trazem a propriedade, mesmo com a expiração definida. Um bridge ou proxy no meio pode falar MQTT 3.1.1 e remover propriedades do MQTT 5.
  • Nada é entregue. Verifique session_present, os níveis de QoS (QoS 0 muitas vezes não é enfileirado) e as ACLs. Uma assinatura recusada pelo broker nunca enfileira nada. A consulta online de reason codes ajuda a decifrar o SUBACK.
  • As mensagens expiram mais tarde que o esperado. Os brokers verificam a expiração na entrega ou em uma varredura periódica. Um ou dois segundos de diferença tudo bem; minutos, não.

Checklist para testes de expiração

  1. Conecte publisher e subscriber com MQTT 5 e registre o CONNACK.
  2. Use um client ID fixo, Clean Start false e uma expiração de sessão maior que o teste.
  3. Publique em QoS 1 com pelo menos três expirações: menor que a espera, maior que a espera e nenhuma.
  4. Deixe o subscriber offline por mais tempo que a expiração curta. Reconecte e valide o que chega.
  5. Confira se o intervalo encaminhado caiu mais ou menos o tempo de espera.
  6. Repita com uma mensagem retida e com um novo subscriber depois da expiração.
  7. Limpe tudo: encerre a sessão com Session Expiry 0 e apague os topics retidos com payload vazio.

A expiração de mensagens é uma propriedade pequena com um efeito grande: ela decide se um dispositivo que acorda recebe comandos úteis ou uma rajada de comandos velhos. Coloque o script acima na sua suíte de testes e rode-o depois de cada atualização ou migração do broker.

Perguntas frequentes

O Message Expiry Interval afeta clientes que estão online?

Normalmente não. Um subscriber online recebe a mensagem quase na hora, muito antes de ela expirar. O intervalo importa quando a mensagem espera: na fila da sessão de um cliente offline, como mensagem retida ou no backlog de um consumidor lento.

Por que meu subscriber recebe um valor de expiração menor do que o que eu publiquei?

Esse é o comportamento correto. O MQTT 5 exige que o broker encaminhe o tempo de vida restante, então uma mensagem publicada com 60 segundos que esperou 20 segundos numa fila chega com uma expiração de cerca de 40 segundos.

A expiração de mensagens funciona com MQTT 3.1.1?

Não. O Message Expiry Interval é uma propriedade do MQTT 5. Um publisher 3.1.1 não consegue defini-la, e o broker trata mensagens sem a propriedade como se nunca expirassem. O broker ainda pode aplicar os próprios limites globais.