Tests de brokers

Tester le Message Expiry Interval de MQTT 5

Comment vérifier le Message Expiry Interval de MQTT 5 : messages en file pour clients hors ligne, expiration des messages retenus et intervalle restant.

Mis à jour · 8 min de lecture

Tester le Message Expiry Interval de MQTT 5

Le Message Expiry Interval de MQTT 5 indique au broker combien de temps un message reste utile. S'il attend encore sa livraison quand ce délai est écoulé, le broker doit le supprimer au lieu de livrer des données périmées. Pour le tester, publiez des messages avec différentes expirations pendant qu'un subscriber persistant est hors ligne, attendez, reconnectez-vous et vérifiez quels messages arrivent et avec quel intervalle restant.

Ce guide déroule ce test avec mosquitto_pub / mosquitto_sub, un script Python auto-vérifiant et un test séparé pour les messages retenus.

Comment l'expiration fonctionne vraiment

Le publisher définit la propriété Message Expiry Interval (en secondes) sur un paquet PUBLISH. Ensuite, c'est le broker qui en est responsable :

SituationCe que doit faire le broker
Subscriber connectéLivrer immédiatement ; l'expiration compte rarement
Subscriber hors ligne avec session persistanteMettre le message en file, le supprimer une fois l'intervalle écoulé
Message retenuLe servir aux nouveaux subscribers jusqu'à expiration, puis le supprimer
Message transmis après attenteEnvoyer l'intervalle restant, pas la valeur d'origine
Pas de propriété d'expirationLe message n'expire jamais (des limites globales du broker peuvent s'appliquer)

Le point clé pour les tests : l'expiration n'est visible que lorsqu'un message attend. Un test avec un subscriber toujours connecté ne prouve rien. Il vous faut une session hors ligne ou un message retenu. Pour la théorie et les conseils de conception, voir l'expiration des messages MQTT.

Prérequis

  • Un broker compatible MQTT 5. Vérifiez le CONNACK d'une connexion MQTT 5, ou lancez d'abord les tests généraux des fonctions MQTT 5.
  • Un subscriber qui conserve sa session hors ligne : client ID fixe, Clean Start à false et Session Expiry Interval non nul. Si la session elle-même expire, sa file disparaît avec elle et votre test mesure autre chose. Les sessions persistantes expliquent le mécanisme.
  • QoS 1 pour l'abonnement comme pour la publication. Beaucoup de brokers ne mettent pas du tout en file les messages QoS 0 pour les clients hors ligne.

Les exemples utilisent broker.connectmq.com et des topics sous testmqtt/expiry/. Ajoutez un suffixe aléatoire pour que votre test n'entre pas en collision avec celui d'autres utilisateurs.

Test manuel rapide avec la CLI Mosquitto

Étape 1 : créez une session persistante et abonnez-vous, puis arrêtez le subscriber avec Ctrl+C.

Terminal 1 — créer la session, puis Ctrl+C
mosquitto_sub -h broker.connectmq.com -p 1883 -V mqttv5 \
  -i expiry-sub-7f3a -c -x 600 -q 1 \
  -t 'testmqtt/expiry/7f3a/#' -F '%t expiry=%E payload=%p'

-c désactive Clean Start, -x 600 conserve la session 10 minutes après la déconnexion et %E affiche l'intervalle d'expiration de chaque message reçu.

Étape 2 : pendant que le subscriber est hors ligne, publiez trois messages :

Terminal 2 — publier pendant que le subscriber est hors ligne
mosquitto_pub -h broker.connectmq.com -V mqttv5 -q 1 \
  -t testmqtt/expiry/7f3a/short -m 'expires in 10s' \
  -D publish message-expiry-interval 10

mosquitto_pub -h broker.connectmq.com -V mqttv5 -q 1 \
  -t testmqtt/expiry/7f3a/long -m 'expires in 300s' \
  -D publish message-expiry-interval 300

mosquitto_pub -h broker.connectmq.com -V mqttv5 -q 1 \
  -t testmqtt/expiry/7f3a/none -m 'never expires'

Étape 3 : attendez environ 30 secondes, puis relancez exactement la commande de l'étape 1. Un broker correct livre long (avec une expiration nettement inférieure à 300) et none, et ne livre jamais short.

Si short arrive, le broker ignore l'expiration des messages en file. Si rien n'arrive, votre session n'a pas été conservée : vérifiez d'abord la session, pas l'expiration.

Un test Python auto-vérifiant

Un test manuel suffit une fois. Pour les régressions, utilisez un script qui vérifie le résultat. Celui-ci utilise paho-mqtt 2.x et les propriétés MQTT 5 :

expiry_test.py
import time, uuid
import paho.mqtt.client as mqtt
from paho.mqtt.properties import Properties
from paho.mqtt.packettypes import PacketTypes

HOST, PORT = "broker.connectmq.com", 1883
RUN = uuid.uuid4().hex[:8]
TOPIC = f"testmqtt/expiry/{RUN}"
SUB_ID = f"expiry-sub-{RUN}"
WAIT = 15                                  # seconds the subscriber stays offline
received = {}
session = {}

def client(cid):
    return mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=cid, protocol=mqtt.MQTTv5)

