Fundamentos de MQTT

Cómo borrar un mensaje retenido en MQTT (CLI, Python y cliente web)

Un mensaje retenido obsoleto saluda a cada nuevo suscriptor con datos antiguos. Así puedes borrar un mensaje retenido en MQTT con una publicación retenida vacía, desde la línea de comandos, Python, JavaScript y un cliente en el navegador.

Ilustración del árbol de topics de un broker en el que se borra un valor obsoleto para mostrar cómo eliminar un mensaje retenido 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

Para borrar un mensaje retenido en MQTT no necesitas ninguna herramienta de administración del broker: publicas un payload de longitud cero con el flag retain activado en el mismo topic, y el broker descarta el valor almacenado. Esta guía muestra esa técnica desde la línea de comandos, Python, JavaScript y un cliente en el navegador, y también cómo localizar y limpiar en bloque, de forma segura, los topics retenidos obsoletos.

Por qué los mensajes retenidos se quedan atascados

Un mensaje retenido es el último mensaje que un broker guarda para un topic, de modo que cualquier nuevo suscriptor recibe de inmediato el valor actual. Es exactamente lo que quieres para el estado de un dispositivo, un documento de configuración o la última lectura de un sensor. Si la función es nueva para ti, la guía de mensajes retenidos MQTT explica su funcionamiento en detalle.

El problema empieza cuando el valor almacenado deja de ser cierto. Situaciones típicas:

  • Se dio de baja un dispositivo, pero su último estado online sigue apareciendo en los paneles.
  • Una publicación de prueba con el flag retain durante el desarrollo contamina ahora un árbol de topics de producción.
  • Se renombró un topic de configuración y el antiguo sigue entregando ajustes desactualizados a los nuevos clientes.
  • Un fallo de firmware publicó JSON mal formado con retain y todos los suscriptores que se reconectan fallan al procesarlo.

Como el broker reproduce el valor retenido en cada nueva suscripción, el problema nunca desaparece por sí solo. Tienes que sustituir el valor almacenado o eliminarlo.

Cómo borrar un mensaje retenido en MQTT

La especificación MQTT define la regla en una sola frase: un paquete PUBLISH con el flag RETAIN a 1 y un payload de longitud cero elimina cualquier mensaje retenido de ese topic, y el broker no almacena uno nuevo. El mensaje vacío también se reenvía a los suscriptores actuales, así que tus consumidores deben tolerar un payload vacío en lugar de intentar interpretarlo como JSON.

En la práctica importan algunos detalles:

  1. El topic debe coincidir exactamente. Los mensajes retenidos se almacenan por nombre de topic. Publicar un mensaje retenido vacío en sensors/# no hace nada, porque no se permiten comodines en los nombres de topic en los que publicas.
  2. El payload debe estar realmente vacío. Un espacio, un objeto JSON vacío {} o la cadena null son payloads reales y quedarán retenidos como nuevo valor.
  3. El QoS no importa demasiado. QoS 0 basta para una limpieza, pero QoS 1 te da un acuse de recibo de que el broker lo recibió.
  4. Necesitas permiso de publicación en ese topic. Si tus credenciales no pueden publicar ahí, el borrado se rechaza o se descarta en silencio, según el broker y la versión del protocolo.

Borrar mensajes retenidos con mosquitto_pub

Las herramientas de línea de comandos de Mosquitto son la forma más rápida de limpiar un único topic. El flag -r activa retain y -n envía un mensaje nulo (de longitud cero):

# Inspeccionar lo que hay retenido actualmente en el topic
mosquitto_sub -h broker.connectmq.com -p 1883 -i inspect-7f3a \
  -t "connectmq/demo/7f3a/device42/status" -v -C 1 -W 5

# Borrar el mensaje retenido
mosquitto_pub -h broker.connectmq.com -p 1883 -i cleanup-7f3a \
  -t "connectmq/demo/7f3a/device42/status" -r -n

# Confirmar: ahora debería agotar el tiempo sin imprimir nada
mosquitto_sub -h broker.connectmq.com -p 1883 -i verify-7f3a \
  -t "connectmq/demo/7f3a/device42/status" -v -C 1 -W 5

