Fundamentos de MQTT

Receive Maximum en MQTT: control de flujo para dispositivos lentos en MQTT 5

Receive Maximum es el control de flujo integrado de MQTT 5. Limita cuántos mensajes QoS 1 y 2 sin confirmar puede haber en vuelo, para que los dispositivos lentos no se saturen tras una reconexión.

Ilustración de Receive Maximum en MQTT con una ventana limitada de mensajes en vuelo que fluyen desde un broker hacia un pequeño microcontrolador
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

Receive Maximum es el ajuste de MQTT 5 que indica a la otra parte cuántos mensajes QoS 1 y QoS 2 estás dispuesto a tener "en vuelo", sin confirmar, al mismo tiempo. En esta guía aprenderás cómo funciona esta ventana de control de flujo en ambos sentidos, por qué es importante para los microcontroladores y las ráfagas tras una reconexión, y cómo configurarla en mosquitto, Python y Node.js.

Qué problema resuelve Receive Maximum

MQTT 3.1.1 no tenía un control de flujo estándar. Un broker podía enviar mensajes QoS 1 a un cliente tan rápido como lo permitiera la conexión TCP, y el cliente tenía que almacenar cada uno de ellos hasta enviar el PUBACK. En un servidor eso funciona, pero un microcontrolador con unos pocos kilobytes de RAM libre puede quedarse sin memoria, perder mensajes o bloquearse. El problema es peor justo después de una reconexión, cuando una sesión persistente libera de golpe una acumulación de mensajes encolados.

Los brokers y las librerías inventaron sus propios ajustes de "máximo en vuelo" para afrontarlo, pero no se negociaban, así que ninguna de las partes conocía el límite de la otra. MQTT 5 estandarizó la idea como la propiedad Receive Maximum.

Cómo funciona la ventana de Receive Maximum en MQTT

Receive Maximum es un entero de dos bytes, de 1 a 65.535, que cada parte puede declarar:

  • El cliente lo envía en el CONNECT: "Broker, no me envíes más de N paquetes PUBLISH QoS 1/2 sin confirmar".
  • El broker lo envía en el CONNACK: "Cliente, no me envíes más de N paquetes PUBLISH QoS 1/2 sin confirmar".

Si la propiedad no está presente, el valor por defecto es 65.535, lo que en la práctica significa "sin límite". No se permite el valor 0.

Qué cuenta como en vuelo

Tipo de paquete ¿Cuenta para la ventana? Se libera cuando
PUBLISH QoS 0 No No aplica
PUBLISH QoS 1 Sí Se recibe el PUBACK
PUBLISH QoS 2 Sí Se recibe el PUBCOMP (o un PUBREC con un reason code de error)
Mensaje retenido enviado al suscribirse Sí, si se entrega con QoS 1 o 2 Confirmación normal
Mensajes offline encolados Sí, con QoS 1 o 2 Confirmación normal

El emisor mantiene un contador (la especificación lo llama send quota). Cada PUBLISH QoS 1 o 2 lo decrementa; cada confirmación que completa un intercambio lo incrementa. Cuando llega a cero, el emisor debe dejar de enviar mensajes QoS 1 y 2 y esperar. Puede seguir enviando mensajes QoS 0, y la especificación le permite seguir enviando PINGREQ, así que el keep alive no se ve afectado.

Qué ocurre si se supera el límite

Si un emisor ignora la ventana y el receptor recibe más mensajes QoS 1/2 sin confirmar de los que anunció, el receptor debe desconectarse con el reason code 0x93 (Receive Maximum exceeded). En la práctica rara vez lo verás, porque las librerías habituales aplican la ventana automáticamente. Cuando aparece, casi siempre apunta a una implementación de cliente personalizada o muy antigua.

Por qué es importante para los dispositivos lentos

Piensa en un ESP32 que se reconecta tras un corte de Wi-Fi de 10 minutos. Su sesión contiene 200 actualizaciones de configuración QoS 1 encoladas. Sin control de flujo, el broker puede enviar las 200 de golpe. Con un Receive Maximum de 5, el broker envía cinco, espera los PUBACK y envía más a medida que llega cada confirmación. El uso de memoria queda acotado, el procesamiento no se queda atrás y el dispositivo sigue respondiendo.

