El QoS (Quality of Service, calidad de servicio) de MQTT es la garantía de entrega que solicita un cliente cuando publica o se suscribe. En esta guía verás exactamente qué paquetes intercambian QoS 0, 1 y 2, por qué el QoS que llega a un suscriptor puede ser menor que el usado al publicar, cómo encolan las sesiones los mensajes para clientes desconectados y cómo probarlo todo en Python contra broker.connectmq.com.
Si MQTT es nuevo para ti, empieza por ¿Qué es un broker MQTT? y vuelve aquí después.
Qué garantiza realmente el QoS de MQTT
MQTT funciona sobre TCP, así que dentro de una misma conexión los bytes no se pierden ni se desordenan. El QoS trata de otra cosa: qué ocurre cuando una conexión se corta en mitad de una entrega y si el receptor podría recibir el mismo mensaje dos veces.
Hay dos puntos que se malinterpretan a menudo:
- El QoS es salto a salto, no de extremo a extremo. El QoS del publicador rige el tramo del publicador al broker. El QoS de la suscripción rige el tramo del broker al suscriptor. El broker hace de intermediario en ambos.
- Un QoS más alto no es «mejor» por defecto. Cada nivel añade paquetes, estado y latencia. La elección correcta depende de lo que te cueste un mensaje perdido o duplicado.
| Nivel | Nombre | Garantía | Paquetes por mensaje (un tramo) | ¿Puede haber duplicados? |
|---|---|---|---|---|
| QoS 0 | At most once (como máximo una vez) | Mejor esfuerzo, sin confirmación | 1 | No, pero puede haber pérdidas |
| QoS 1 | At least once (al menos una vez) | Se entrega, puede repetirse | 2 | Sí |
| QoS 2 | Exactly once (exactamente una vez) | Se entrega una vez, sin duplicados | 4 | No |
QoS 0: como máximo una vez
QoS 0 es «enviar y olvidar». El emisor escribe un paquete PUBLISH en el socket y se olvida de él inmediatamente. No hay identificador de paquete ni confirmación.
Publisher Broker
| |
|------ PUBLISH (QoS 0) ---->|
| | (no reply, nothing stored)
Si la conexión TCP se rompe mientras el paquete sigue en un búfer del sistema operativo, el mensaje se pierde y nadie se entera. Suena mal, pero para una lectura de temperatura enviada cada dos segundos, la siguiente lectura sustituye de todos modos a la perdida. QoS 0 te da la latencia más baja y la menor sobrecarga en dispositivos con recursos limitados.
QoS 1: al menos una vez
Con QoS 1, el emisor asigna un identificador de paquete y conserva el mensaje hasta que el receptor responde con un PUBACK que lleva el mismo identificador.
Publisher Broker
| |
|-- PUBLISH (QoS 1, id=7) -->| broker stores and forwards
|<------- PUBACK (id=7) -----|
| | publisher deletes message 7
Si el PUBACK no llega nunca (por ejemplo, porque se corta la conexión), el emisor reenvía el PUBLISH con el flag DUP activado tras reconectarse. El problema: puede que el receptor ya hubiera procesado la primera copia, así que ve el mensaje dos veces. Por eso QoS 1 significa al menos una vez.
QoS 1 es el caballo de batalla de la mayoría de los sistemas IoT. Haz que tus consumidores sean idempotentes (por ejemplo, incluye un ID de mensaje o una marca de tiempo en el payload e ignora las repeticiones) y QoS 1 suele ser todo lo que necesitas.
QoS 2: exactamente una vez
QoS 2 usa un handshake de cuatro pasos para que ambas partes coincidan en que el mensaje se entregó exactamente una vez.
Publisher Broker
| |
|--- PUBLISH (QoS 2, id=9) --->| broker stores id 9, does not forward twice
|<------- PUBREC (id=9) -------| "received"
|-------- PUBREL (id=9) ------>| "release it, I will not resend"
|<------- PUBCOMP (id=9) ------| "complete", both sides drop id 9
Qué hace cada paso:
- PUBLISH: el emisor guarda el mensaje y lo envía con un identificador de paquete.
- PUBREC: el receptor registra el identificador. Si llega un PUBLISH duplicado con el mismo ID, se reconoce y no se entrega de nuevo.
- PUBREL: el emisor descarta el mensaje y a partir de ahí solo recuerda el identificador.
- PUBCOMP: el receptor olvida el identificador. El intercambio ha terminado.
El coste son dos viajes de ida y vuelta por mensaje y un estado que ambas partes mantienen hasta que se completa el flujo. Usa QoS 2 cuando un duplicado causaría un daño real y no puedas hacer idempotente al consumidor, como un comando que incrementa un contador o que dispara un evento de facturación.
Degradación del QoS en MQTT: por qué puede que no obtengas el QoS que pediste
El QoS que recibe un suscriptor no es simplemente el que usó el publicador. Intervienen tres valores.
QoS efectivo = min(QoS de publicación, QoS de suscripción)
Cuando el broker reenvía un mensaje, usa el menor entre el QoS del PUBLISH original y el QoS concedido a la suscripción.
| QoS del publicador | QoS de la suscripción | Entregado al suscriptor |
|---|---|---|
| 0 | 2 | 0 |
| 1 | 2 | 1 |
| 2 | 1 | 1 |
| 2 | 0 | 0 |
| 2 | 2 | 2 |
Así que, si necesitas entrega exactamente una vez de extremo a extremo, tanto el publicador como cada suscriptor deben usar QoS 2. Una sola suscripción con QoS 0 convierte silenciosamente el flujo de ese suscriptor en mejor esfuerzo.
El Maximum QoS del broker (MQTT 5)
En MQTT 5, el broker puede anunciar un Maximum QoS en su paquete CONNACK. Si esta propiedad no aparece, el cliente puede suponer que se admite QoS 2. Si aparece, el cliente no debe publicar por encima de ese nivel y el broker concederá las suscripciones como máximo a ese nivel (el SUBACK te indica el QoS concedido).
En ConnectMQ depende de dónde te conectes:
| Entorno | Maximum QoS |
|---|---|
| Área pública de pruebas en broker.connectmq.com | QoS 2 |
| Plan Serverless (Free) | QoS 1 |
| Plan Serverless (Paid) | QoS 2 |
| Dedicated, BYOC, Self Managed Enterprise | QoS 2 |
Comprueba siempre el QoS concedido en el SUBACK en lugar de darlo por hecho. La mayoría de las librerías cliente lo exponen en el callback de suscripción. La especificación de MQTT 5 en docs.oasis-open.org describe las reglas exactas. Para repasar otras propiedades del CONNACK, consulta Características de MQTT 5.
Sesiones, expiración de sesión y cola offline
El QoS solo protege los mensajes a través de una desconexión si la sesión sobrevive a esa desconexión. La sesión es el estado que el broker mantiene para un cliente: sus suscripciones, los mensajes QoS 1/2 en tránsito y los mensajes encolados mientras está desconectado.
- MQTT 3.1.1 usa el flag Clean Session.
cleanSession=truedescarta la sesión al desconectarse.cleanSession=falsepide al broker que la conserve. - MQTT 5 lo divide en Clean Start (empezar de cero al conectarse o no) y Session Expiry Interval (cuántos segundos vive la sesión tras la desconexión).
Mientras existe una sesión persistente y el cliente está desconectado, el broker encola los mensajes QoS 1 y QoS 2 que coinciden con sus suscripciones. Los mensajes QoS 0 no se encolan para clientes desconectados. Cuando el cliente se reconecta con el mismo client ID y sin clean start, se le entregan los mensajes encolados.
Subscriber (id=sensor-reader) Broker
|-- CONNECT clean_start=0, expiry=600 -->|
|-- SUBSCRIBE plant/# QoS 1 ------------>|
| ... connection lost ... |
| | PUBLISH plant/a QoS 1 -> queued
| | PUBLISH plant/b QoS 0 -> dropped
|-- CONNECT clean_start=0 --------------->|
|<-- CONNACK session_present=1 -----------|
|<-- PUBLISH plant/a ---------------------|
En ConnectMQ, la expiración de sesión tiene un tope por plan, y en el área pública de pruebas las sesiones duran hasta 30 minutos. Ten en cuenta además que las sesiones se guardan en memoria: no sobreviven a un reinicio del broker, así que diseña los flujos críticos para que toleren una reconexión con session_present=0.
Cómo elegir el nivel de QoS de MQTT adecuado
| Caso de uso | QoS recomendado | Por qué |
|---|---|---|
| Telemetría de alta frecuencia (temperatura, vibración) | 0 | La siguiente muestra sustituye a la perdida; mínima sobrecarga |
| Estado de dispositivos, alarmas, eventos | 1 | Deben llegar; los duplicados son fáciles de ignorar |
| Comandos a actuadores (idempotentes: «poner la válvula al 40 %») | 1 | Repetir el mismo comando no causa daño |
| Comandos no idempotentes («incrementar contador», «dispensar») | 2 | Un duplicado cambiaría el resultado |
| Eventos de facturación, medición o transacciones | 2 (o 1 con IDs de deduplicación) | Contabilidad exactamente una vez |
Una regla práctica: usa QoS 1 por defecto con consumidores idempotentes, baja a QoS 0 para datos de alta frecuencia que puedas permitirte perder y reserva QoS 2 para los pocos tipos de mensaje en los que un duplicado es un error.
Práctica: QoS 1 y QoS 2 de MQTT en Python
El siguiente script usa paho-mqtt 2.x y el área pública de broker.connectmq.com (sin usuario ni contraseña). Se suscribe con QoS 2, publica un mensaje con QoS 1 y otro con QoS 2 y registra las confirmaciones. Instala la librería con pip install "paho-mqtt>=2.0".
import time
import uuid
import paho.mqtt.client as mqtt
BROKER = "broker.connectmq.com"
UID = uuid.uuid4().hex[:8]
TOPIC = f"connectmq/demo/{UID}/qos"
def on_connect(client, userdata, flags, reason_code, properties):
print("CONNACK:", reason_code)
if properties is not None and hasattr(properties, "MaximumQoS"):
print("Broker Maximum QoS:", properties.MaximumQoS)
client.subscribe(f"{TOPIC}/#", qos=2)
def on_subscribe(client, userdata, mid, reason_code_list, properties):
print("SUBACK granted:", [rc.value for rc in reason_code_list])
def on_message(client, userdata, msg):
print(f"Received {msg.topic} QoS={msg.qos} payload={msg.payload.decode()}")
def on_publish(client, userdata, mid, reason_code, properties):
print(f"Publish flow complete for mid={mid}")
client = mqtt.Client(
mqtt.CallbackAPIVersion.VERSION2,
client_id=f"qos-demo-{UID}",
protocol=mqtt.MQTTv5,
)
client.on_connect = on_connect
client.on_subscribe = on_subscribe
client.on_message = on_message
client.on_publish = on_publish
client.connect(BROKER, 1883, keepalive=30)
client.loop_start()
time.sleep(2)
client.publish(f"{TOPIC}/one", "hello at least once", qos=1).wait_for_publish()
client.publish(f"{TOPIC}/two", "hello exactly once", qos=2).wait_for_publish()
time.sleep(2)
client.loop_stop()
client.disconnect()
Con QoS 1, on_publish se dispara cuando llega el PUBACK. Con QoS 2, solo se dispara después del PUBCOMP, al final del handshake de cuatro pasos. Si no se imprime ninguna línea de Maximum QoS, el broker no envió la propiedad, lo que significa que QoS 2 está permitido.
Observa los paquetes con la CLI de Mosquitto
Las herramientas de línea de comandos de Eclipse Mosquitto imprimen cada paquete con -d, lo que hace visibles los flujos anteriores. En una terminal:
mosquitto_sub -h broker.connectmq.com -p 1883 -i qos-sub-a1b2 -t "connectmq/demo/a1b2/#" -q 2 -v -d
En una segunda terminal, publica con QoS 1 y después con QoS 2:
mosquitto_pub -h broker.connectmq.com -p 1883 -i qos-pub-a1b2 -t "connectmq/demo/a1b2/q1" -q 1 -m "hello1" -d
mosquitto_pub -h broker.connectmq.com -p 1883 -i qos-pub-a1b2 -t "connectmq/demo/a1b2/q2" -q 2 -m "hello2" -d
La salida del publicador muestra exactamente las secuencias descritas antes:
Client qos-pub-a1b2 sending PUBLISH (d0, q1, r0, m1, 'connectmq/demo/a1b2/q1', ... (6 bytes))
Client qos-pub-a1b2 received PUBACK (Mid: 1, RC:0)
...
Client qos-pub-a1b2 sending PUBLISH (d0, q2, r0, m1, 'connectmq/demo/a1b2/q2', ... (6 bytes))
Client qos-pub-a1b2 received PUBREC (Mid: 1)
Client qos-pub-a1b2 sending PUBREL (m1)
Client qos-pub-a1b2 received PUBCOMP (Mid: 1, RC:0)
Sustituye a1b2 por tu propia cadena aleatoria. Todo lo que hay en el área pública puede leerlo cualquiera, así que nunca publiques allí datos sensibles. Para experimentar con la degradación, suscríbete con -q 0 y publica con -q 2: la salida de depuración del suscriptor mostrará q0 en el PUBLISH entregado. Encontrarás más consejos sobre el servidor público en la guía del servidor MQTT público gratuito.
Errores habituales con el QoS
- Suponer QoS 2 de extremo a extremo mientras un suscriptor usa QoS 0. Manda el eslabón más débil.
- Usar QoS 2 para telemetría de alta frecuencia. Cuatro paquetes por mensaje y por tramo se acumulan rápido, y la ventana de mensajes en tránsito (Receive Maximum) se llena.
- Client IDs aleatorios con sesiones persistentes. La cola offline solo funciona si el cliente se reconecta con el mismo client ID. Un ID aleatorio nuevo crea una sesión nueva y vacía.
- Ignorar el QoS concedido en el SUBACK. En un plan limitado a QoS 1, una suscripción con QoS 2 se concede como QoS 1.
Empieza con ConnectMQ
El servidor MQTT público gratuito es ideal para aprender el QoS de MQTT, pero el tráfico de producción debe ir a un broker MQTT gestionado privado con credenciales. Regístrate gratis en app.connectmq.com (plan Serverless, sin tarjeta de crédito), crea un broker y usa el usuario, la contraseña y el client ID generados. Pasa a Serverless (Paid) o Dedicated cuando necesites QoS 2, más conexiones o mensajes más grandes.
Preguntas frecuentes
¿Cuál es el nivel de QoS de MQTT por defecto?
La mayoría de las librerías cliente usan QoS 0 por defecto tanto para publicar como para suscribirse. Indica el QoS explícitamente en tu código para que el comportamiento quede claro para cualquiera que lo lea.
¿QoS 2 hace que MQTT sea más lento?
Sí. QoS 2 necesita cuatro paquetes y dos viajes de ida y vuelta por mensaje en cada tramo, frente a dos paquetes en QoS 1 y uno en QoS 0. Para la mayor parte de la telemetría, QoS 0 o 1 ofrecen mejor rendimiento.
¿Por qué mi suscriptor recibió QoS 1 si publiqué con QoS 2?
El QoS entregado es el mínimo entre el QoS de publicación y el QoS concedido a la suscripción. Tu suscripción se concedió con QoS 1, bien porque lo solicitaste, bien porque el Maximum QoS del broker es 1, como ocurre en el plan Serverless (Free) de ConnectMQ.
¿Se encolan los mensajes QoS 0 para clientes desconectados?
No. Los brokers solo encolan mensajes QoS 1 y QoS 2 para clientes con sesión persistente. Los mensajes QoS 0 dirigidos a un cliente desconectado se descartan.
¿Puedo usar QoS 2 en broker.connectmq.com?
Sí. El área pública anónima admite QoS 0, 1 y 2. Los brokers privados admiten QoS 2 en Serverless (Paid) y planes superiores, mientras que el plan Serverless gratuito está limitado a QoS 1.