def connect_props(session_expiry):
    p = Properties(PacketTypes.CONNECT)
    p.SessionExpiryInterval = session_expiry
    return p

def on_message(c, userdata, msg):
    left = getattr(msg.properties, "MessageExpiryInterval", None)
    received[msg.topic.rsplit("/", 1)[1]] = left

# 1. Create a persistent session with a subscription, then go offline
sub = client(SUB_ID)
sub.connect(HOST, PORT, clean_start=True, properties=connect_props(600))
sub.loop_start()
sub.subscribe(f"{TOPIC}/#", qos=1)
time.sleep(1)
sub.disconnect()
sub.loop_stop()

# 2. Publish with different expiry intervals while it is offline
pub = client(f"expiry-pub-{RUN}")
pub.connect(HOST, PORT)
pub.loop_start()
for name, expiry in [("short", 5), ("long", 120), ("none", None)]:
    props = Properties(PacketTypes.PUBLISH)
    if expiry:
        props.MessageExpiryInterval = expiry
    pub.publish(f"{TOPIC}/{name}", name, qos=1, properties=props).wait_for_publish()
pub.disconnect()
pub.loop_stop()

time.sleep(WAIT)

# 3. Resume the session and collect what is left
sub = client(SUB_ID)
sub.on_message = on_message
sub.on_connect = lambda c, u, flags, rc, p: session.update(present=flags.session_present)
sub.connect(HOST, PORT, clean_start=False, properties=connect_props(600))
sub.loop_start()
time.sleep(3)
end = Properties(PacketTypes.DISCONNECT)
end.SessionExpiryInterval = 0              # clean up the session
sub.disconnect(properties=end)
sub.loop_stop()

print("session present:", session.get("present"), "received:", received)
if not session.get("present"):
    raise SystemExit("ABORT: the broker did not keep the session, expiry cannot be judged")
checks = {
    "short expired": "short" not in received,
    "long delivered": "long" in received,
    "long shows remaining time": received.get("long") is not None and received["long"] <= 120 - WAIT,
    "no-expiry delivered": "none" in received,
}
for name, ok in checks.items():
    print(("PASS " if ok else "FAIL ") + name)

Une exécution réussie affiche session present: True et une valeur long légèrement inférieure à 105. Un écart d'une seconde est normal, car les brokers arrondissent à la seconde.

ABORT avec session present: False signifie que le broker a supprimé la session ou ne l'a jamais stockée : l'expiration ne peut pas être évaluée. Les endpoints de test publics partagés ne conservent souvent pas du tout les sessions hors ligne, lancez donc ce test sur votre propre instance de broker. Sinon, vérifiez d'abord les paramètres de session : tester l'expiration de session couvre ce cas.

Tester l'expiration des messages retenus

Les messages retenus sont le deuxième endroit où l'expiration compte, et celui qu'on oublie. Un statut retenu « appareil en ligne » avec 30 secondes d'expiration disparaît tout seul si l'appareil cesse de le rafraîchir.

Message retenu avec une durée de vie de 20 secondes
mosquitto_pub -h broker.connectmq.com -V mqttv5 -q 1 -r \
  -t testmqtt/expiry/7f3a/status -m online \
  -D publish message-expiry-interval 20