La ventana también ofrece una contrapresión natural para los consumidores backend: si un worker envía el PUBACK solo después de haber escrito un mensaje en su base de datos, un Receive Maximum pequeño impide que el broker acumule más trabajo del que el worker puede gestionar. Es un buen complemento de las suscripciones compartidas de MQTT, donde varios workers comparten un flujo y la ventana de cada worker limita su parte del trabajo en vuelo.

Cómo configurar Receive Maximum en los clientes

Prueba contra el servidor MQTT público gratuito en broker.connectmq.com con un prefijo único como connectmq/demo/r2m6/.... El área pública está abierta a todo el mundo y sirve para pruebas, no para datos de producción, así que usa payloads ficticios.

mosquitto_sub

mosquitto_sub -h broker.connectmq.com -p 1883 -V mqttv5 \
  -i recvmax-demo-r2m6 -q 1 -v -d \
  -D connect receive-maximum 5 \
  -t 'connectmq/demo/r2m6/#'

La salida de -d muestra las propiedades del CONNECT que enviaste y las propiedades del CONNACK que devolvió el broker, incluido su propio Receive Maximum si fijó uno.

Python (paho-mqtt 2.x)

import time
import paho.mqtt.client as mqtt
from paho.mqtt.properties import Properties
from paho.mqtt.packettypes import PacketTypes

def on_connect(client, userdata, flags, reason_code, properties):
    server_rm = getattr(properties, "ReceiveMaximum", 65535)
    print("Connected:", reason_code, "| broker receive maximum:", server_rm)
    client.subscribe("connectmq/demo/r2m6/#", qos=1)

def on_message(client, userdata, msg):
    # Simula un dispositivo lento; paho envía el PUBACK cuando este callback termina
    time.sleep(0.5)
    print("Handled", msg.topic, msg.payload.decode())

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
                     client_id="recvmax-py-r2m6",
                     protocol=mqtt.MQTTv5)
client.on_connect = on_connect
client.on_message = on_message

# Limita lo que *nosotros* enviamos de forma concurrente (ventana de salida)
client.max_inflight_messages_set(10)

# Pide al broker que limite lo que *él* nos envía (ventana de entrada)
props = Properties(PacketTypes.CONNECT)
props.ReceiveMaximum = 5

client.connect("broker.connectmq.com", 1883, keepalive=60, properties=props)
client.loop_forever()

Publica una ráfaga desde otro terminal y observa cómo llegan los mensajes a un ritmo constante:

for i in $(seq 1 30); do
  mosquitto_pub -h broker.connectmq.com -p 1883 -i burst-r2m6 \
    -q 1 -t 'connectmq/demo/r2m6/burst' -m "msg $i"
done

Ten en cuenta que el time.sleep dentro del callback también bloquea el bucle de red de paho. Para esta demostración no importa, pero en producción lleva el trabajo lento a un hilo aparte y deja libre el bucle, o los pings de keep alive se retrasarán.

Node.js (MQTT.js v5)

const mqtt = require("mqtt");

const client = mqtt.connect("mqtts://broker.connectmq.com:8883", {
  clientId: "recvmax-node-r2m6",
  protocolVersion: 5,
  properties: { receiveMaximum: 5 },
});

client.on("connect", (connack) => {
  console.log("broker properties:", connack.properties);
  client.subscribe("connectmq/demo/r2m6/#", { qos: 1 });
});

client.on("message", (topic, payload) => {
  console.log(topic, payload.toString());
});

Cómo elegir un valor de Receive Maximum

Cliente Valor sugerido Por qué
Microcontrolador (ESP32, ESP8266) De 1 a 10 RAM limitada, procesamiento secuencial
Gateway Raspberry Pi De 10 a 50 Más memoria, pero CPU todavía modesta
Worker backend que escribe en una base de datos Aproximadamente el número de escrituras paralelas que puede sostener La contrapresión se ajusta al rendimiento real
Dashboard con valores en directo Por defecto Tráfico mayoritariamente QoS 0, que no está limitado

