Tests de brokers

Lire les résultats d'un benchmark MQTT sans se tromper

Interprétez correctement un benchmark MQTT : pourquoi les moyennes mentent, comment la coordinated omission masque la latence et comment présenter les chiffres.

Mis à jour · 8 min de lecture

Lire les résultats d'un benchmark MQTT sans se tromper

Un benchmark MQTT produit des chiffres très vite. Produire des chiffres fiables est plus difficile. La plupart des résultats trompeurs viennent des mêmes quelques erreurs : citer des moyennes, mesurer le générateur de charge au lieu du broker, ignorer la coordinated omission et comparer des exécutions qui n'ont pas été configurées de la même façon.

Ce guide passe en revue chaque piège, montre comment le repérer dans vos propres données et se termine par un modèle de rapport qui rend les résultats reproductibles. Il suppose que vous avez déjà lancé un test — sinon, commencez par les tests de charge d'un broker MQTT.

Les moyennes cachent les messages lents

La latence ne suit jamais une loi normale. Un broker sous charge délivre la plupart des messages en quelques millisecondes et une petite fraction en centaines de millisecondes — pendant une pause du garbage collector, une écriture disque ou une retransmission. La moyenne mélange les deux groupes en un chiffre qui ne décrit ni l'un ni l'autre.

ExécutionMoyennep50p99Max
A6 ms4 ms12 ms30 ms
B6 ms3 ms140 ms900 ms

(Valeurs illustratives.) Les deux exécutions ont la même moyenne, mais dans la B un message sur cent arrive avec plus d'un dixième de seconde de retard. Pour un appareil qui publie une fois par seconde, cela fait un retard visible environ toutes les deux minutes.

Des règles qui vous gardent honnête :

  • Indiquez des percentiles : p50 (le cas typique), p99 (la queue), max (le pire cas observé). Ajoutez le p99.9 seulement si vous avez assez d'échantillons.
  • Ne faites jamais la moyenne de percentiles entre exécutions ou clients. La moyenne de dix valeurs p99 n'est pas le p99 des données combinées — fusionnez les échantillons bruts ou les histogrammes.
  • Vérifiez la taille de l'échantillon. Le p99 de 100 messages est un seul message. Collectez-en des milliers par exécution.

La coordinated omission

La coordinated omission est la raison la plus fréquente pour laquelle un benchmark paraît meilleur que la réalité. Elle survient quand le générateur de charge attend une réponse avant d'envoyer la requête suivante. Si le broker se fige 500 ms, un publisher synchrone enregistre un message lent — puis n'envoie tout simplement pas les 49 autres qu'une vraie flotte aurait envoyés pendant cette demi-seconde. Ces messages, qui auraient tous subi une latence élevée, n'apparaissent jamais dans les données.

Les vrais appareils ne se coordonnent pas avec le broker. Un capteur publie selon son propre calendrier, que le dernier PUBACK soit arrivé ou non. Pour reproduire cela, mesurez la latence à partir de l'heure d'envoi prévue, et non du moment où le message a réellement été remis au socket :

intended_time.py — latency without coordinated omission
import json, statistics, time, uuid
import paho.mqtt.client as mqtt

HOST, PORT = "broker.connectmq.com", 1883
TOPIC = f"testmqtt/bench-{uuid.uuid4().hex[:8]}/latency"
N, RATE = 20000, 500                   # messages, messages par seconde
samples = []

def on_message(client, userdata, msg):
    intended = json.loads(msg.payload)["t"]
    samples.append((time.perf_counter() - intended) * 1000)

sub = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
sub.on_message = on_message
sub.connect(HOST, PORT)
sub.subscribe(TOPIC, qos=1)
sub.loop_start()

pub = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
pub.max_queued_messages_set(0)         # 0 = file d'attente illimitée côté client
pub.connect(HOST, PORT)
pub.loop_start()
time.sleep(1)

start = time.perf_counter()
for i in range(N):
    intended = start + i / RATE        # le calendrier que suivrait un vrai appareil
    delay = intended - time.perf_counter()
    if delay > 0:
        time.sleep(delay)
    pub.publish(TOPIC, json.dumps({"i": i, "t": intended}), qos=1)

time.sleep(5)
q = statistics.quantiles(samples, n=1000)   # 999 points de coupure
print(f"received {len(samples)}/{N}")
print(f"p50 {q[499]:.1f} ms  p99 {q[989]:.1f} ms  p99.9 {q[998]:.1f} ms  max {max(samples):.1f} ms")

Si le publisher prend du retard sur son calendrier — parce que le broker ou le réseau a freiné —, chaque message en retard intègre ce délai dans sa mesure. Comparez la sortie avec une version qui horodate time.perf_counter() au moment du publish : si le p99 basé sur l'heure prévue est nettement plus élevé, le broker se bloquait et la mesure naïve le masquait.

Mesurez-vous le broker ou le client ?

Une machine de benchmark qui manque de CPU, de sockets ou de bande passante produit un plateau qui ressemble exactement à une limite du broker. Avant d'accuser le broker, vérifiez le générateur de charge :

  • CPU du générateur. Si un cœur reste à 100 % (fréquent avec des clients mono-thread, comme une seule boucle réseau paho qui traite des centaines de messages par seconde), le plafond vient de chez vous. Répartissez les clients sur plusieurs processus ou machines.
  • Plateaux sur des nombres ronds. Des connexions qui s'arrêtent à 1 024, 4 096 ou autour de 28 000–64 000 indiquent généralement une limite de descripteurs de fichiers ou de ports éphémères sur l'hôte client. Augmentez ulimit -n ou ajoutez des IP source.
  • Interface réseau. Multipliez le débit de messages par la taille sur le fil (le calculateur de taille de paquet donne les octets exacts par PUBLISH) et comparez avec la capacité du lien — surcoût TLS compris.
  • Le test à deux machines. Lancez la même charge depuis deux générateurs, chacun à la moitié du débit. Si le débit total augmente, le générateur unique était le goulot d'étranglement.