# Immediately: prints the message (expiry close to 20)
mosquitto_sub -h broker.connectmq.com -V mqttv5 \
  -t testmqtt/expiry/7f3a/status -F '%r %E %p' -W 3

sleep 25

# After expiry: prints nothing and exits after the 3 s timeout
mosquitto_sub -h broker.connectmq.com -V mqttv5 \
  -t testmqtt/expiry/7f3a/status -F '%r %E %p' -W 3

Dans la première sortie, %r affiche 1 pour un message retenu. Lors de la seconde exécution, rien ne doit s'afficher. Si l'ancienne valeur apparaît encore, le broker garde les messages retenus au-delà de leur expiration. Certains brokers suppriment les retenus expirés de façon paresseuse ou via un minuteur, prévoyez donc une marge. Le comportement des messages retenus en général est couvert dans tester les messages retenus.

Vous pouvez aussi l'observer en direct dans le client MQTT en ligne : abonnez-vous au topic de statut avant et après la fenêtre d'expiration et comparez.

Observer l'expiration des messages dans le navigateur

Résultats fréquents et leur signification

  • Le message à courte durée de vie est livré après son expiration. Le broker ignore la propriété, ou la session passe par un chemin de code MQTT 3.1.1. Vérifiez que publisher et subscriber se connectent en version 5 du protocole.
  • L'expiration transmise est égale à la valeur d'origine. Le broker ne déduit pas le temps d'attente. Cela casse la logique en aval qui retransmet le message, comme les bridges ou les passerelles.
  • Les messages reçus n'ont pas la propriété, alors que l'expiration était définie. Un bridge ou un proxy intermédiaire parle peut-être MQTT 3.1.1 et supprime les propriétés MQTT 5.
  • Rien n'est livré. Vérifiez session_present, les niveaux de QoS (QoS 0 n'est souvent pas mis en file) et les ACL. Un abonnement refusé par le broker ne met jamais rien en file. L'outil de recherche des reason codes aide à décoder les SUBACK.
  • Les messages expirent plus tard que prévu. Les brokers vérifient l'expiration à la livraison ou lors d'un balayage périodique. Une ou deux secondes d'écart, c'est normal ; des minutes, non.

Checklist des tests d'expiration

  1. Connectez publisher et subscriber en MQTT 5 et journalisez le CONNACK.
  2. Utilisez un client ID fixe, Clean Start à false et une expiration de session plus longue que le test.
  3. Publiez en QoS 1 avec au moins trois expirations : plus courte que l'attente, plus longue que l'attente, et aucune.
  4. Laissez le subscriber hors ligne plus longtemps que l'expiration courte. Reconnectez-le et vérifiez ce qui arrive.
  5. Contrôlez que l'intervalle transmis a diminué d'environ le temps d'attente.
  6. Recommencez avec un message retenu et un nouveau subscriber après expiration.
  7. Nettoyez : terminez la session avec Session Expiry 0 et effacez les topics retenus avec un payload vide.

L'expiration des messages est une petite propriété aux grands effets : elle décide si un appareil qui se réveille reçoit des commandes utiles ou une rafale de commandes périmées. Ajoutez le script ci-dessus à votre suite de tests et exécutez-le après chaque mise à jour ou migration du broker.

Questions fréquentes

Le Message Expiry Interval affecte-t-il les clients connectés ?

En général non. Un subscriber connecté reçoit le message presque immédiatement, bien avant son expiration. L'intervalle compte quand le message attend : dans la file de session d'un client hors ligne, comme message retenu ou dans le retard d'un consommateur lent.

Pourquoi mon subscriber reçoit-il une expiration plus courte que celle publiée ?

C'est le comportement attendu. MQTT 5 impose au broker de transmettre la durée de vie restante : un message publié avec 60 secondes qui a attendu 20 secondes dans une file arrive avec une expiration d'environ 40 secondes.

L'expiration des messages fonctionne-t-elle avec MQTT 3.1.1 ?

Non. Le Message Expiry Interval est une propriété MQTT 5. Un publisher 3.1.1 ne peut pas la définir, et le broker considère qu'un message sans cette propriété n'expire jamais. Le broker peut toutefois appliquer ses propres limites globales.