Fundamentos de MQTT

Intervalo de expiración de mensajes en MQTT: cómo evitar comandos obsoletos

MQTT 5 permite al publicador indicar cuánto tiempo sigue siendo válido un mensaje. Aprende cómo funciona el message expiry interval con las sesiones persistentes y los retained messages, y cómo evitar que los dispositivos que se conectan tarde ejecuten comandos obsoletos.

Reloj junto a un comando en cola que se descarta cuando vence el intervalo de expiración de mensajes MQTT
Pruébalo en tu propio broker MQTTConsigue un broker MQTT privado y gestionado con el plan Free y empieza con ejemplos de código listos para usar.
Pruébalo gratis Ejemplos de código Sin tarjeta

El intervalo de expiración de mensajes MQTT (message expiry interval) es una propiedad de MQTT 5 que indica al broker durante cuántos segundos sigue siendo válido un mensaje. Una vez pasado ese tiempo, el broker descarta el mensaje en lugar de entregárselo a un cliente que se reconecta tarde. En este artículo aprenderás exactamente cómo funciona la expiración de mensajes MQTT, verás cómo descarta un comando obsoleto en una prueba real y obtendrás una checklist para elegir valores de expiración en tu propio sistema.

El problema: comandos que llegan demasiado tarde

Imagina un dispositivo suscrito con QoS 1 y una sesión persistente. Un operador envía "abrir puerta" mientras el dispositivo está desconectado. El broker encola el comando porque la sesión sigue viva. Dos horas después, el dispositivo se reconecta por un enlace celular inestable y el broker entrega diligentemente "abrir puerta". Ya no hay nadie esperando en la puerta.

Antes de MQTT 5 no había forma de evitarlo a nivel de protocolo. Los desarrolladores añadían marcas de tiempo a los payloads y confiaban en que todos los dispositivos las comprobaran. MQTT 5 llevó la solución al protocolo con el message expiry interval, una propiedad del paquete PUBLISH. Es una de las funciones de MQTT 5 más prácticas para cualquiera que controle equipos físicos.

Cómo funciona el intervalo de expiración de mensajes MQTT

El publicador establece la propiedad en segundos, como un entero de cuatro bytes. El broker aplica entonces estas reglas:

  1. Cuando llega el mensaje, el broker registra la hora.
  2. Si el mensaje está en cola para un suscriptor desconectado con sesión persistente y el intervalo vence antes de la entrega, el broker lo descarta.
  3. Cuando el broker entrega el mensaje, reescribe la propiedad con el tiempo restante. Un mensaje publicado con 60 segundos y entregado 45 segundos después llega con un intervalo de 15.
  4. Si el mensaje es retained, la copia retenida también expira. Pasado el intervalo, los nuevos suscriptores ya no la reciben.
  5. Si la propiedad no está presente, el mensaje nunca expira, que es el comportamiento de MQTT 3.1.1.

La cuarta regla hace que la expiración sirva para algo más que los comandos. Un aviso retenido de "modo mantenimiento activo" puede configurarse para que expire por sí solo, de modo que un indicador olvidado no se quede ahí durante semanas.

Tipo de mensaje Expiración sugerida Motivo
Comando de actuador (abrir, arrancar, parar) De 5 a 60 s Solo es válido mientras un operador está pendiente
Envío de configuración De horas a días Sigue siendo útil cuando un dispositivo se reconecta más tarde
Telemetría hacia un consumidor persistente Minutos Las lecturas antiguas pierden valor, pero pueden rellenar huecos
Estado temporal retenido La duración del estado Se borra solo automáticamente
Notificación de alarma Hasta que deja de requerir acción Evita avalanchas de alarmas desfasadas

Por qué la expiración necesita sesiones persistentes para importar

La expiración solo afecta a los mensajes que esperan en algún sitio. En una sesión limpia sin estado almacenado, un suscriptor desconectado simplemente se pierde los mensajes, así que no hay nada que expirar. La expiración importa en dos lugares:

  • Colas de sesión. Un cliente que se conecta con clean start a false y un session expiry interval mayor que cero tiene sus mensajes QoS 1 y QoS 2 en cola mientras está desconectado. Consulta el session expiry interval de MQTT, que explica cómo se construye esa cola.
  • Almacenamiento de retenidos. Un mensaje retenido con expiración se elimina tras el intervalo.

Fíjate en la diferencia entre ambos ajustes de expiración: la expiración de sesión controla cuánto tiempo recuerda el broker a un cliente; la expiración de mensaje controla durante cuánto tiempo merece la pena entregar un mensaje concreto. Normalmente querrás ambos.

Probar la expiración de mensajes en un broker real

Usaremos el servidor MQTT público gratuito en broker.connectmq.com, que admite MQTT 5 y sesiones de hasta 30 minutos. Usa tu propio prefijo de topic y recuerda que es para pruebas y prototipos, no para datos de producción.

Paso 1: crea una sesión persistente. Conéctate una vez con clean start desactivado (-c), una expiración de sesión de 600 segundos (-x 600) y QoS 1, y luego detén el proceso con 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

Paso 2: mientras el suscriptor está desconectado, publica un comando que expira en 10 segundos y otro 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

Paso 3: espera al menos 20 segundos y vuelve a conectarte exactamente con el mismo comando del paso 1. Solo llega report-status (300s). El comando de apertura expiró en la cola y nunca se entregó. Repite los pasos 2 y 3 rápidamente, en menos de 10 segundos, y llegarán ambos mensajes.

