Fundamentos

Sesiones persistentes en MQTT: Clean Session vs Clean Start

Entiende las sesiones persistentes de MQTT: cleanSession en 3.1.1, Clean Start y Session Expiry en MQTT 5, el flag Session Present y la cola de mensajes QoS 1/2.

Actualizado · 7 min de lectura

Una sesión MQTT es el estado que el broker y el cliente guardan el uno del otro: a qué topics se ha suscrito el cliente y qué mensajes siguen en vuelo. Con una sesión persistente, ese estado sobrevive a una desconexión, de modo que un dispositivo que se cae de la red unos minutos vuelve con las mismas suscripciones y recibe los mensajes que se perdió. Este artículo explica cómo funcionan las sesiones en MQTT 3.1.1 (cleanSession) y en MQTT 5 (Clean Start más Session Expiry Interval).

Qué contiene una sesión

La especificación define el estado de sesión de cada lado. En el broker incluye:

  • La existencia de la propia sesión, identificada por el client ID.
  • Las suscripciones del cliente (filtros de topic y su QoS).
  • Mensajes QoS 1 y QoS 2 enviados al cliente pero aún no confirmados del todo.
  • Mensajes QoS 1 y QoS 2 pendientes de enviar al cliente: la «cola offline».
  • Mensajes QoS 2 recibidos del cliente pero aún no confirmados del todo.
  • Opcionalmente, mensajes QoS 0 pendientes de enviar (los brokers pueden encolarlos, pero normalmente no lo hacen).

El cliente guarda su propia mitad: los mensajes QoS 1 y 2 que envió pero que no están confirmados del todo, y los mensajes QoS 2 que recibió pero no ha completado. Por eso los niveles de QoS solo mantienen sus garantías entre reconexiones cuando se usa una sesión persistente.

MQTT 3.1.1: el flag cleanSession

En MQTT 3.1.1 un único bit del paquete CONNECT lo decide todo:

cleanSessionAl conectarAl desconectar
1 (true)Se descarta cualquier sesión existente para este client ID y empieza una nuevaLa sesión se elimina
0 (false)Se reanuda la sesión existente; si no la hay, se crea una nuevaLa sesión se conserva y los mensajes QoS 1/2 de sus suscripciones se encolan

Según el protocolo, una sesión persistente en 3.1.1 nunca caduca por sí sola. Los brokers añaden sus propios límites, como los ajustes persistent_client_expiration y max_queued_messages de Mosquitto; de lo contrario, los client IDs abandonados se acumularían para siempre. Un client ID de longitud cero solo se permite con cleanSession=1, porque una sesión que no se puede identificar no se puede reanudar.

MQTT 5: Clean Start y Session Expiry Interval

MQTT 5 divide el antiguo flag en dos controles independientes:

  • Clean Start (un flag): si se descarta cualquier sesión existente al conectar.
  • Session Expiry Interval (una propiedad del CONNECT, en segundos): cuánto tiempo conserva el broker la sesión después de cerrarse la conexión de red. Si no está presente o vale 0, la sesión termina con la conexión; 0xFFFFFFFF significa que nunca caduca.

Eso te da combinaciones que 3.1.1 no podía expresar, como «empieza siempre de cero, pero conserva mi sesión una hora si me caigo» (Clean Start = 1, expiry = 3600). El cliente también puede cambiar el expiry en su paquete DISCONNECT, siempre que no se haya conectado con un expiry de 0.

MQTT 3.1.1Configuración equivalente en MQTT 5
cleanSession = 1Clean Start = 1, Session Expiry Interval = 0
cleanSession = 0Clean Start = 0, Session Expiry Interval = 0xFFFFFFFF

Los brokers MQTT 5 también pueden acortar el expiry solicitado y anunciar el valor real con una propiedad Session Expiry Interval en el CONNACK. Combínalo con el Message Expiry Interval de cada mensaje para que los comandos en cola no se entreguen horas después de haber dejado de tener sentido. Tienes más diferencias en MQTT 5 vs 3.1.1.

El flag Session Present

El CONNACK del broker lleva un flag Session Present que indica al cliente lo que ha ocurrido realmente:

  • 1: el broker encontró y reanudó una sesión almacenada. Las suscripciones siguen activas; no necesitas volver a suscribirte.
  • 0: no se reanudó ninguna sesión (se activó Clean Start, caducó o el broker la perdió). El cliente debe volver a suscribirse.

Los clientes robustos comprueban este flag en lugar de suponer nada. El patrón sencillo más seguro es volver a suscribirse siempre que Session Present sea 0; volver a suscribirse cuando vale 1 está permitido, pero sustituye las suscripciones existentes y puede provocar que se reenvíen los mensajes retenidos.

Ejemplos

Con la CLI de Mosquitto, -c desactiva la sesión limpia e -i fija un client ID; ambas cosas son necesarias para una sesión persistente:

Suscriptor persistente con mosquitto_sub
# 1. Create the session, then press Ctrl+C
mosquitto_sub -h broker.hivemq.com -i sensor-42-sub -c -q 1 -t 'testmqtt/sess-demo/#'