La combinación -C 1 -W 5 indica a mosquitto_sub que salga tras un mensaje o cinco segundos, lo que lo convierte en una sonda rápida para saber si hay algo retenido. Los ejemplos usan el servidor MQTT público gratuito, pensado para pruebas y prototipos y no para datos de producción, con un prefijo único; sustituye 7f3a por tu propio ID aleatorio.

En un broker privado, añade credenciales y TLS:

mosquitto_pub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs \
  -u YOUR_USERNAME -P YOUR_PASSWORD -i YOUR_CLIENT_ID \
  -t "plant1/line2/press/status" -r -n -q 1

Limpieza en bloque con --remove-retained

mosquitto_sub tiene una opción útil para subárboles completos. Con --remove-retained, cada mensaje retenido que recibe se borra publicando un mensaje retenido vacío en ese mismo topic. Combínalo con --retained-only para que solo actúe sobre los valores retenidos e ignore el tráfico en directo:

mosquitto_sub -h broker.connectmq.com -p 1883 -i purge-7f3a \
  -t "connectmq/demo/7f3a/#" --retained-only --remove-retained -v -W 5

Ejecuta primero el comando de inspección, lee la lista con atención y después purga. Un comodín como # sobre un árbol compartido puede borrar mucho más de lo que pretendías.

Borrar un mensaje retenido con Python

Cuando la limpieza forma parte de un script de baja de dispositivos, Python con paho-mqtt 2.x resulta cómodo. Pasar payload=None produce un payload de longitud cero:

import time
import paho.mqtt.client as mqtt

BROKER = "broker.connectmq.com"
TOPIC = "connectmq/demo/7f3a/device42/status"

def on_connect(client, userdata, flags, reason_code, properties):
    print("Connected:", reason_code)
    info = client.publish(TOPIC, payload=None, qos=1, retain=True)
    info.wait_for_publish(timeout=5)
    print("Retained message cleared on", TOPIC)
    client.disconnect()

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="cleanup-py-7f3a")
client.on_connect = on_connect
client.connect(BROKER, 1883, keepalive=30)
client.loop_forever()

Localizar y limpiar un subárbol completo

Esta versión se suscribe con un comodín, recopila todos los topics que entregan un mensaje retenido durante una ventana breve y luego los borra uno a uno. Limita el ritmo de publicación, algo importante en el servidor público, donde cada cliente está limitado a 10 mensajes por segundo.

import time
import paho.mqtt.client as mqtt

BROKER = "broker.connectmq.com"
FILTER = "connectmq/demo/7f3a/#"
found = set()

def on_message(client, userdata, msg):
    if msg.retain and msg.payload:
        found.add(msg.topic)

def on_connect(client, userdata, flags, reason_code, properties):
    client.subscribe(FILTER, qos=1)

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="purge-py-7f3a")
client.on_connect = on_connect
client.on_message = on_message
client.connect(BROKER, 1883, keepalive=30)
client.loop_start()
time.sleep(3)  # recopilar los mensajes retenidos

print(f"Found {len(found)} retained topics")
for topic in sorted(found):
    print("Clearing", topic)
    client.publish(topic, payload=None, qos=1, retain=True).wait_for_publish(5)
    time.sleep(0.15)  # mantenerse por debajo del límite de ritmo por cliente

client.loop_stop()
client.disconnect()

Fíjate en la comprobación de msg.retain. Los brokers activan el flag retain en los mensajes entregados a causa de una nueva suscripción, de modo que los mensajes en directo que lleguen durante la ventana no se confunden con valores almacenados.

Borrar un mensaje retenido desde JavaScript o el navegador

Con MQTT.js se aplica la misma regla: una cadena vacía o un buffer vacío con retain: true.

const mqtt = require("mqtt");

const client = mqtt.connect("mqtts://broker.connectmq.com:8883", {
  clientId: "cleanup-js-7f3a",
  protocolVersion: 5,
});

client.on("connect", () => {
  client.publish(
    "connectmq/demo/7f3a/device42/status",
    "",
    { qos: 1, retain: true },
    (err) => {
      console.log(err ? `Failed: ${err.message}` : "Retained message cleared");
      client.end();
    }
  );
});

Si prefieres no escribir código, el cliente MQTT en el navegador del portal de ConnectMQ también sirve: conéctate a tu broker, introduce el topic exacto, deja vacío el campo del payload, activa retain y publica. El explorador de topics en directo es una buena forma de ver qué topics conservan todavía un valor antes y después de la limpieza.

