Tests de brokers

Tester MQTT sur des réseaux cellulaires lents et instables

Émulez latence, jitter et perte de paquets 2G, 3G et LTE avec tc netem, puis ajustez keep-alive, taille de payload, QoS et sessions MQTT pour l'IoT mobile.

Mis à jour · 9 min de lecture

Tester MQTT sur des réseaux cellulaires lents et instables

Un client MQTT parfaitement stable sur le Wi-Fi du bureau peut s'effondrer sur un modem cellulaire : connexions qui expirent, déconnexions keep-alive toutes les quelques minutes, messages QoS 1 qui arrivent par rafales. La solution commence par reproduire les conditions cellulaires sur votre poste — latence, jitter, perte de paquets et débit limité — puis par ajuster keep-alive, taille de payload, QoS et sessions jusqu'à ce que le client y résiste.

Ce guide montre comment émuler des liens 2G, 3G et LTE avec tc netem, quels réglages MQTT tester avec chaque profil, et fournit un script qui mesure la livraison et la latence pour comparer les configurations.

Ce qui rend le cellulaire différent

Un lien cellulaire n'est pas seulement un « Wi-Fi lent ». Il échoue de façons précises qui frappent la connexion TCP longue durée de MQTT :

  • RTT élevé et variable. Chaque échange MQTT qui attend une réponse — CONNACK, SUBACK, PUBACK, PINGRESP — paie le RTT complet, et le jitter le rend imprévisible.
  • Pertes en rafales. Les handovers et un signal faible font perdre plusieurs paquets d'affilée. TCP les retransmet : MQTT voit des blocages de plusieurs secondes plutôt que des messages perdus.
  • Délai de réveil de la radio. Après une période calme, le modem passe dans un état radio inactif ; le premier paquet suivant attend que la radio se reconnecte, ce qui ajoute une latence notable à ce message.
  • NAT d'opérateur (CGNAT). Les opérateurs mobiles placent les appareils derrière des passerelles NAT qui oublient les flux inactifs. Si rien ne traverse la connexion plus longtemps que le timeout d'inactivité du NAT, elle devient semi-ouverte : l'appareil se croit connecté, le broker n'entend plus jamais parler de lui. Voir MQTT et les timeouts NAT.
  • Données facturées. Chaque octet d'en-têtes, de keep-alive et de handshake TLS coûte de l'argent et de la batterie sur une flotte de milliers d'appareils.

Émuler un lien cellulaire avec tc netem

Sous Linux, tc avec la discipline de file netem ajoute délai, jitter, perte et limitation de débit à une interface. Les profils ci-dessous sont des points de départ approximatifs pour des conditions typiques, pas des mesures d'un réseau particulier : remplacez-les par les chiffres de vos propres appareils dès que vous les avez.

ProfilDélai (un sens)JitterPerteDébit
Bon LTE40 ms10 ms0,1 %10 mbit
LTE faible120 ms40 ms1 %1 mbit
3G200 ms50 ms1 %384 kbit
2G / EDGE400 ms150 ms3 %64 kbit
Zone blanche1500 ms500 ms15 %16 kbit

Le montage le plus propre consiste à exécuter le client MQTT dans un conteneur et à ne façonner que le réseau de ce conteneur, sans toucher à l'hôte :

A shaped test container
docker run --rm -it --cap-add NET_ADMIN --name cell python:3.12-slim bash

# inside the container
apt-get update && apt-get install -y iproute2 mosquitto-clients
pip install paho-mqtt
Apply and change profiles (inside the container)
# 3G-like: 200 ms ± 50 ms, 1 % loss with 25 % correlation (bursty), 384 kbit/s
tc qdisc add dev eth0 root netem delay 200ms 50ms loss 1% 25% rate 384kbit

tc qdisc change dev eth0 root netem delay 400ms 150ms loss 3% 25% rate 64kbit   # 2G
tc qdisc show dev eth0                                                         # verify
tc qdisc del dev eth0 root                                                     # back to normal

netem ne façonne que le trafic sortant. Côté client, cela suffit pour la plupart des tests : le délai s'applique une fois par aller-retour, et la perte de segments sortants déclenche le même comportement de retransmission TCP. Pour façonner le lien descendant séparément, placez netem côté broker d'un bridge ou utilisez Toxiproxy, qui retarde et bride chaque sens indépendamment.

