Una prueba de estrés de conexiones MQTT responde a una pregunta sencilla: ¿cuántos clientes puede mantener este broker a la vez y a qué velocidad puede aceptarlos? El método consiste en aumentar conexiones inactivas a un ritmo controlado mientras observas la memoria, la CPU y la latencia de conexión del broker, y en asegurarte de que el límite que alcanzas pertenece al broker y no a la máquina que genera la carga.
La mayoría de los primeros intentos se detienen en un número sospechosamente redondo. Esta guía muestra cómo eliminar esos límites del lado del cliente, ejecutar la prueba con emqtt-bench o con un script de Node.js, y simular la tormenta de reconexiones que sigue a cada reinicio del broker.
Qué estás midiendo realmente
| Métrica | Por qué importa |
|---|---|
| Máximo de conexiones concurrentes | Tamaño de flota que el broker puede mantener en reposo |
| Tasa de conexión (CONNECT/s) | Lo rápido que una flota puede volver tras una caída |
| Latencia de conexión (p50/p99) | Tiempo desde el SYN de TCP hasta el CONNACK bajo carga |
| Memoria del broker por conexión | Planificación de capacidad; mídela, no la adivines |
| Tipos de error | Rechazos, timeouts, reason codes del CONNACK |
Las conexiones le cuestan al broker memoria para buffers de socket, estado de sesión y suscripciones, además de CPU para los handshakes TLS y el procesamiento del keep-alive. Todo esto depende del broker, la versión y la configuración, así que la única cifra con sentido es la que mides con tu configuración: mismo TLS, mismo keep-alive, misma autenticación y aproximadamente las mismas suscripciones por cliente.
Primero arregla el generador de carga
Una sola máquina Linux tiene tres límites que frenan las conexiones salientes mucho antes de que un broker decente se rinda.
Descriptores de archivo
Cada socket TCP es un descriptor de archivo. El límite blando por defecto suele ser 1.024:
ulimit -n # límite blando actual
ulimit -n 200000 # subirlo en esta shell (hasta el límite duro)
cat /proc/sys/fs/file-max # techo del sistemaPara un cambio permanente, define nofile en /etc/security/limits.conf o, en Docker, pasa --ulimit nofile=200000:200000.
Puertos efímeros
Cada conexión desde una IP de origen hacia una misma IP y puerto del broker necesita un puerto local único. El rango por defecto de Linux es 32768–60999, unos 28.000 puertos: el sitio clásico donde las pruebas se estancan.
sysctl net.ipv4.ip_local_port_range
sudo sysctl -w net.ipv4.ip_local_port_range="1024 65000"Eso te da unas 64.000 conexiones por IP de origen. Más allá necesitas más IP de origen (o más puertos/IP del broker, o más máquinas de carga).
Seguimiento de conexiones
Si el generador de carga tiene un firewall con seguimiento de conexiones (habitual con Docker e iptables), la tabla de conntrack puede llenarse. El log del kernel muestra entonces nf_conntrack: table full, dropping packet.
sudo sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
sudo sysctl -w net.netfilter.nf_conntrack_max=262144Ejecutar la prueba con emqtt-bench
emqtt-bench está hecho para esto. El comando conn abre conexiones a un ritmo fijo y las mantiene vivas:
docker run --rm -it --ulimit nofile=200000:200000 emqx/emqtt-bench conn \
-h "$MQTT_HOST" -p 1883 \
-c 10000 -R 500 -k 60 \
-u "$MQTT_USER" -P "$MQTT_PASS"Define MQTT_HOST con tu propio broker; el endpoint compartido broker.connectmq.com es solo para pequeñas pruebas de humo. -c es el número de clientes, -R la tasa de conexión por segundo y -k el keep-alive en segundos. Usa el mismo keep-alive que tus dispositivos: keep-alives más cortos significan más tráfico PINGREQ por cliente inactivo.
Para pasar de una IP de origen, añade direcciones secundarias a la máquina de carga y deja que emqtt-bench reparta los clientes entre ellas con --ifaddr. Usa la red del host para que el contenedor vea esas direcciones:
docker run --rm -it --network host --ulimit nofile=200000:200000 \
emqx/emqtt-bench conn \
-h "$MQTT_HOST" -p 1883 \
-c 50000 -R 1000 -k 60 \
--ifaddr 10.0.0.11,10.0.0.12Sube por escalones, por ejemplo 10k, 25k, 50k, y mantén cada nivel varios minutos. Un broker que acepta 50.000 conexiones pero empieza a perderlas en el siguiente ciclo de keep-alive no ha superado la prueba.
Un script de Node.js que puedes modificar
Cuando necesitas flujos de autenticación reales, suscripciones por cliente o client IDs personalizados, un pequeño script con MQTT.js 5 es más fácil de adaptar que una herramienta de benchmark:
import mqtt from "mqtt";
const URL = process.env.MQTT_URL ?? "mqtt://broker.connectmq.com:1883";
const TOTAL = Number(process.argv[2] ?? 100); // por defecto: una pequeña prueba de humo
const RATE = Number(process.argv[3] ?? 200); // conexiones nuevas por segundo
let opened = 0, up = 0, errors = 0, closes = 0;
function open(i) {
const c = mqtt.connect(URL, {
clientId: `stress-${process.pid}-${i}`,
username: process.env.MQTT_USER,
password: process.env.MQTT_PASS,
keepalive: 60,
connectTimeout: 15_000,
reconnectPeriod: 2000 + Math.floor(Math.random() * 8000), // jitter por cliente
});
let online = false;
c.on("connect", () => {
if (!online) { online = true; up++; }
c.subscribe(`testmqtt/stress/${i}/cmd`, { qos: 1 });
});
c.on("close", () => { if (online) { online = false; up--; } closes++; });
c.on("error", () => errors++);
}
const timer = setInterval(() => {
for (let k = 0; k < RATE / 10 && opened < TOTAL; k++) open(opened++);
if (opened >= TOTAL) clearInterval(timer);
}, 100);
setInterval(() => {
console.log(`${new Date().toISOString()} opened=${opened} connected=${up} closes=${closes} errors=${errors}`);
}, 1000);npm install mqtt@5
ulimit -n 100000
MQTT_URL="mqtts://$MQTT_HOST:8883" node conn-stress.mjs 20000 300Un proceso de Node.js mantiene sin problema varios miles de conexiones inactivas; para más, ejecuta varios procesos o máquinas. Vigila la CPU del generador de carga: si está al máximo, tus cifras miden el cliente, no el broker. Para interpretar la respuesta del broker a conexiones rechazadas, busca el código del CONNACK en la referencia de reason codes.
Observa el lado del broker
Los contadores del cliente solo cuentan la mitad de la historia. En cada escalón, registra en el host del broker:
ss -s # resumen de sockets
ss -tn state established '( sport = :1883 )' | wc -l
ps -o rss=,pcpu= -p "$(pgrep -d, mosquitto)" # memoria (KB) y CPU del proceso del brokerMosquitto también publica $SYS/broker/clients/connected; suscríbete con el cliente online o con mosquitto_sub -t '$SYS/broker/clients/connected' -v (ver topics $SYS de MQTT). Recuerda que el host del broker necesita el mismo margen de descriptores de archivo: para Mosquitto con systemd, define LimitNOFILE= en un drop-in del servicio y revisa max_connections en mosquitto.conf (el generador de configuración de Mosquitto puede crear una base). Los errores que solo aparecen en los logs del broker se tratan en demasiadas conexiones.
La memoria por conexión es fácil de calcular: (RSS con N conexiones − RSS en reposo) ÷ N. Mídela con y sin TLS y con tu número real de suscripciones, porque ambos cambian el resultado.
Simula una tormenta de reconexiones
Mantener 50.000 clientes inactivos es la parte fácil. El momento peligroso es cuando todos se reconectan a la vez: tras un reinicio del broker, un failover del balanceador o un corte breve de red regional. Es una prueba simultánea de la tasa de conexión, la capacidad de handshakes TLS y la del backend de autenticación.
Dos formas de provocarla en tu propio broker:
- Reinicia el broker mientras el script mantiene las conexiones y observa cómo vuelve a subir
connected=. - Corta la red entre el generador de carga y el broker durante más tiempo que el keep-alive (por ejemplo con una regla de iptables o con Toxiproxy) y luego restáurala.
Registra cuánto tarda en volver al número total de conexiones y cuántos intentos fallaron por el camino. Con un intervalo de reconexión fijo, todos los clientes reintentan en el mismo instante y el broker recibe oleadas sincronizadas. El reconnectPeriod con jitter del script anterior las reparte; compara ambas variantes y verás por qué las tormentas de reconexión son tanto un problema de diseño del cliente como de capacidad del broker.
Checklist de la prueba de estrés de conexiones
- Sube
ulimit -n, amplíaip_local_port_rangey revisa conntrack en cada máquina de carga. - Planifica las IP de origen: unas 64.000 conexiones por IP de origen y endpoint del broker.
- Replica producción: TLS, keep-alive, autenticación y suscripciones por cliente.
- Sube por escalones y mantén cada uno durante varios intervalos de keep-alive.
- Registra conexiones, memoria, CPU y latencia de conexión del broker en cada escalón.
- Provoca una tormenta de reconexiones y mide el tiempo de recuperación.
- Repite tras actualizaciones o cambios de configuración del broker.
Una prueba de estrés de conexiones es sobre todo cuestión de honestidad: elimina los límites de tu lado, imita el comportamiento real de los clientes y empuja hasta que sea el broker, y no el generador de carga, quien te muestre dónde está el techo. Después planifica tu flota cómodamente por debajo y asegúrate de que tus clientes se reconecten con jitter para que ese techo nunca se ponga a prueba por accidente.
Preguntas frecuentes
¿Por qué mi prueba de conexiones MQTT se detiene en unos 28.000 clientes?
Normalmente es el generador de carga quedándose sin puertos efímeros: Linux usa por defecto unos 28.000 puertos locales para conexiones desde una IP de origen hacia una misma IP y puerto del broker. Amplía net.ipv4.ip_local_port_range o añade más IP de origen.
¿Cuántas conexiones puede manejar un broker MQTT?
Depende del broker, la memoria, la CPU, TLS, el keep-alive y el tráfico de mensajes, así que no hay una cifra universal. Mídelo: aumenta las conexiones mientras registras memoria y CPU del broker, y busca el punto donde suben la latencia de conexión o los errores.
¿Puedo hacer una prueba de estrés contra un broker MQTT público?
No. Abrir miles de conexiones contra un broker público compartido lo degrada para todos y hará que te limiten o bloqueen. Haz pruebas de estrés de conexiones solo contra brokers propios o instancias destinadas a ello.