Un valor de 1 ofrece un procesamiento estricto de uno en uno, lo que también puede simplificar la lógica de ordenación, a costa del rendimiento en enlaces de alta latencia. Cada hueco en vuelo requiere un viaje de ida y vuelta, así que en un enlace móvil con mucha latencia una ventana diminuta puede limitar notablemente el rendimiento. Mídelo con tu tráfico real.

Receive Maximum y el orden de los mensajes

Una ventana mayor que 1 no rompe las garantías de orden de MQTT: el broker sigue enviando los mensajes de un topic en el orden en que los recibió, y el cliente los sigue recibiendo en ese orden. Lo que cambia es cuántos pueden estar esperando confirmación a la vez. Si tu aplicación confirma los mensajes fuera de orden, por ejemplo al pasarlos a un pool de hilos, el orden de procesamiento es responsabilidad tuya, no del protocolo. El comportamiento es el mismo tanto si te conectas a un broker de ConnectMQ como a un Mosquitto local o a cualquier otro broker MQTT 5, porque la ventana la negocia el propio protocolo en lugar de configurarse en un solo lado.

Lista de comprobación para la resolución de problemas

  1. Los mensajes llegan despacio tras reconectar. Es la ventana haciendo su trabajo. Auméntala si el dispositivo lo soporta, o reduce la acumulación con la caducidad de mensajes.
  2. El dispositivo sigue quedándose sin memoria. Comprueba si la mayor parte del tráfico es QoS 0; Receive Maximum no lo limita. Revisa también el tamaño máximo de paquete.
  3. El publicador se atasca. La send quota de tu cliente llegó a cero porque el Receive Maximum del broker es pequeño o los PUBACK tardan. Registra las propiedades del CONNACK.
  4. Desconexión con 0x93. Un cliente envió más mensajes en vuelo de los permitidos; actualiza o corrige la librería cliente.
  5. El ajuste no tiene efecto. Te estás conectando con MQTT 3.1.1. Receive Maximum solo existe en MQTT 5.

Para entender cómo se acumulan los mensajes encolados en primer lugar, consulta cómo encola MQTT los mensajes para clientes offline. ¿Eres nuevo en el protocolo? Empieza por el tutorial de MQTT.

Ajusta el control de flujo en ConnectMQ

ConnectMQ ejecuta MQTT 3.1.1 y 5.0 sobre TCP, TLS, WebSocket y WebSocket seguro, así que las propiedades de MQTT 5 como Receive Maximum funcionan desde cualquier cliente. Las cuotas por plan y la protección contra avalanchas mantienen estable un broker MQTT compartido en la nube, mientras que Receive Maximum protege a cada uno de tus dispositivos. Crea un broker Serverless gratuito, sin tarjeta de crédito, conéctate con YOUR_USERNAME, YOUR_PASSWORD y YOUR_CLIENT_ID, y usa el cliente integrado en el navegador del portal para lanzar ráfagas de prueba contra tus dispositivos. Para flotas en producción, un broker MQTT gestionado elimina por completo el trabajo de operar el broker.

Preguntas frecuentes

¿Cuál es el Receive Maximum por defecto en MQTT?

Si no se envía la propiedad, el Receive Maximum es 65.535, lo que en la práctica significa que no hay límite. Los clientes deben fijar explícitamente un valor menor para obtener control de flujo.

¿Limita Receive Maximum los mensajes QoS 0?

No. Solo cuenta los paquetes PUBLISH QoS 1 y QoS 2 que aún no se han confirmado. Los mensajes QoS 0 nunca están en vuelo en ese sentido.

¿Está disponible Receive Maximum en MQTT 3.1.1?

No. Es una propiedad de MQTT 5. Los clientes MQTT 3.1.1 dependen de ajustes de mensajes en vuelo propios de cada librería que no se comunican al broker.

¿Qué significa el reason code 0x93?

Significa Receive Maximum exceeded: el emisor tenía en vuelo más mensajes QoS 1 o 2 sin confirmar de los que permitía el receptor, así que el receptor cerró la conexión.

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.