01 · Punto de partida
Qué se puede perder en MQTT
MQTT corre sobre TCP, y TCP ya garantiza que los bytes lleguen completos y en orden mientras la conexión viva. Entonces, ¿de qué hacen falta garantías adicionales? De lo que TCP no cubre: la conexión puede cortarse, y cuando eso ocurre, lo que estaba en tránsito o lo que se envió después se pierde. El emisor no sabe si el mensaje llegó, y el receptor puede haberlo recibido sin que el emisor lo sepa. Un corte de conexión no es una rareza en IoT, sino el estado normal de una red celular o un Wi-Fi débil.
Los mecanismos de MQTT atacan tres problemas distintos que conviene no mezclar:
- ¿Llegó mi mensaje? La calidad de servicio (QoS) define cuánto se insiste y se confirma.
- ¿Y si el destinatario no estaba? Las sesiones persistentes guardan los mensajes para entregarlos cuando vuelva, y los mensajes retenidos guardan el último valor de un tópico para quien llegue tarde.
- ¿Cómo me entero de que alguien se cayó? La última voluntad es un mensaje que el broker publica por el cliente que desapareció.
Una advertencia sobre la simulación del laboratorio: representa los fallos de la conexión como «paquetes MQTT que no llegan». No es pérdida de paquetes de radio (esa la tapa TCP), sino cortes de la conexión durante el intercambio; los porcentajes altos corresponden a redes muy inestables, para ver el efecto.
02 · Cuánto se insiste
Calidad de servicio: 0, 1 y 2
Cada publicación (y cada suscripción) tiene un nivel de calidad de servicio, que determina qué intercambio de paquetes hace falta para darla por entregada.
| Nivel | Garantía | Paquetes | Duplicados | Cuándo usarlo |
|---|---|---|---|---|
| QoS 0 | Como máximo una vez («disparar y olvidar») | 1 | No | Lecturas periódicas que la siguiente reemplaza; telemetría de alta frecuencia |
| QoS 1 | Al menos una vez: el emisor reenvía hasta recibir la confirmación | 2, más los reenvíos | Posibles | Alarmas, eventos y comandos; es el nivel más usado cuando importa la entrega |
| QoS 2 | Exactamente una vez: un intercambio de cuatro paquetes con identificador | 4, más los reenvíos | No | Cuando un duplicado es peor que el costo: facturación, contadores no idempotentes |
Detalles importantes de cómo funciona:
- El identificador de paquete. En QoS 1 y 2 cada publicación lleva un número de 16 bits que permite asociar las confirmaciones. Los reenvíos llevan la bandera
DUPpara indicar que son copias. - Los duplicados de QoS 1. Si el receptor ya entregó el mensaje pero su confirmación se perdió, el emisor reenvía y el receptor lo entrega de nuevo. Por eso, con QoS 1, las órdenes deben ser idempotentes (tema 11) o llevar un identificador para descartar repeticiones.
- QoS 2 no es «mejor» sin más. Elimina los duplicados a costa del doble de paquetes y más estado en ambos extremos, y muchos clientes pequeños ni lo soportan. Casi siempre se resuelve más barato con QoS 1 más un mensaje idempotente.
- El nivel efectivo es el menor de los dos. El emisor publica con un nivel y el suscriptor pidió otro al suscribirse; el broker entrega con el mínimo de ambos. Un mensaje publicado con QoS 2 llega con QoS 0 a un suscriptor que pidió 0. Para tener garantías de extremo a extremo hay que pedirlas en las dos puntas.
- La garantía es salto a salto, entre el cliente y el broker, y entre el broker y cada suscriptor. QoS no asegura que el servicio que consume el mensaje lo haya procesado, solo que llegó al siguiente eslabón.
03 · Memoria del broker
Sesiones persistentes
Al conectarse, el cliente indica con una bandera (en MQTT 3.1.1, clean session) si quiere una sesión nueva o continuar con la anterior. Esto cambia lo que el broker recuerda:
| Aspecto | Sesión limpia | Sesión persistente |
|---|---|---|
| Suscripciones | Se borran al desconectar; hay que volver a suscribirse | Se conservan |
| Mensajes publicados mientras el cliente estaba desconectado | Se pierden | Los de QoS 1 y 2 se guardan en una cola y se entregan al volver |
| Confirmaciones pendientes | Se descartan | Se retoman: se completan los intercambios interrumpidos |
| Costo para el broker | Mínimo | Memoria y disco por cada cliente, incluso desconectado |
| Respuesta a la conexión | — | El CONNACK incluye la bandera «sesión presente» que dice si había estado guardado |
La combinación que resuelve el problema de las órdenes perdidas es la de sesión persistente + suscripción con QoS 1 + publicación con QoS 1. Con ella, una orden publicada mientras el dispositivo estaba desconectado espera en el broker y se entrega cuando el dispositivo vuelve. Requiere que el identificador del cliente sea estable (el mismo en cada conexión), porque con él el broker reconoce la sesión.
Tres cuidados:
- La cola no es infinita. El broker limita cuántos mensajes guarda por cliente (por ejemplo, 1000), y descarta los excedentes. Un dispositivo apagado durante semanas no encontrará todas las órdenes.
- Las órdenes viejas pierden valor. Un «abrir la válvula 10 minutos» entregado tres horas después puede ser dañino. Las órdenes llevan hora de emisión y vencimiento (tema 11), y el dispositivo descarta las vencidas. MQTT 5 ofrece incluso un vencimiento del mensaje en el propio protocolo.
- Las sesiones se acumulan. Los dispositivos dados de baja dejan sesiones huérfanas con colas llenas. Se les pone un tiempo de vida (en MQTT 5, el session expiry interval) o se limpian desde la administración.
04 · El último valor
Mensajes retenidos
Un suscriptor que se conecta a las 14:00 a un tópico donde el dispositivo publica una vez por hora no recibe nada hasta las 15:00. Para el panel que acaba de abrirse, eso es una hora de pantalla vacía. Un mensaje retenido resuelve el problema: al publicar con la bandera retain, el broker guarda el último mensaje de ese tópico y lo entrega de inmediato a cualquiera que se suscriba más tarde.
- Solo se guarda uno por tópico: el último. Un retenido nuevo reemplaza al anterior.
- Se borra publicando una carga vacía con la bandera
retainen ese tópico. - Es ideal para estados: «la puerta está cerrada», «la configuración vigente es la 17», «el dispositivo está en línea». Valores que representan una situación que dura, y de la que interesa saber el último dato.
- No es para eventos: «se abrió la puerta» retenido le llegaría a cada suscriptor nuevo como si acabara de pasar. Los eventos se publican sin retener.
Hay una trampa conocida: el dato retenido envejece sin avisar. Si el dispositivo se rompe, su última lectura sigue entregándose a los suscriptores nuevos como si fuera actual. Por eso los mensajes retenidos de mediciones deben llevar la hora de la medición en la carga, para que quien los reciba pueda juzgar su antigüedad. Y los retenidos huérfanos (de dispositivos dados de baja) deben limpiarse, o quedan para siempre en el broker.
05 · El aviso póstumo
Última voluntad
Un dispositivo que se cae no puede avisar que se cayó. El testamento (last will and testament, LWT) resuelve esto invirtiendo la responsabilidad: al conectarse, el cliente le entrega al broker un mensaje (tópico, carga, calidad, retención) para que lo publique si el cliente desaparece sin despedirse. El broker lo publica cuando detecta una desconexión anómala: se corta la conexión, vence el keepalive (a una vez y media el tiempo declarado) o el cliente comete un error de protocolo. Si el cliente se despide con un DISCONNECT ordenado, el testamento no se publica.
El patrón estándar para saber quién está vivo combina el testamento con un mensaje de nacimiento y la retención:
- Al conectar, el dispositivo declara un testamento: tópico
casa/camara07/estado, cargaoffline, retenido. - Apenas conecta, publica en ese mismo tópico
online, retenido (el mensaje de nacimiento). - Si se cae, el broker publica
offliney lo deja retenido. - Cualquier panel o servicio que se suscriba a
casa/+/estadorecibe, de inmediato, el estado actual de cada dispositivo, y se entera en vivo de los cambios.
El tiempo de detección depende del keepalive: con 60 segundos, hasta 90 segundos pasan entre la caída y el aviso. Un keepalive corto detecta antes pero cuesta más pings (y energía); uno largo ahorra y avisa tarde. El testamento tampoco avisa de un dispositivo que sigue conectado pero cuelga su firmware sin dejar de enviar pings; para eso sirven los latidos de la aplicación (tema 18). Y si un dispositivo se reconecta rápido, puede recibir su propio offline después de haber publicado online, según el orden en que el broker detecte la caída: el nacimiento debe publicarse después de haber confirmado la conexión nueva.
// Conectar declarando el testamento (PubSubClient):
// connect(id, usuario, clave, tópicoTestamento, calidadTestamento, retenerTestamento, cargaTestamento)
bool ok = mqtt.connect(idCliente, USUARIO, CLAVE,
"casa/camara07/estado", 1, true, "offline");
if (ok) {
mqtt.publish("casa/camara07/estado", "online", true); // nacimiento, retenido
mqtt.subscribe("casa/camara07/comandos", 1); // suscripción con QoS 1
}
// Despedirse a propósito (por ejemplo, antes de dormir): marcar fuera de línea a mano.
mqtt.publish("casa/camara07/estado", "offline", true);
mqtt.disconnect(); // desconexión ordenada: no dispara el testamento
Una nota sobre la biblioteca del ejemplo: PubSubClient solo publica con QoS 0 y solo permite suscribirse con QoS 0 o 1; para publicar con QoS 1 o 2 se necesita otra biblioteca (hay varias para ESP32 y otras plataformas). Es un buen ejemplo de por qué conviene verificar qué soporta la biblioteca antes de diseñar el sistema alrededor de una garantía.
06 · Decisiones
Cómo elegir para cada mensaje
No hay un nivel global: se elige por tipo de mensaje, según lo que cuesta perderlo y lo que cuesta duplicarlo.
| Mensaje | QoS | Retenido | Notas |
|---|---|---|---|
| Telemetría periódica (temperatura cada minuto) | 0 | No (o sí, si interesa el último valor) | La siguiente lectura reemplaza a una perdida; con retención, incluir la hora de la medición |
| Alarmas y eventos importantes | 1 | No | No deben perderse; el receptor tolera un duplicado (identificador del evento) |
| Comandos al dispositivo | 1, con sesión persistente | No | Idempotentes, con vencimiento; el dispositivo confirma el resultado |
| Confirmación de comando | 1 | No | Lleva el identificador de la orden |
| Estado del dispositivo (en línea o fuera de línea) | 1 | Sí, con testamento | Patrón nacimiento y testamento |
| Configuración vigente | 1 | Sí | El dispositivo la recibe al suscribirse, aunque esté recién conectado |
| Conteos que no admiten duplicados (facturación) | 2 (o 1 con identificador) | No | Evaluar si un identificador único resuelve más barato |
Y un recordatorio sobre la energía: cada nivel agrega paquetes que mantienen la radio encendida un poco más. Para un dispositivo con batería que publica de a ratos, la diferencia entre QoS 0 y QoS 1 en un envío (un paquete de confirmación más) es pequeña frente al apretón de manos de TCP y TLS, pero con miles de mensajes diarios se acumula. La regla práctica: QoS 0 para lo repetitivo y reemplazable; QoS 1 para lo que no debe perderse; QoS 2 casi nunca.
07 · Analogía con programación
Probar las garantías en Python
El programa tiene dos partes. La primera extiende el mini broker del tema 25 con mensajes retenidos, sesiones persistentes con cola para QoS 1 y 2, y testamento. Primero reproduce el patrón de nacimiento y testamento: el panel ve a la cámara pasar a online y a offline cuando se cae sin despedirse, y una alarma que se suscribe después recibe de inmediato el último estado retenido. Después prueba una sesión persistente: una persiana se desconecta, el servidor publica una orden con QoS 1 y otra con QoS 0, y al reconectar solo recibe la de QoS 1. La segunda parte simula 20.000 mensajes enviados con cada nivel de QoS cuando falla una fracción de los paquetes (por cortes de la conexión), con reenvíos y hasta 10 intentos, y mide cuántos llegan, cuántos se duplican y cuánto cuesta en paquetes y bytes.
Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.
# ---------- Parte 1: mensajes retenidos, sesiones persistentes y última voluntad en un mini broker ----------
class Broker:
def __init__(self):
self.retenidos = {} # tópico -> último mensaje retenido
self.clientes = {} # nombre -> {"filtros": [...], "conectado": bool, "cola": [...], "testamento": ...}
@staticmethod
def coincide(filtro, topico):
f, t = filtro.split("/"), topico.split("/")
for i, nivel in enumerate(f):
if nivel == "#":
return True
if i >= len(t) or (nivel != "+" and nivel != t[i]):
return False
return len(f) == len(t)
def conectar(self, nombre, testamento=None, sesion_limpia=True):
previo = self.clientes.get(nombre)
if sesion_limpia or previo is None:
self.clientes[nombre] = {"filtros": [], "conectado": True, "cola": [], "testamento": testamento}
print(f" [{nombre}] conecta con sesión nueva")
else:
previo.update(conectado=True, testamento=testamento)
pendientes, previo["cola"] = previo["cola"], []
print(f" [{nombre}] reconecta con sesión persistente: recibe {len(pendientes)} mensaje(s) guardado(s): {pendientes}")
def suscribir(self, nombre, filtro):
self.clientes[nombre]["filtros"].append(filtro)
for topico, carga in self.retenidos.items(): # el retenido se entrega al suscribirse
if self.coincide(filtro, topico):
print(f" [{nombre}] recibe de inmediato el retenido {topico} = {carga!r}")
def publicar(self, topico, carga, retener=False, qos=0):
if retener:
if carga == "":
self.retenidos.pop(topico, None) # una carga vacía borra el retenido
else:
self.retenidos[topico] = carga
for nombre, c in self.clientes.items():
if any(self.coincide(f, topico) for f in c["filtros"]):
if c["conectado"]:
print(f" [{nombre}] recibe {topico} = {carga!r}")
elif qos >= 1:
c["cola"].append((topico, carga)) # solo se guarda para sesiones persistentes y QoS 1 o 2
def caer(self, nombre):
"""Desconexión sin despedirse: el broker publica el testamento."""
c = self.clientes[nombre]
c["conectado"] = False
print(f" [{nombre}] se cayó sin despedirse")
if c["testamento"]:
topico, carga = c["testamento"]
self.publicar(topico, carga, retener=True)
print("Estado retenido y testamento:")
b = Broker()
b.conectar("panel")
b.suscribir("panel", "casa/+/estado")
b.conectar("camara07", testamento=("casa/camara07/estado", "offline"))
b.publicar("casa/camara07/estado", "online", retener=True) # mensaje de nacimiento
b.caer("camara07")
b.conectar("alarma")
b.suscribir("alarma", "casa/camara07/estado") # llega tarde y recibe el último estado retenido
print("Sesión persistente y mensajes en cola:")
b.conectar("persiana", sesion_limpia=False)
b.suscribir("persiana", "casa/persiana/comandos")
b.caer("persiana")
b.publicar("casa/persiana/comandos", "subir", qos=1) # se publica con el cliente desconectado
b.publicar("casa/persiana/comandos", "bajar", qos=0) # QoS 0: se pierde
b.conectar("persiana", sesion_limpia=False)
# ---------- Parte 2: qué garantiza cada calidad de servicio cuando la conexión falla ----------
class Aleatorio:
def __init__(self, semilla=4242):
self.estado = semilla
def uniforme(self):
self.estado = (self.estado * 1664525 + 1013904223) & 0xFFFFFFFF
return self.estado / 4294967296
MAX_REINTENTOS = 10
BYTES = {"PUBLISH": 69, "ACK": 4} # PUBLISH de 40 bytes de carga y los paquetes PUBACK, PUBREC, PUBREL y PUBCOMP
def enviar(qos, perdida, azar):
"""Un mensaje. Devuelve (veces que se entregó a la aplicación, paquetes enviados, bytes enviados)."""
entregas, paquetes, bytes_ = 0, 0, 0
def llega():
return azar.uniforme() >= perdida
if qos == 0:
paquetes, bytes_ = 1, BYTES["PUBLISH"]
return (1 if llega() else 0), paquetes, bytes_
if qos == 1:
for _ in range(MAX_REINTENTOS):
paquetes += 1; bytes_ += BYTES["PUBLISH"]
if llega():
entregas += 1 # el receptor entrega cada copia que le llega
paquetes += 1; bytes_ += BYTES["ACK"]
if llega(): # PUBACK
break
return entregas, paquetes, bytes_
# QoS 2: PUBLISH -> PUBREC y PUBREL -> PUBCOMP; el receptor entrega solo la primera copia
recibido = False
for _ in range(MAX_REINTENTOS):
paquetes += 1; bytes_ += BYTES["PUBLISH"]
if llega():
if not recibido:
entregas, recibido = 1, True
paquetes += 1; bytes_ += BYTES["ACK"]
if llega(): # PUBREC
break
if recibido:
for _ in range(MAX_REINTENTOS):
paquetes += 1; bytes_ += BYTES["ACK"]
if llega():
paquetes += 1; bytes_ += BYTES["ACK"]
if llega(): # PUBCOMP
break
return entregas, paquetes, bytes_
def evaluar(qos, perdida, mensajes=20000):
azar = Aleatorio()
llegaron = duplicados = paquetes = bytes_ = 0
for _ in range(mensajes):
e, p, b = enviar(qos, perdida, azar)
llegaron += e >= 1
duplicados += e > 1
paquetes += p
bytes_ += b
return 100 * llegaron / mensajes, 100 * duplicados / mensajes, paquetes / mensajes, bytes_ / mensajes
print()
print("calidad pérdida llegan duplicados paquetes/mensaje bytes/mensaje")
for perdida in (0.05, 0.20, 0.40):
for qos in (0, 1, 2):
ok, dup, pq, by = evaluar(qos, perdida)
print(f"QoS {qos} {perdida:>6.0%} {ok:>6.1f} % {dup:>9.1f} % {pq:>16.2f} {by:>14.1f}")
Resultado con los valores iniciales. En la primera parte, el panel recibe online cuando la cámara publica su nacimiento y offline cuando se cae; la alarma, que llega tarde, recibe de inmediato el retenido offline. La persiana, tras reconectar con sesión persistente, recibe un solo mensaje guardado, subir (QoS 1); bajar (QoS 0) se perdió. En la simulación, con 5 % de fallos: QoS 0 entrega el 94,9 %; QoS 1 el 100 %, con 5,2 % de duplicados y 2,17 paquetes por mensaje (80,9 bytes contra 69); QoS 2 el 100 % sin duplicados, con 4,33 paquetes (89,7 bytes). Con 20 %: QoS 0 entrega el 80,2 %; QoS 1 el 100 % con 19,6 % de duplicados y 112 bytes; QoS 2 el 100 % con 5,61 paquetes y 124 bytes. Con 40 %: QoS 0 el 60,4 %; QoS 1 el 100 % con 39,6 % de duplicados, 4,39 paquetes y 196 bytes; QoS 2, 8,81 paquetes y 215 bytes.
Tres conclusiones. Una: QoS 1 y 2 entregan prácticamente todo; la diferencia está en los duplicados: QoS 1 los tiene en una proporción casi igual a la de fallos, QoS 2 no. Otra: el costo de QoS crece con la inestabilidad: con 5 % de fallos, QoS 1 gasta 17 % más bytes; con 40 %, 2,8 veces. Y la tercera: QoS 2 cuesta apenas un poco más que QoS 1 en bytes pero el doble en paquetes, lo que importa en energía y en latencia.
08 · Exploración interactiva
¿Qué llega con una conexión inestable?, de extremo a extremo
Este laboratorio ejecuta la misma simulación del código anterior, con el mismo generador pseudoaleatorio. Elegí el nivel de QoS y el porcentaje de paquetes que se pierden por cortes de la conexión. Las barras comparan los tres niveles con esa inestabilidad: a la izquierda, el porcentaje de mensajes que llegan (con los duplicados anotados); a la derecha, los bytes enviados por mensaje.
Calidad de servicio con una conexión inestable
Elegí los parámetros y presioná «Simular».
Recorrido guiado
- Con los valores iniciales (QoS 1, 20 % de pérdidas), simulá. Llega el 100 %, hay 19,6 % de duplicados, 2,80 paquetes por mensaje y 112 bytes (1,63 veces QoS 0): «Llegan, con duplicados».
- Cambiá a QoS 0: llega el 80,2 %, se pierde el 19,8 % y no hay duplicados; 69 bytes. Lo más barato, a costa de perder uno de cada cinco mensajes.
- Pasá a QoS 2: llega el 100 % sin duplicados, pero con 5,61 paquetes por mensaje y 124 bytes (1,79 veces QoS 0). La garantía de «una sola vez» cuesta el doble de paquetes que QoS 1.
- Con QoS 1, bajá la pérdida a 5 %: 5,2 % de duplicados y apenas 1,17 veces el costo de QoS 0. En una red buena, QoS 1 cuesta casi lo mismo que QoS 0.
- Subí a 40 % con QoS 1: 39,6 % de duplicados, 4,39 paquetes y 196 bytes (2,84 veces). Una red casi caída hace caros los reintentos; ahí no hay nivel barato.
- Compará los duplicados de QoS 1 con la pérdida: casi coinciden (5,2 con 5; 19,6 con 20; 39,6 con 40). Cada vez que se pierde la confirmación, el mensaje se entrega de nuevo: los duplicados de QoS 1 son esperables y la aplicación debe tolerarlos.
Desafío: el costo de un duplicado
Un dispositivo recibe la orden «avanzar el dosificador una unidad» por QoS 1. En una red con 20 % de fallos, el 19,6 % de las órdenes llega dos veces. Si la orden no es idempotente, de cada 100 órdenes el dosificador avanza 119,6 veces: casi 20 de más. Con QoS 2 no hay duplicados, pero cuesta el doble de paquetes. Una tercera opción es hacer idempotente la orden: en lugar de «avanzar una unidad», «fijar la posición en 17» con un número de secuencia. Así un duplicado no cambia nada y QoS 1 (más barato) basta. Casi siempre es mejor invertir en el diseño del mensaje que en una garantía más cara del protocolo.
09 · Método de trabajo
Lista de verificación de las garantías
- Clasificá los mensajes (telemetría, alarma, comando, confirmación, estado, configuración) y asignale a cada tipo su QoS y su retención.
- QoS en las dos puntas: el nivel efectivo es el menor entre el de publicación y el de suscripción.
- Mensajes idempotentes con identificador, para tolerar los duplicados de QoS 1.
- Sesión persistente y un identificador de cliente estable para los dispositivos que reciben comandos; límite de cola y vencimiento de sesión.
- Comandos con hora de emisión y vencimiento, para descartar los viejos que llegan al reconectar.
- Retención para estados y configuración, con la hora de la medición en la carga y limpieza de los retenidos huérfanos.
- Testamento y nacimiento retenidos en un tópico de estado de cada dispositivo.
- Keepalive elegido según la rapidez con que se quiera detectar una caída y la energía disponible.
- Verificá la biblioteca: qué niveles de QoS soporta al publicar y al suscribir, y si maneja sesión y testamento.
- Probá con caos: matar la conexión en medio de un intercambio, desconectar el dispositivo mientras se publican órdenes, reiniciar el broker.
La prueba más reveladora: apagá un dispositivo, publicá tres órdenes (una por cada nivel de QoS) y encendelo de nuevo. Con una sesión persistente, tendría que recibir las de QoS 1 y 2 y haber perdido la de QoS 0. Si no ocurre así, algo en la cadena (identificador, bandera de sesión, nivel de suscripción) no está bien.
10 · Comprensión
Confusiones frecuentes
«Con QoS 2 no se pierde nada y no hay duplicados, así que uso siempre QoS 2»
Cuesta el doble de paquetes que QoS 1, exige más estado y muchas bibliotecas pequeñas no lo soportan. Casi siempre es mejor QoS 1 con mensajes idempotentes.
«Si publico con QoS 1, el suscriptor recibe con QoS 1»
El broker entrega con el menor de los dos niveles. Si el suscriptor pidió QoS 0, recibe con QoS 0 y puede perder el mensaje. La garantía de extremo a extremo exige pedirla en la suscripción.
«Con una sesión persistente, el broker guarda todos los mensajes que me perdí»
Solo los de QoS 1 y 2, y hasta un límite de cola. Los de QoS 0 se pierden, y los excedentes se descartan. Además, un mensaje viejo puede haber perdido sentido.
«Un mensaje retenido es un mensaje que se reenvía a todos los suscriptores cada tanto»
Se entrega una vez a cada suscriptor nuevo, en el momento de suscribirse. A los ya suscritos no se les repite. Es la memoria del último valor, no un reenvío periódico.
«El testamento se publica siempre que el cliente se desconecta»
Solo cuando la desconexión es anómala. Si el cliente se despide con un DISCONNECT, el testamento no se publica: por eso, antes de una desconexión intencional, conviene publicar a mano el estado «offline».
«El estado retenido siempre está al día»
Es el último que alguien publicó, y envejece. Si el dispositivo muere sin testamento, o el testamento falla, el broker sigue entregando el último «online» a quien pregunte. Hay que combinar retención con testamento y con la hora de la medición.
11 · Práctica guiada
Predecí antes de comprobar
1. Nivel efectivo
¿Con qué QoS recibe el suscriptor en cada caso? (a) se publica con QoS 2 y se suscribió con QoS 1; (b) se publica con QoS 0 y se suscribió con QoS 2; (c) se publica con QoS 1 y se suscribió con QoS 1.
Ver solución
El mínimo de los dos: (a) QoS 1; (b) QoS 0; (c) QoS 1. En (b), pedir QoS 2 al suscribirse no mejora nada si el publicador usó 0.
2. La orden del dispositivo apagado
Un dispositivo estuvo desconectado 10 minutos. El servidor publicó «abrir» con QoS 1. ¿Qué hace falta para que la reciba al volver, y qué ocurre con un «cerrar» publicado con QoS 0?
Ver solución
Hace falta una sesión persistente (con el mismo identificador de cliente), una suscripción con QoS 1 y la publicación con QoS 1. El «abrir» espera en la cola del broker y se entrega al reconectar; el «cerrar» con QoS 0 se pierde. Y como la orden pudo llegar tarde, debe llevar un vencimiento.
3. Retenidos
Un panel se abre a las 14:00. El sensor publica cada hora, a la hora en punto. ¿Qué muestra el panel si el sensor publica con retención y si no?
Ver solución
Con retención, el panel recibe de inmediato la lectura de las 14:00 (o la última publicada). Sin retención, queda vacío hasta las 15:00. Para borrar un retenido se publica una carga vacía con la bandera de retención.
4. Tiempo de detección
Un dispositivo con keepalive de 60 s se cae sin despedirse. ¿Cuánto tarda el broker en publicar el testamento? ¿Y con keepalive de 20 s?
Ver solución
El broker espera hasta una vez y media el keepalive: 90 s con 60 s, y 30 s con 20 s. Un keepalive corto detecta antes, pero cuesta más pings y energía.
5. Costo de las garantías
Con 20 % de fallos, QoS 0 cuesta 69 bytes por mensaje, QoS 1 cuesta 112 y QoS 2, 124. ¿Cuántas veces más? ¿Qué porcentaje de mensajes duplica QoS 1?
Ver solución
QoS 1: 112 / 69 ≈ 1,63 veces. QoS 2: 124 / 69 ≈ 1,79 veces. QoS 1 duplica alrededor del 19,6 % de los mensajes: la fracción de veces que se pierde la confirmación.
6. Elegir el nivel
Elegí QoS y retención para: (a) la temperatura cada minuto; (b) la alarma de apertura de una puerta; (c) el estado en línea o fuera de línea del dispositivo; (d) la configuración vigente.
Ver solución
(a) QoS 0, sin retención (o con, si interesa el último valor con su hora). (b) QoS 1, sin retención, con identificador del evento. (c) QoS 1, retenido, con testamento y mensaje de nacimiento. (d) QoS 1, retenido: el dispositivo la recibe al suscribirse.
12 · Síntesis
Ideas para recordar
- TCP cubre los bytes mientras la conexión vive; QoS, las sesiones, los retenidos y el testamento cubren lo que ocurre cuando la conexión se corta.
- QoS 0 envía una vez y se olvida; QoS 1 reenvía hasta recibir la confirmación (al menos una vez, con posibles duplicados); QoS 2 usa cuatro paquetes para entregar exactamente una vez, al doble de costo.
- El nivel efectivo es el menor entre el de publicación y el de suscripción; la garantía es salto a salto, no de extremo a extremo hasta el proceso que consume.
- Una sesión persistente guarda las suscripciones y los mensajes de QoS 1 y 2 durante la desconexión; con un identificador estable, las órdenes esperan en el broker y se entregan al volver, con límite de cola y vencimiento.
- Con QoS 1 hay que tolerar duplicados: los mensajes idempotentes e identificados son más baratos que QoS 2.
- Un mensaje retenido guarda el último valor de un tópico para los suscriptores nuevos; sirve para estados y configuraciones, no para eventos, y envejece sin avisar.
- El testamento es el mensaje que el broker publica si el cliente desaparece sin despedirse; con un nacimiento retenido forma el patrón de estado en línea o fuera de línea.
- La detección de una caída tarda hasta una vez y media el keepalive.
- Se elige el nivel por tipo de mensaje: QoS 0 para lo repetitivo, QoS 1 para lo que no debe perderse, QoS 2 casi nunca.
¿Listo para el tema 29?
Antes de avanzar, armá la tabla de mensajes de tu sistema con su QoS, su retención y su vencimiento, y escribí el testamento y el nacimiento de un dispositivo. Después hacé la prueba del dispositivo apagado: publicá tres órdenes con QoS 0, 1 y 2 mientras está desconectado y verificá cuál recibe al volver.
En el próximo tema armamos el diseño de tópicos y mensajes: cómo nombrar, jerarquizar y versionar los tópicos y las cargas para que el sistema crezca sin rehacerse.