Dans un scénario de fan-out, un message part vers de nombreux destinataires : un changement de configuration poussé à toute une flotte, une mise à jour de prix affichée sur 10 000 tableaux de bord, une annonce « firmware disponible ». Le publisher envoie un PUBLISH ; le broker en envoie un par abonnement correspondant. Un test de fan-out mesure le temps nécessaire pour que le dernier subscriber reçoive le message, et si tous l'ont bien reçu.
Ce guide met en place un benchmark 1 → 10 000, ajoute une sonde qui mesure la dispersion de livraison et fait le calcul de bande passante pour que la facture réseau ne vous surprenne pas. Pour les tests de débit généraux, voir le test de débit MQTT ; pour le schéma inverse, le test de fan-in.
Pourquoi le fan-out est un test à part
Les tests de débit en publication associent généralement chaque publisher à un subscriber. Le fan-out sollicite autre chose : la capacité du broker à copier un message dans des milliers de files sortantes et à les vider vers des milliers de sockets.
| Facteur | Effet sur le fan-out |
|---|---|
| Nombre de subscribers | Messages sortants par seconde = débit de publication × subscribers |
| QoS | En QoS 1, le broker suit un PUBACK par subscriber et par message |
| Taille du payload | Multipliée par le nombre de subscribers sur le réseau |
| TLS | Chaque copie est chiffrée séparément pour chaque connexion |
| Subscribers lents | Leurs files grossissent ; les bons brokers les isolent des autres |
| Organisation des abonnements | Les abonnements riches en wildcards coûtent plus cher à évaluer que les topics exacts |
La question n'est pas « combien de messages par seconde », mais « combien de temps avant que chaque subscriber l'ait » — la dispersion de livraison — à la taille réelle de votre flotte.
D'abord le calcul de bande passante
Faites le calcul avant de commencer : un test de fan-out peut saturer un lien réseau bien avant que le broker ne peine. Un PUBLISH sur le fil représente à peu près en-tête fixe + 2 octets de longueur de topic + topic + identifiant de paquet (QoS > 0) + propriétés + payload. Voir le surcoût de bande passante de MQTT pour le détail.
Exemple : payload de 500 octets sur testmqtt/fanout/broadcast (25 octets) en QoS 0 ≈ 530 octets par PUBLISH.
| Débit de publication | Subscribers | Messages sortants/s | Trafic sortant |
|---|---|---|---|
| 1 msg/s | 1 000 | 1 000 | ≈ 0,5 Mo/s (≈ 4 Mbit/s) |
| 1 msg/s | 10 000 | 10 000 | ≈ 5,3 Mo/s (≈ 42 Mbit/s) |
| 10 msg/s | 10 000 | 100 000 | ≈ 53 Mo/s (≈ 420 Mbit/s) |
Ajoutez-y le surcoût TCP/IP et TLS. La dernière ligne dépasse ce que beaucoup de machines de génération de charge peuvent recevoir seules, c'est pourquoi les tests de fan-out sont généralement répartis sur plusieurs machines. Le calculateur de taille de paquet vous donne la taille exacte par paquet pour votre topic et votre payload.
Étape 1 : ouvrir les subscribers
emqtt-bench peut maintenir des milliers de connexions de subscribers. Répartissez les 10 000 sur quatre machines (ou conteneurs sur des hôtes différents) pour rester loin des limites de descripteurs de fichiers et de ports éphémères :
ulimit -n 65535
docker run --rm -it --ulimit nofile=65535:65535 emqx/emqtt-bench sub \
-h broker.connectmq.com -p 1883 \
-c 2500 -i 5 \
-t testmqtt/fanout/broadcast -q 0 \
-u "$MQTT_USER" -P "$MQTT_PASS"-i 5 ouvre une connexion toutes les 5 ms (200 par seconde et par machine) : les quatre machines ont donc besoin d'un peu plus de 12 secondes pour connecter tout le monde. Ouvrir les connexions plus vite est un test en soi — voir le test de stress des connexions — à ne pas mélanger avec les mesures de fan-out. Attendez que les quatre machines annoncent le total avant de publier.
Étape 2 : publier et sonder
La sortie subscriber d'emqtt-bench indique le débit de réception agrégé, mais pas le retard du subscriber le plus lent. Ce script paho-mqtt 2.x ajoute 50 subscribers sondes dans un seul processus, publie des messages horodatés sur le même topic de broadcast et rapporte la latence et la dispersion de livraison entre les sondes. Comme le publisher et les sondes partagent la même horloge, les chiffres sont directement comparables :
import json, threading, time, uuid
from collections import defaultdict
import paho.mqtt.client as mqtt
HOST, PORT = "broker.connectmq.com", 1883
TOPIC = "testmqtt/fanout/broadcast"
PROBES, MESSAGES, RATE, QOS = 50, 60, 1, 0
lat = defaultdict(list) # seq -> latences (ms) sur l'ensemble des sondes
lock = threading.Lock()
def on_message(client, userdata, msg):
try:
m = json.loads(msg.payload)
except ValueError:
return # ignorer le trafic hors sonde
with lock:
lat[m["seq"]].append((time.perf_counter() - m["t"]) * 1000)
def new_client(name):
return mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id=f"{name}-{uuid.uuid4().hex[:6]}")
probes = []
for n in range(PROBES):
c = new_client(f"fanout-probe-{n}")
c.on_message = on_message
c.connect(HOST, PORT)
c.subscribe(TOPIC, qos=QOS)
c.loop_start()
probes.append(c)
pub = new_client("fanout-pub")
pub.connect(HOST, PORT)
pub.loop_start()
time.sleep(3) # laisser les abonnements s'établir
for seq in range(MESSAGES):
body = {"seq": seq, "t": time.perf_counter(), "pad": "x" * 450}
pub.publish(TOPIC, json.dumps(body), qos=QOS)
time.sleep(1 / RATE)
time.sleep(5)
pct = lambda xs, q: xs[min(len(xs) - 1, int(len(xs) * q))]
all_lat = sorted(v for vals in lat.values() for v in vals)
spread = sorted(max(v) - min(v) for v in lat.values() if len(v) > 1)
print(f"delivered {len(all_lat)}/{PROBES * MESSAGES}")
if all_lat:
print(f"latency p50 {pct(all_lat, .5):.1f} p99 {pct(all_lat, .99):.1f} max {all_lat[-1]:.1f} ms")
if spread:
print(f"spread p50 {pct(spread, .5):.1f} max {spread[-1]:.1f} ms")Exécutez-le trois fois : sans subscribers du bench (référence), avec 2 500, puis avec les 10 000. La référence donne la part du réseau ; l'augmentation au-delà est le coût de fan-out du broker. Recommencez ensuite avec QOS = 1 et un RATE plus élevé pour trouver où la latence commence à grimper.
Lire les résultats
- Livrés < attendus sur les sondes en QoS 0 signifie que le broker a supprimé des messages pour certaines connexions — en général parce que leurs files sortantes étaient pleines. Réessayez en QoS 1 et consultez les logs du broker.
- La latence p99 augmente avec le nombre de subscribers alors que p50 reste stable : c'est normal jusqu'à un certain point, le broker écrit dans les sockets l'un après l'autre. L'important est que p99 reste dans votre exigence.
- La dispersion s'envole à des débits de publication plus élevés : le broker (ou son réseau) n'arrive pas à vider toutes les copies avant l'arrivée du message suivant. Vous avez dépassé le débit de fan-out soutenable.
- Les machines du bench annoncent des débits de réception plus faibles que prévu — vérifiez d'abord leur CPU et leur réseau. Recevoir 100 000 msg/s représente un vrai travail pour un générateur de charge.
- Quelques subscribers décrochent et sont déconnectés — voir consommateurs lents. En production, un appareil lent ne doit pas retarder tous les autres.
Concevoir les topics pour le fan-out
Le test se termine souvent par une discussion de conception. Quelques schémas tiennent bien la route :
- Du broadcast seulement là où tout le monde en a besoin.
fleet/all/configpour les messages que chaque appareil doit recevoir ; gardez-les petits. - Des topics de groupe plutôt qu'un broadcast géant.
fleet/region/eu/configoufleet/model/x200/firmwareréduisent le fan-out par message et permettent des déploiements progressifs. - Retenez l'état courant. Pour la configuration, publiez avec le flag retain afin que les appareils qui se reconnectent plus tard reçoivent immédiatement la dernière valeur — voir messages retenus. Vérifiez qu'un subscriber tardif la reçoit.
- Choisissez le QoS selon la conséquence. Mises à jour remplaçables en QoS 0 ; commandes qui doivent arriver en QoS 1, dimensionnées pour que le suivi par subscriber du broker reste supportable.
- Évitez ici les shared subscriptions. Elles répartissent la charge ; elles ne diffusent pas.
Une courte checklist
- Calculez la bande passante sortante pour votre flotte cible et votre débit de publication.
- Répartissez les subscribers sur assez de machines de charge ; augmentez les limites de descripteurs de fichiers.
- Connectez d'abord tous les subscribers, puis publiez — gardez les tests de connexion à part.
- Mesurez latence et dispersion avec des sondes, pas seulement avec des débits de réception agrégés.
- Recommencez en QoS 0 et QoS 1, avec et sans TLS.
- Notez CPU, mémoire et réseau sortant du broker à côté des chiffres côté client.
Le fan-out transforme un petit message en une grosse rafale de travail et de trafic. Faites d'abord le calcul de bande passante, connectez les subscribers avant de publier et jugez le résultat sur le subscriber le plus lent — c'est lui que vos utilisateurs remarqueront.
Questions fréquentes
Qu'est-ce que le fan-out en MQTT ?
Le fan-out est le nombre de subscribers qui reçoivent chaque message publié. Un PUBLISH sur un topic avec 10 000 abonnements correspondants oblige le broker à envoyer 10 000 paquets PUBLISH : le trafic sortant et le travail du broker croissent avec les subscribers, pas avec les publishers.
Une shared subscription permet-elle de faire du fan-out ?
Non, c'est l'inverse. Une shared subscription livre chaque message à un seul membre du groupe, ce qui répartit la charge entre consommateurs. Le fan-out livre chaque message à chaque subscriber.
Les messages de broadcast doivent-ils utiliser QoS 0 ou QoS 1 ?
QoS 0 pour les mises à jour fréquentes et remplaçables, où le message suivant remplace le précédent. QoS 1 pour les commandes ou annonces que chaque appareil doit recevoir, en acceptant que le broker suive alors un acquittement par subscriber.