Le côté broker raconte la suite : relevez son CPU, sa mémoire, son nombre de connexions et ses débits de messages (par exemple via les topics $SYS ou Prometheus) à côté des chiffres côté client. Un broker à 30 % de CPU avec une courbe de débit bloquée attend autre chose.

Horloges, préchauffage et autres distorsions discrètes

  • Décalage d'horloge. Si le publisher et le subscriber tournent sur des machines différentes, la latence de bout en bout inclut l'écart entre leurs horloges. NTP maintient les horloges à quelques millisecondes au mieux — souvent moins bien. Mesurez avec publisher et subscriber dans le même processus, ou utilisez des allers-retours. Plus de détails dans problèmes d'horloge et d'horodatage MQTT.
  • Préchauffage. Les premières secondes incluent les handshakes TCP et TLS, la compilation JIT et les défauts de cache. Écartez une période de préchauffage ou rapportez-la séparément.
  • Exécutions trop courtes. Une exécution de 30 secondes peut manquer des événements périodiques comme les snapshots de persistance ou la rotation des logs. Maintenez chaque palier de charge plusieurs minutes ; utilisez un soak test pour plusieurs heures.
  • Messages perdus. Des percentiles calculés uniquement sur les messages arrivés semblent excellents si les lents ont été abandonnés. Indiquez toujours reçus / envoyés à côté de la latence.
  • QoS différente. Les QoS 0, 1 et 2 n'ont ni le même nombre de paquets ni le même coût ; ne comparez jamais des résultats entre niveaux de QoS comme s'il s'agissait du même test. Voir le benchmark QoS 0 vs 1 vs 2.

Repérer un mauvais résultat

Symptôme dans les donnéesCause probableComment confirmer
Débit plat alors que le CPU du broker est basGénérateur de charge saturéVérifier le CPU du générateur ; refaire avec deux générateurs
Reçus < envoyés en QoS 0Client ou broker qui abandonne sous pressionRefaire en QoS 1 et comparer les comptes
p99 correct, max énormeUn blocage isolé (GC, failover, disque)Tracer la latence dans le temps, pas seulement un histogramme
Le p50 monte régulièrement pendant l'exécutionFile qui s'accumule (consommateur lent)Surveiller les métriques de file / inflight du broker
Résultats qui varient de 30 % entre exécutions identiquesEnvironnement partagé ou bruyantLancer trois fois ; tester sur un broker isolé
Latence négative ou impossiblement basseHorloges de machines différentesMesurer dans un seul processus

Comment présenter les résultats d'un benchmark MQTT

Un chiffre sans sa configuration ne peut être ni reproduit ni comparé. Indiquez au minimum :

ChampExemple
Broker, version, déploiementBroker X 2.x, 4 vCPU / 8 Go, un seul nœud
Outil client et versionemqtt-bench / script maison paho-mqtt 2.x
Générateur de charge2 machines, même région, pic CPU 55 %
Réseau et transportMême région, TCP 1883 (ou TLS 8883)
Protocole et QoSMQTT 5, QoS 1, clean start
Charge1 000 publishers, 1 msg/s chacun, JSON de 512 o, topics 1:1
Durée5 min de préchauffage exclues, 20 min mesurées
RésultatsDébit envoyé/reçu, p50, p99, p99.9, max, erreurs
Données brutesLien vers l'histogramme ou le CSV

Deux habitudes rendent les rapports bien plus utiles :

  1. Tracez la latence dans le temps. Un histogramme cache quand les messages lents se sont produits. Une série temporelle montre si la queue est une propriété constante ou quelques événements.
  2. Montrez le point de saturation. Au lieu d'un chiffre unique, lancez des paliers de charge croissants et indiquez le débit auquel le p99 dépasse votre objectif (par exemple 50 ms). Ce débit est votre capacité utilisable ; le débit maximal absolu ne l'est pas.

Une vérification rapide avant chaque exécution

Avant de lancer un long benchmark, connectez-vous avec le client MQTT en ligne, abonnez-vous à votre topic de test et publiez quelques messages à la main. Si un message met visiblement du temps ou si l'abonnement ne reçoit rien, corrigez cela d'abord — un test de charge ne vous en dira pas plus.

Tester votre topic de benchmark

Un benchmark ne vaut que par les questions qu'il pose. Mesurez à partir de l'heure d'envoi prévue, surveillez le générateur de charge, présentez les percentiles avec les comptes de livraison et la configuration complète : vos chiffres MQTT résisteront à la prochaine personne qui tentera de les reproduire — vous y compris, dans quelques mois.

Questions fréquentes

Pourquoi la latence moyenne d'un benchmark MQTT est-elle trompeuse ?

Les distributions de latence sont asymétriques : la plupart des messages sont rapides et quelques-uns très lents. La moyenne mélange les deux et masque la queue lente que les appareils et les utilisateurs remarquent. Indiquez plutôt p50, p99 et le maximum.

Qu'est-ce que la coordinated omission dans un benchmark MQTT ?

Elle se produit quand le générateur de charge attend une réponse lente avant d'envoyer le message suivant. Le blocage n'est mesuré qu'une fois au lieu de l'être pour chaque message qui aurait dû partir pendant ce temps : l'outil sous-estime la latence précisément quand le broker est en difficulté.

Combien de messages faut-il pour un p99 fiable ?

Au moins plusieurs milliers d'échantillons par exécution. Avec 100 messages, le p99 correspond à un seul échantillon. Visez 10 000 ou plus pour le p99 et 100 000 ou plus si vous voulez citer le p99.9.