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ção | O que o broker deve fazer |
|---|---|
| Subscriber online | Entregar imediatamente; a expiração raramente importa |
| Subscriber offline com sessão persistente | Enfileirar a mensagem e descartá-la quando o intervalo passar |
| Mensagem retida | Servi-la a novos subscribers só até expirar, depois apagá-la |
| Mensagem encaminhada depois de esperar | Enviar o intervalo restante, não o valor original |
| Sem propriedade de expiração | A 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
falsee 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.
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:
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:
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.
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 3Na 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
- Conecte publisher e subscriber com MQTT 5 e registre o CONNACK.
- Use um client ID fixo, Clean Start
falsee uma expiração de sessão maior que o teste. - Publique em QoS 1 com pelo menos três expirações: menor que a espera, maior que a espera e nenhuma.
- Deixe o subscriber offline por mais tempo que a expiração curta. Reconecte e valide o que chega.
- Confira se o intervalo encaminhado caiu mais ou menos o tempo de espera.
- Repita com uma mensagem retida e com um novo subscriber depois da expiração.
- 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.