# 2. While it is offline, publish with QoS 1
mosquitto_pub -h broker.hivemq.com -q 1 -t 'testmqtt/sess-demo/cmd' -m 'reboot'

# 3. Reconnect with the same ID: the queued message is delivered
mosquitto_sub -h broker.hivemq.com -i sensor-42-sub -c -q 1 -t 'testmqtt/sess-demo/#'
MQTT.js: sesión MQTT 5 que sobrevive una hora
import mqtt from 'mqtt';

const client = mqtt.connect('wss://broker.hivemq.com:8884/mqtt', {
  protocolVersion: 5,
  clientId: 'sensor-42-sub',
  clean: false, // Clean Start = 0
  properties: { sessionExpiryInterval: 3600 },
});

client.on('connect', (connack) => {
  if (!connack.sessionPresent) {
    client.subscribe('testmqtt/sess-demo/#', { qos: 1 });
  }
});
Paho MQTT (Python): MQTT 3.1.1
import paho.mqtt.client as mqtt

def on_connect(client, userdata, flags, reason_code, properties):
    if not flags.session_present:
        client.subscribe("testmqtt/sess-demo/#", qos=1)

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
                     client_id="sensor-42-sub", clean_session=False)
client.on_connect = on_connect
client.connect("broker.hivemq.com", 1883)
client.loop_forever()

Puedes generar código equivalente para otros lenguajes con el generador de código MQTT, y usar el generador de client IDs para crear IDs estables y únicos. Para publicar los mensajes de prueba desde el navegador, usa el cliente online:

Pruébalo en el cliente MQTT online

¿Cuánto debe durar una sesión?

Con MQTT 5 tienes que elegir un Session Expiry Interval, y el valor adecuado depende de cuánto tiempo puede estar ausente el cliente de forma realista y seguir interesándole lo que se perdió:

  • Apps móviles y dashboards: minutos. Lo bastante para sobrevivir a un túnel o a un cambio de red, y lo bastante poco para que el broker no guarde estado de usuarios que ya cerraron la app.
  • Dispositivos de campo en enlaces poco fiables: de horas a un día, ajustado a la duración típica de los cortes.
  • Consumidores de backend: lo suficiente para cubrir una ventana de despliegue o reinicio, de modo que no se pierda ningún mensaje mientras arranca una versión nueva.

Las sesiones también interactúan con el Last Will. En MQTT 5 el will se publica cuando pasa el Will Delay Interval o cuando termina la sesión, lo que ocurra primero. Un will delay corto con una sesión más larga permite a un dispositivo reconectarse discretamente tras un fallo breve sin disparar una alerta de «offline».

Errores habituales

  • Client IDs aleatorios. Muchas librerías generan un ID aleatorio en cada arranque, lo que crea sin avisar una sesión nueva cada vez. Las sesiones persistentes requieren un ID estable.
  • QoS 0 en algún punto de la cadena. El QoS de entrega es el menor entre el de publicación y el de suscripción. Si alguno es 0, los mensajes no se encolan para clientes offline.
  • Dos clientes con el mismo ID. El broker desconecta al más antiguo cuando se conecta el nuevo, y ambos se pelean por la sesión. Consulta errores de conexión MQTT.
  • Colas sin límite. Un dispositivo que pasa días offline puede acumular un gran backlog. Usa la caducidad de sesiones y mensajes, y los límites de cola del broker, para mantenerlo bajo control.
  • Reinicios del broker. Las sesiones solo sobreviven a un reinicio del broker si este las persiste en disco (en Mosquitto, persistence true).

Cuándo usar sesiones persistentes

Úsalas en dispositivos que no pueden perderse comandos ni actualizaciones de configuración, y en consumidores de backend que deben procesar todos los mensajes QoS 1. Prefiere sesiones limpias para herramientas de vida corta, dashboards a los que solo les importan los últimos valores (para eso están los mensajes retenidos) y cualquier cosa que se conecte con client IDs desechables. En redes inestables, combina una sesión persistente con un intervalo de keep alive sensato para que las desconexiones se detecten rápido y la cola tome el relevo.

Preguntas frecuentes

¿Qué significa clean session false en MQTT?

Con cleanSession a false en MQTT 3.1.1, el broker conserva la sesión del cliente después de que se desconecte: sus suscripciones y los mensajes QoS 1 y QoS 2 que lleguen mientras está offline. Cuando el cliente se reconecta con el mismo client ID, reanuda esa sesión y recibe los mensajes en cola.

¿Por qué no recibo los mensajes offline con una sesión persistente?

Revisa cuatro cosas: que el cliente se reconecte exactamente con el mismo client ID, que clean session sea false (o, en MQTT 5, Clean Start false con un Session Expiry Interval distinto de cero), que tanto la suscripción como la publicación usen QoS 1 o 2, y que el broker no haya alcanzado su límite de cola o el expiry de la sesión.

¿Qué diferencia hay entre Clean Session y Clean Start?

En MQTT 3.1.1, cleanSession controla a la vez si se descarta una sesión anterior al conectar y si la nueva sobrevive a la desconexión. MQTT 5 los separa: Clean Start solo controla si se descarta la sesión anterior, y el Session Expiry Interval controla cuánto tiempo sobrevive la sesión tras desconectarse.