Resolución de problemas: un mensaje retenido que no desaparece

Síntoma Causa probable Solución
El valor antiguo reaparece segundos después Un dispositivo o servicio lo sigue publicando con retain Localiza al publicador, corrige su flag retain y vuelve a borrar
No cambia nada tras la publicación vacía El topic no coincide (mayúsculas, barra final, nivel de más) Copia el topic exacto de la salida de una suscripción con -v
Los suscriptores fallan tras la limpieza Los consumidores intentan interpretar el payload vacío Ignora los mensajes de longitud cero en el manejador de mensajes
El valor se sustituye en lugar de eliminarse El payload era un espacio, {} o null Usa -n o payload=None, no una cadena de relleno
La limpieza funciona en pruebas pero no en producción Las credenciales no tienen permiso de publicación en ese topic Usa la cuenta propietaria del árbol de topics

La primera fila es, con diferencia, la más habitual. Un valor retenido que "vuelve" casi siempre es porque algo lo está republicando: a menudo un script de bridge, un flujo de Node-RED o un dispositivo que publica su estado con retain en cada reconexión.

Evita los datos retenidos obsoletos desde el principio

Borrar mensajes retenidos es una tarea de limpieza; diseñarlos bien reduce la frecuencia con la que la necesitas.

  • Usa la expiración de mensajes de MQTT 5. Fija un Message Expiry Interval en las publicaciones retenidas para que el broker las descarte pasado un tiempo. La guía del intervalo de expiración de mensajes cubre valores razonables.
  • Combina el estado con el Last Will. Un estado online retenido debe ir acompañado de un mensaje Last Will offline retenido, para que el valor almacenado siga siendo exacto cuando un dispositivo se cae.
  • Mantén acotados los topics retenidos. Retén estado, no flujos. La telemetría de alta frecuencia rara vez necesita retain, y cuantos menos topics retenidos, menos hay que limpiar.
  • No uses los mensajes retenidos como base de datos. Guardan el último valor por comodidad, no un historial duradero. Almacena lo que necesites conservar en una base de datos real alimentada por un suscriptor.
  • Documenta qué topics son retenidos. Una lista breve en tus convenciones de nombres de topics evita sorpresas cuando alguien añade una suscripción con comodín.

El comportamiento completo del flag retain está especificado en el estándar OASIS MQTT 5.0, y el tutorial de MQTT, más amplio, sitúa los mensajes retenidos en contexto junto con el QoS, las sesiones y los wills.

Gestiona los topics retenidos en ConnectMQ

ConnectMQ admite mensajes retenidos tanto en MQTT 3.1.1 como en 5.0, así que todas las técnicas anteriores funcionan sin cambios. Si vas más allá de los experimentos, un broker MQTT gestionado te ofrece credenciales privadas, TLS en el puerto 8883 y un portal con cliente en el navegador y explorador de topics en directo para este tipo de mantenimiento. Puedes leer más sobre cómo usar un broker MQTT en la nube sin tener que operar servidores tú mismo.

Crea un broker Serverless gratuito en app.connectmq.com, sin tarjeta de crédito, y tendrás un usuario, una contraseña y un client ID en un par de minutos.

Preguntas frecuentes

¿Cómo borro un mensaje retenido en MQTT?

Publica en exactamente el mismo topic un mensaje con payload de longitud cero y el flag retain activado. Con Mosquitto es mosquitto_pub -t <topic> -r -n, y el broker deja de almacenar un valor retenido para ese topic.

¿Puedo borrar todos los mensajes retenidos con un comodín?

No con una sola publicación, porque no se permiten comodines en los nombres de topic. Suscríbete con un filtro con comodín para descubrir los topics retenidos y luego borra cada uno, o usa mosquitto_sub --retained-only --remove-retained.

¿Enviar un objeto JSON vacío borra el mensaje retenido?

No. {} es un payload de dos bytes, así que el broker lo retiene como nuevo valor. Solo un payload realmente vacío elimina el mensaje retenido.

¿Reciben los suscriptores el mensaje vacío?

Sí, los suscriptores actuales reciben el mensaje de longitud cero como cualquier otra publicación. Asegúrate de que tus consumidores ignoren los payloads vacíos en lugar de fallar al interpretarlos.

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.