Vérifiez le profil avant de tester MQTT : ping -c 10 broker.connectmq.com doit montrer le délai ajouté, et un mosquitto_pub doit toujours passer.

Mesurer la livraison avec chaque profil

Ce script (paho-mqtt 2.x) connecte un subscriber et un publisher, envoie un lot de messages QoS 1 et indique combien sont arrivés, le temps de connexion et les percentiles de latence de bout en bout. Exécutez-le dans le conteneur façonné, une fois par profil.

cell_test.py
import json, sys, threading, time, uuid
import paho.mqtt.client as mqtt

HOST, PORT = "broker.connectmq.com", 1883
TOPIC = f"testmqtt/cellular/{uuid.uuid4().hex[:8]}"
N, QOS, SIZE = int(sys.argv[1]), int(sys.argv[2]), int(sys.argv[3])
latencies, done = [], threading.Event()

def on_message(client, userdata, msg):
    latencies.append((time.time() - json.loads(msg.payload)["t"]) * 1000)
    if len(latencies) == N:
        done.set()

def connect(name, keepalive=60):
    ready = threading.Event()
    c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"{name}-{uuid.uuid4().hex[:6]}")
    c.on_connect = lambda *args: ready.set()
    t0 = time.time()
    c.connect(HOST, PORT, keepalive=keepalive)
    c.loop_start()
    if not ready.wait(30):
        sys.exit(f"{name}: no CONNACK within 30 s")
    print(f"{name}: connected in {(time.time() - t0) * 1000:.0f} ms")
    return c

sub = connect("sub")
sub.on_message = on_message
sub.subscribe(TOPIC, qos=QOS)
pub = connect("pub")
time.sleep(2)

pad = "x" * SIZE
for i in range(N):
    pub.publish(TOPIC, json.dumps({"i": i, "t": time.time(), "pad": pad}), qos=QOS)
    time.sleep(0.2)

done.wait(60)
latencies.sort()
pct = lambda q: latencies[min(len(latencies) - 1, int(len(latencies) * q))]
print(f"received {len(latencies)}/{N} at QoS {QOS}, {SIZE}-byte padding")
if latencies:
    print(f"p50 {pct(0.5):.0f} ms  p95 {pct(0.95):.0f} ms  max {latencies[-1]:.0f} ms")
Run it
python cell_test.py 100 1 200    # 100 messages, QoS 1, ~200-byte payloads
python cell_test.py 100 0 200    # same at QoS 0 for comparison
python cell_test.py 20 1 20000   # few large payloads

Publisher et subscriber partagent l'horloge du conteneur : aucune erreur de dérive d'horloge dans les latences. Les deux connexions passent par l'interface façonnée, ce qui est pessimiste — en production, le subscriber se trouve généralement dans un data center — mais rend les écarts entre configurations bien visibles. Gardez le client MQTT en ligne abonné à testmqtt/cellular/# sur une connexion normale pour voir les messages arriver au compte-gouttes.

Régler le keep-alive pour l'opérateur, pas pour le labo

En cellulaire, le keep-alive a deux rôles : maintenir l'entrée NAT en vie et détecter les liens morts. L'intervalle de keep-alive doit être plus court que le timeout d'inactivité du NAT de l'opérateur, sinon la connexion meurt en silence entre deux pings.

Pour trouver ce timeout, connectez-vous, restez totalement inactif (keep-alive supérieur à la fenêtre de test), puis publiez après des pauses croissantes : 1, 2, 5, 10, 20 minutes. La première pause après laquelle le publish n'atteint jamais le broker (et où le client ne s'en aperçoit qu'au ping ou à l'écriture suivante) est votre limite NAT. Réglez le keep-alive nettement en dessous.

Testez aussi l'échec inverse : avec les profils 2G et « zone blanche », assurez-vous que le keep-alive ne provoque pas de déconnexions. Si le PINGRESP met souvent plusieurs secondes, un client aux timeouts agressifs peut couper des connexions saines. MQTT autorise le broker à attendre une fois et demie le keep-alive avant d'abandonner : des valeurs très courtes (moins de 15–20 s) laissent peu de marge sur les liens lents.

Taille du payload et overhead du protocole