Configurar la expiración en Python y Node.js

En paho-mqtt 2.x se adjunta un objeto Properties a la publicación. El cliente debe 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()

En el lado receptor, msg.properties contiene MessageExpiryInterval con los segundos restantes, que un dispositivo puede registrar o usar para rechazar comandos a los que casi no les queda tiempo.

Con MQTT.js v5 la propiedad va en las opciones de publicación:

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();
    }
  );
});

Errores habituales y resolución de problemas

La expiración se ignora por completo. Comprueba que tanto el publicador como la conexión con el broker usan MQTT 5. Un cliente 3.1.1 no puede enviar la propiedad, y un suscriptor 3.1.1 recibe el mensaje sin ella.

El mensaje sigue llegando tarde. La expiración impide la entrega desde la cola del broker. No ayuda si el mensaje ya está en el búfer del propio dispositivo o en un gateway intermedio. Los dispositivos que ejecutan comandos deberían seguir comparando una marca de tiempo del payload con su reloj cuando la seguridad importa.

Los comandos expiran demasiado rápido. Ten en cuenta la latencia de la red y de las colas del broker. Una expiración de 1 o 2 segundos puede descartar comandos legítimos en enlaces lentos.

Ha desaparecido un mensaje retenido. Si alguien publicó el valor retenido con expiración, desaparece cuando vence el intervalo. A menudo es intencionado; revisa el publicador antes de suponer una pérdida de datos. Para lo básico de la retención, consulta los retained messages de MQTT.

Expiración frente a QoS. La QoS controla si un mensaje se entrega; la expiración controla si todavía merece la pena entregarlo. Un mensaje QoS 1 con expiración puede, legítimamente, no llegar nunca.

Combinar la expiración con otras salvaguardas

La expiración de mensajes es una capa de defensa, no todo el diseño. Para comandos que mueven equipos físicos, una configuración robusta suele combinar varios mecanismos:

  1. Expiración en todos los comandos, para que el broker elimine de las colas los obsoletos.
  2. Un ID de comando y una marca de tiempo de emisión en el payload, para que el dispositivo pueda rechazar cualquier cosa más antigua que su propio umbral, aunque un gateway la haya retenido en búfer.
  3. Sin flag retain en los comandos, para que un reinicio nunca vuelva a ejecutar una instrucción antigua.
  4. Un topic de confirmación en el que el dispositivo informe de si ejecutó o rechazó el comando, idealmente usando el patrón de petición/respuesta de MQTT.
  5. Enclavamientos en el dispositivo para todo lo relacionado con la seguridad. La red nunca debería ser lo único que impide una acción insegura.

Piensa en la expiración como la herramienta que mantiene limpias las colas y hace que las entregas tardías sean raras, mientras que las comprobaciones en el dispositivo hacen imposible una ejecución tardía.

Expiración de mensajes en ConnectMQ

ConnectMQ admite MQTT 5.0 con message expiry, session expiry, response topic y el resto de propiedades de MQTT 5 en sus conexiones MQTT 5.0. En producción, conecta tus herramientas de operador y tus dispositivos con las credenciales del portal por TLS en el puerto 8883 y establece como política una expiración en cada topic de comandos. Un broker MQTT gestionado se encarga de operar el almacén de sesiones y las colas, así que tu equipo solo tiene que elegir valores de expiración razonables. El tutorial de MQTT cubre el resto de los fundamentos.

Empieza con ConnectMQ

Crea un broker gratis en app.connectmq.com con el plan Serverless, sin tarjeta de crédito. Conéctate con MQTT 5, añade una expiración a tu primer comando y pruébalo con un dispositivo que se reconecte tarde. Hacerlo en un broker MQTT en la nube significa no tener servidores que mantener mientras experimentas.

Preguntas frecuentes

¿Qué unidad usa el intervalo de expiración de mensajes?

Segundos, almacenados como un entero sin signo de cuatro bytes. Si la propiedad no se establece, el mensaje no expira.

¿Ve el suscriptor el valor de expiración original?

No. El broker envía el tiempo restante, así que un suscriptor puede saber cuánta validez le queda al mensaje cuando llega.

¿Funciona la expiración de mensajes con MQTT 3.1.1?

No. Es una propiedad de MQTT 5. Con MQTT 3.1.1 necesitas una marca de tiempo en el payload que el receptor compruebe por sí mismo.

¿Pueden expirar los mensajes retenidos?

Sí. Un mensaje retenido publicado con un intervalo de expiración se elimina del almacenamiento de retenidos una vez que vence el intervalo.

Artículos relacionados

¿Quién inventó MQTT? La historia, de los oleoductos al IoT

MQTT lo inventaron en 1999 Andy Stanford-Clark, de IBM, y Arlen Nipper, de Arcom, para monitorizar oleoductos vía satélite. Sigue su camino hacia el open source, OASIS, ISO y los brokers MQTT gestionados en la nube de hoy.

8 min de lectura

Por qué tu cliente MQTT se desconecta constantemente (y cómo solucionarlo)

Los bucles de reconexión son uno de los problemas más habituales de MQTT. Conoce las cinco causas típicas, desde los client ID duplicados hasta los timeouts de keep alive y los paquetes demasiado grandes, y sigue un flujo de diagnóstico que localiza la tuya.

9 min de lectura

Todos los artículos →

Tu broker MQTT privado, a unos pocos clics

Crea una cuenta, obtén las credenciales de tu broker y conecta tu primer dispositivo, sin servidores que instalar, parchear ni escalar.