O intervalo de expiração de mensagem MQTT (message expiry interval) é uma propriedade do MQTT 5 que diz ao broker por quantos segundos uma mensagem continua válida. Depois que esse tempo passa, o broker joga a mensagem fora em vez de entregá-la a um client que se reconecta tarde. Neste artigo você vai aprender exatamente como a expiração funciona, vê-la descartar um comando obsoleto em um teste real e receber um checklist para escolher valores de expiração no seu próprio sistema.
O Problema: Comandos Que Chegam Tarde Demais
Imagine o controlador do portão de acesso de uma usina, inscrito com QoS 1 e uma sessão persistente. Um operador envia "abrir portão" enquanto o dispositivo está offline. O broker enfileira o comando porque a sessão ainda está ativa. Duas horas depois, o dispositivo se reconecta por um link celular instável e o broker, obedientemente, entrega "abrir portão". Já não há ninguém parado no portão.
Antes do MQTT 5, não havia uma forma no nível do protocolo de impedir isso. Os desenvolvedores adicionavam timestamps aos payloads e torciam para que todo dispositivo os verificasse. O MQTT 5 levou a solução para dentro do protocolo com o message expiry interval, uma propriedade do pacote PUBLISH. É um dos recursos do MQTT 5 mais práticos para quem controla equipamentos físicos.
Como Funciona o Intervalo de Expiração de Mensagem MQTT
O publicador define a propriedade em segundos, como um inteiro de quatro bytes. O broker então aplica estas regras:
- Quando a mensagem chega, o broker registra o horário.
- Se a mensagem está enfileirada para um assinante offline com sessão persistente e o intervalo se esgota antes da entrega, o broker a descarta.
- Quando o broker de fato entrega a mensagem, ele reescreve a propriedade com o tempo restante. Uma mensagem publicada com 60 segundos e entregue 45 segundos depois chega com um intervalo de 15.
- Se a mensagem é retida, a cópia retida também expira. Depois do intervalo, novos assinantes deixam de recebê-la.
- Se a propriedade está ausente, a mensagem nunca expira, que é o comportamento do MQTT 3.1.1.
A quarta regra torna a expiração útil para mais do que comandos. Um aviso retido de "modo de manutenção ativo" pode ser configurado para expirar sozinho, de modo que uma flag esquecida não fique ali por semanas.
| Tipo de mensagem | Expiração sugerida | Justificativa |
|---|---|---|
| Comando de atuador (abrir, ligar, parar) | 5 a 60 s | Só é válido enquanto um operador está acompanhando |
| Envio de configuração | Horas a dias | Continua útil quando um dispositivo se reconecta depois |
| Telemetria para um consumidor persistente | Minutos | Leituras antigas perdem valor, mas podem preencher lacunas |
| Estado temporário retido | Duração do estado | Limpa-se automaticamente |
| Notificação de alarme | Até deixar de exigir ação | Evita enxurradas de alarmes desatualizados |
Por Que a Expiração Só Importa com Sessões Persistentes
A expiração só afeta mensagens que esperam em algum lugar. Em uma clean session sem estado armazenado, um assinante desconectado simplesmente perde as mensagens, então não há nada para expirar. A expiração importa em dois lugares:
- Filas de sessão. Um client que se conecta com clean start igual a false e um session expiry interval maior que zero tem suas mensagens QoS 1 e QoS 2 enfileiradas enquanto está offline. Veja session expiry interval no MQTT, que explica como essa fila é montada.
- Armazenamento retido. Uma retained message com expiração é removida depois do intervalo.
Observe a diferença entre as duas configurações de expiração: a session expiry controla por quanto tempo o broker se lembra de um client; a message expiry controla por quanto tempo vale a pena entregar uma única mensagem. Normalmente você quer as duas.
Testando a Expiração de Mensagens em um Broker Real
Vamos usar o servidor MQTT público em broker.connectmq.com, apenas para testes, que suporta MQTT 5 e sessões de até 30 minutos. Use o seu próprio prefixo de topic.
Passo 1: crie uma sessão persistente. Conecte-se uma vez com clean start desativado (-c), session expiry de 600 segundos (-x 600) e QoS 1, e depois pare com Ctrl+C:
mosquitto_sub -V mqttv5 -h broker.connectmq.com -p 1883 -i exp-dev-m4t9 \
-c -x 600 -q 1 -t connectmq/demo/m4t9/gate/cmd -v
Passo 2: enquanto o assinante está offline, publique um comando que expira em 10 segundos e outro que dura 5 minutos:
mosquitto_pub -V mqttv5 -h broker.connectmq.com -p 1883 -i exp-op-m4t9 -q 1 \
-t connectmq/demo/m4t9/gate/cmd -m 'open (10s)' \
-D publish message-expiry-interval 10
mosquitto_pub -V mqttv5 -h broker.connectmq.com -p 1883 -i exp-op-m4t9 -q 1 \
-t connectmq/demo/m4t9/gate/cmd -m 'report-status (300s)' \
-D publish message-expiry-interval 300
Passo 3: espere pelo menos 20 segundos e reconecte com exatamente o mesmo comando do passo 1. Só report-status (300s) chega. O comando de abertura expirou na fila e nunca foi entregue. Rode os passos 2 e 3 de novo rapidamente, em menos de 10 segundos, e as duas mensagens chegam.
Definindo a Expiração em Python e Node.js
No paho-mqtt 2.x você anexa um objeto Properties à publicação. O client precisa usar MQTT 5:
import json
import uuid
import paho.mqtt.client as mqtt
from paho.mqtt.properties import Properties
from paho.mqtt.packettypes import PacketTypes
client = mqtt.Client(
mqtt.CallbackAPIVersion.VERSION2,
client_id=f"op-{uuid.uuid4().hex[:8]}",
protocol=mqtt.MQTTv5,
)
client.connect("broker.connectmq.com", 1883, keepalive=30)
client.loop_start()
props = Properties(PacketTypes.PUBLISH)
props.MessageExpiryInterval = 30 # segundos
cmd = {"action": "open", "issuedBy": "operator-3"}
info = client.publish("connectmq/demo/m4t9/gate/cmd", json.dumps(cmd), qos=1, properties=props)
info.wait_for_publish()
client.loop_stop()
client.disconnect()
No lado receptor, msg.properties contém MessageExpiryInterval com os segundos restantes, que um dispositivo pode registrar ou usar para rejeitar comandos que estão quase sem tempo.
Com o MQTT.js v5, a propriedade fica nas opções de publicação:
const mqtt = require("mqtt");
const client = mqtt.connect("mqtts://broker.connectmq.com:8883", {
clientId: "op-" + Math.random().toString(16).slice(2, 10),
protocolVersion: 5,
});
client.on("connect", () => {
client.publish(
"connectmq/demo/m4t9/gate/cmd",
JSON.stringify({ action: "close" }),
{ qos: 1, properties: { messageExpiryInterval: 30 } },
(err) => {
if (err) console.error(err);
client.end();
}
);
});
Armadilhas e Solução de Problemas
A expiração é totalmente ignorada. Verifique se tanto o publicador quanto a conexão com o broker usam MQTT 5. Um client 3.1.1 não consegue enviar a propriedade, e um assinante 3.1.1 recebe a mensagem sem ela.
A mensagem ainda chega atrasada. A expiração impede a entrega a partir da fila do broker. Ela não ajuda se a mensagem já estiver no buffer do próprio dispositivo ou em um gateway intermediário. Dispositivos que executam comandos ainda devem comparar um timestamp no payload com o próprio relógio quando a segurança estiver em jogo.
Os comandos expiram rápido demais. Lembre-se da latência da rede e das filas do broker. Uma expiração de 1 ou 2 segundos pode descartar comandos legítimos em links lentos.
A retained message desapareceu. Se alguém publicou o valor retido com expiração, ele some quando o intervalo se esgota. Muitas vezes isso é intencional; verifique o publicador antes de presumir perda de dados. Para o básico sobre retenção, veja mensagens retidas no MQTT.
Expiração versus QoS. O QoS controla se uma mensagem é entregue; a expiração controla se ainda vale a pena entregá-la. Uma mensagem QoS 1 com expiração pode, legitimamente, nunca chegar.
Combinando a Expiração com Outras Salvaguardas
A expiração de mensagens é uma camada de defesa, não o projeto inteiro. Para comandos que movem equipamentos físicos, uma configuração robusta normalmente combina vários mecanismos:
- Expiração em todo comando, para que o broker descarte os obsoletos das filas.
- Um ID de comando e um timestamp de emissão no payload, para que o dispositivo possa rejeitar qualquer coisa mais antiga que seu próprio limite, mesmo que um gateway a tenha guardado em buffer.
- Nenhuma flag retain em comandos, para que um reboot nunca reexecute uma instrução antiga.
- Um topic de confirmação, em que o dispositivo informa que executou ou rejeitou o comando, de preferência usando o padrão de requisição/resposta do MQTT.
- Intertravamentos no dispositivo para tudo o que envolve segurança. A rede nunca deve ser a única coisa impedindo uma ação insegura.
Pense na expiração como a ferramenta que mantém as filas limpas e torna a entrega atrasada rara, enquanto as verificações no dispositivo tornam a execução atrasada impossível.
Expiração de Mensagens no ConnectMQ
O ConnectMQ suporta MQTT 5.0 com message expiry, session expiry, response topic e as demais propriedades do MQTT 5 em suas conexões MQTT 5.0. Em produção, conecte suas ferramentas de operação e seus dispositivos com as credenciais do portal via TLS na porta 8883 e defina expiração em todo topic de comando como política. Um broker MQTT gerenciado cuida de operar o armazenamento de sessões e as filas, então sua equipe só precisa escolher valores de expiração sensatos. O tutorial de MQTT cobre os demais fundamentos.
Comece com o ConnectMQ
Crie um broker MQTT gerenciado gratuitamente em app.connectmq.com no plano Serverless, sem cartão de crédito. Conecte-se com MQTT 5, adicione uma expiração ao seu primeiro comando e teste-a com um dispositivo que se reconecta tarde. Rodar tudo em um broker MQTT na nuvem significa nenhum servidor para manter enquanto você experimenta.
Perguntas frequentes
Qual unidade o message expiry interval usa?
Segundos, armazenados como um inteiro sem sinal de quatro bytes. Se a propriedade não for definida, a mensagem não expira.
O assinante vê o valor de expiração original?
Não. O broker envia o tempo restante, para que o assinante saiba quanto de validade resta quando a mensagem chega.
A expiração de mensagens funciona com MQTT 3.1.1?
Não. É uma propriedade do MQTT 5. Com MQTT 3.1.1 você precisa de um timestamp no payload que o próprio receptor verifique.
Retained messages podem expirar?
Sim. Uma retained message publicada com um message expiry interval é removida do armazenamento retido assim que o intervalo se esgota.