Sur un lien à 64 kbit/s, un message de 20 Ko occupe l'uplink pendant quelques secondes, et tout ce qui suit attend. Avec le profil 2G, comparez petits et gros payloads avec le script : la latence p95 grimpe avec la taille.

  • Gardez des payloads compacts. Des clés JSON courtes ou des formats binaires comme CBOR et Protobuf aident. Le calculateur de taille de paquet montre ce que la longueur du topic et les propriétés ajoutent à chaque message.
  • Regroupez avec discernement. Un message de dix mesures coûte moins d'overhead que dix messages, mais une connexion perdue au milieu du lot fait plus de dégâts. Testez les deux.
  • Comptez le TLS. Un handshake TLS complet coûte plusieurs allers-retours et quelques kilo-octets. Sur un lien à 400 ms, cela représente des secondes par connexion : les reconnexions fréquentes coûtent cher. Mesurez le temps de connexion en TLS sur le port 8883 avec chaque profil, et consultez l'overhead de bande passante MQTT pour les chiffres sous-jacents.
  • Utilisez les topic aliases MQTT 5 si de longs topics sont envoyés de façon répétée, après avoir vérifié que votre broker les prend en charge.

QoS et sessions sur des liens avec pertes

RéglageComportement en cellulaireTest à mener
QoS 0Le moins d'octets ; tout ce qui est en vol à la coupure est perduCouper le lien en plein lot, compter les pertes
QoS 1Un PUBACK de plus par message ; renvoi après reconnexion, doublons possiblesMême coupure, rechercher les doublons
QoS 2Quatre paquets par message, chacun payant le RTTComparer le débit avec QoS 1 en 2G
Session persistanteAbonnements et messages en file survivent aux reconnexionsDéconnecter 2 minutes, reconnecter, compter la file

Pour la plupart des télémétries cellulaires, QoS 1 avec un traitement idempotent côté récepteur est le choix pragmatique. Associez-le à une session persistante — clean_start=False et un session expiry interval en MQTT 5 — pour que l'appareil n'ait pas à se réabonner et que les messages mis en file pour lui survivent à une courte coupure. Détails dans les sessions persistantes MQTT.

Testez explicitement les reconnexions avec les mauvais profils : coupez le lien (tc qdisc change dev eth0 root netem loss 100%), attendez, rétablissez-le, et vérifiez que le client se reconnecte avec backoff au lieu de marteler le réseau, et que les messages QoS 1 publiés pendant la coupure arrivent une fois le lien revenu.

Checklist des tests cellulaires

  1. Faites d'abord un smoke test sur un réseau normal, puis appliquez chaque profil netem.
  2. Notez par profil le temps de connexion (avec et sans TLS), le nombre de messages livrés et la latence p50/p95.
  3. Trouvez le timeout d'inactivité NAT de l'opérateur avec des tests inactivité puis publish ; réglez le keep-alive en dessous.
  4. Coupez le lien 30 s puis 5 min ; vérifiez le backoff de reconnexion, la reprise de session et la relivraison QoS 1.
  5. Comparez tailles de payload et regroupement avec le profil 2G.
  6. Rejouez les cas critiques sur du vrai matériel avec la SIM de production.

Les réseaux cellulaires resteront toujours plus lents et moins prévisibles que le labo. Les émuler à chaque changement transforme « ça marche au bureau » en « ça marche dans un camion qui roule », et les chiffres de ces exécutions vous donnent une base solide pour vos choix de keep-alive, de QoS et de payload.

Questions fréquentes

Quel keep-alive choisir pour MQTT sur réseau cellulaire ?

Assez court pour rester sous le timeout d'inactivité du NAT de l'opérateur, assez long pour ne pas gaspiller batterie et données. Beaucoup de déploiements se situent entre 60 et 300 secondes, mais les timeouts NAT varient selon l'opérateur : mesurez avec votre SIM la durée d'inactivité après laquelle la connexion meurt silencieusement, et réglez le keep-alive nettement en dessous.

La perte de paquets fait-elle perdre des messages MQTT ?

Pas directement. MQTT fonctionne sur TCP, qui retransmet les segments perdus : la perte se traduit par des pics de latence et des blocages. Les messages sont perdus quand la connexion tombe : les messages QoS 0 en vol disparaissent, tandis que QoS 1 et 2 sont renvoyés après reconnexion si la session persiste.

Peut-on émuler des conditions cellulaires sous macOS ou Windows ?

Sous macOS, le Network Link Conditioner d'Apple applique des profils de latence, de perte et de débit à tout le système. Sur n'importe quel OS, vous pouvez exécuter le client dans un conteneur Linux avec tc netem, ou placer Toxiproxy entre client et broker pour la latence et les limites de débit.