Article

Symfony Messenger : rejouer proprement un message échoué

L
2 septembre 2026 36 vues

Un message Symfony Messenger a épuisé ses tentatives et attend dans le failure transport ? Voici comment retrouver le bon identifiant, lire l’exception avec le niveau de détail utile, corriger la cause puis relancer uniquement ce message. La vérification finale évite de confondre une file vide avec un traitement réellement réussi.

Un message qui termine dans la file d’échec de Symfony Messenger n’est pas seulement une tâche à relancer. C’est d’abord la trace d’un traitement qui a épuisé ses tentatives, avec une exception, un contexte et parfois un effet métier déjà partiellement produit. Le rejouer sans lire cette histoire revient à remettre en circulation le même problème.

La bonne approche consiste à isoler le message concerné, comprendre ce qui l’a arrêté, corriger la cause, puis ne relancer que cet identifiant. On peut alors suivre son nouveau passage et vérifier qu’il ne revient pas aussitôt dans la file d’échec. Les exemples ci-dessous utilisent les commandes documentées par Symfony 8.x ; si votre projet repose sur une version plus ancienne, un passage par --help permet de confirmer les options disponibles.

Ce que la file d’échec conserve vraiment

Lorsqu’un handler lève une exception, Messenger applique la stratégie de nouvelle tentative du transport d’origine. Avec les valeurs par défaut actuelles, il effectue jusqu’à trois nouvelles tentatives, espacées selon le délai et le multiplicateur configurés. Une fois cette stratégie épuisée, le message n’est conservé que si un failure_transport a été déclaré. Sans ce transport, il finit par être abandonné.

Le transport d’échec est donc une file à part entière, souvent nommée failed, et non un simple journal. Avec Doctrine, il peut s’appuyer sur la même table que les autres transports tout en utilisant un queue_name distinct. D’autres pilotes, comme AMQP ou Redis, matérialisent cette séparation différemment. Ce qui compte est le nom du transport déclaré dans la configuration.

# config/packages/messenger.yaml
framework:
    messenger:
        failure_transport: failed
        transports:
            async:
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
                retry_strategy:
                    max_retries: 3
                    delay: 1000
                    multiplier: 2
            failed: 'doctrine://default?queue_name=failed'

Cette configuration globale peut être remplacée transport par transport. Si l’application en possède plusieurs, les commandes d’administration acceptent l’option --transport. Il faut alors la préciser pour ne pas inspecter ou relancer la mauvaise file.

php bin/console messenger:failed:show --transport=failed_high_priority

Repérer le bon message sans toucher au reste

Commencez par afficher le contenu du transport d’échec. La commande ne traite rien : elle fournit les identifiants qui serviront ensuite à cibler l’intervention. Sur les versions Symfony actuelles, l’affichage est limité par défaut à cinquante messages, ce qui évite de déverser toute une file volumineuse dans le terminal.

php bin/console messenger:failed:show

Une file chargée se lit plus facilement en réduisant le volume ou en regardant sa répartition. Les options --max, --class-filter et --stats figurent dans la documentation actuelle. Elles ne sont toutefois pas présentes dans toutes les anciennes branches de Messenger : la commande d’aide de l’application reste l’autorité pour la version réellement installée.

php bin/console messenger:failed:show --max=10
php bin/console messenger:failed:show --class-filter='App\Message\GenerateInvoice'
php bin/console messenger:failed:show --stats
php bin/console messenger:failed:show --help

L’identifiant affiché appartient au transport d’échec. Notez-le avec la classe du message et l’heure de l’échec, puis rapprochez-les d’un événement métier connu : numéro de commande, identifiant de document ou trace de requête. Si plusieurs failure transports existent, gardez aussi leur nom. Un identifiant seul peut être ambigu d’un transport à l’autre.

Lire l’échec avant de le rejouer

La vue générale suffit pour trouver un candidat, pas pour décider qu’il est sûr de le relancer. Demandez le détail de cet identifiant avec une verbosité accrue. -vv ajoute les informations utiles au diagnostic, notamment davantage de contexte sur le message et l’exception, sans qu’il soit nécessaire d’imaginer ce que le terminal devrait afficher.

php bin/console messenger:failed:show 20 -vv

Si vous travaillez sur un transport d’échec spécifique, conservez la même précision dans la commande. Exécutez-la aussi dans le même environnement que celui où l’incident s’est produit : une configuration dev peut pointer vers une autre file ou charger d’autres services que prod.

php bin/console messenger:failed:show 20 --transport=failed_high_priority -vv --env=prod

Lisez d’abord la chaîne d’exceptions et le handler concerné, puis le contenu métier du message et ses stamps lorsque ceux-ci sont visibles. Cherchez notamment une dépendance indisponible, une contrainte de base de données, une donnée devenue invalide, un problème de désérialisation ou une limite distante. Les logs applicatifs et ceux du worker complètent ce tableau, surtout s’ils partagent un identifiant de corrélation.

Attention aux données sensibles. Le détail d’un message et une trace peuvent contenir une adresse, un jeton ou une donnée métier confidentielle. Copiez seulement ce qui est nécessaire dans un ticket et expurgez les secrets avant de partager la sortie.

Corriger la cause plutôt que vider la file

Un échec temporaire et un défaut déterministe n’appellent pas la même réponse. Si la base de données ou une API était momentanément indisponible et que le service est revenu, le message peut être rejoué sans changement de code. Si le handler échoue toujours sur la même donnée, il faut d’abord corriger la validation, la migration, la configuration ou l’état métier qui provoque l’exception.

Avant la relance, demandez-vous aussi si le handler est idempotent. Messenger peut livrer un message plusieurs fois dans son fonctionnement normal, par exemple si un worker termine le traitement puis s’arrête avant l’acquittement. Un handler qui débite un paiement, décrémente un stock ou expédie un e-mail doit donc reconnaître un événement déjà traité, idéalement grâce à une clé d’idempotence stable et à une contrainte d’unicité en base.

Une correction de code n’est réellement active qu’après son déploiement et le redémarrage des workers persistants. Symfony recommande de demander leur arrêt propre après le déploiement afin que le gestionnaire de processus les recrée avec le nouveau code.

php bin/console messenger:stop-workers

Dans ce cas, attendez que Supervisor, systemd ou votre orchestrateur ait relancé les consommateurs. Sinon, le message peut être repris par un processus qui conserve encore l’ancienne version du handler, et revenir dans la file pour exactement la même raison.

Rejouer un seul message et observer son passage

La forme la plus sûre reste la relance ciblée et interactive. Passez l’identifiant relevé plus tôt, ajoutez -vv, puis lisez la confirmation proposée par la commande avant de l’accepter. Vous gardez ainsi un dernier arrêt entre le diagnostic et l’exécution.

php bin/console messenger:failed:retry 20 -vv

Sur les versions récentes, lancer messenger:failed:retry -vv sans identifiant parcourt les messages un par un et demande s’il faut les rejouer, les ignorer ou les supprimer ; la possibilité de sauter un message dans ce dialogue est apparue avec Symfony 7.2. Ce mode reste très différent d’une relance forcée de tout le lot, car chaque message conserve une décision humaine.

Pour une automatisation déjà validée, --force supprime la confirmation. Avec un identifiant explicite, la portée reste lisible :

php bin/console messenger:failed:retry 20 --force -vv

En revanche, la commande globale ci-dessous peut reprendre toute la file accessible au transport. Ne la lancez qu’après avoir compté et échantillonné les messages, vérifié que leurs causes sont corrigées, estimé la charge sur les bases et API, et confirmé l’idempotence des effets métier. Avec certains transports, certaines stratégies très permissives ou d’anciennes versions, un message qui échoue immédiatement peut redevenir visible pendant que la commande globale parcourt encore la file : c’est une manière de retomber dans une boucle de traitement. Sur une file hétérogène, préférez donc les identifiants explicites. Consultez toujours messenger:failed:retry --help sur le projet avant d’intégrer une forme globale à un script, car les filtres et le comportement disponibles évoluent avec Messenger.

php bin/console messenger:failed:retry --force -vv

Une relance n’est pas une purge. Ne supprimez pas un message pour faire disparaître l’alerte. La commande messenger:failed:remove a sa place lorsqu’un événement est volontairement abandonné, après analyse et avec une trace de cette décision.

Si le message échoue encore

Gardez la commande ouverte avec -vv et suivez en parallèle les logs de l’application ou de la plateforme. Le niveau -vv de Console correspond à une sortie « très verbeuse » ; il rend visibles des informations que la sortie normale masque, mais il ne remplace pas les logs structurés du handler, du transport et des dépendances.

Une fois la relance terminée, réinterrogez précisément l’identifiant. S’il n’est plus dans le transport d’échec et que l’effet métier attendu est présent une seule fois, la récupération est cohérente. S’il réapparaît, comparez la nouvelle exception à l’ancienne au lieu de relancer encore. Symfony indique qu’un message de nouveau défaillant repasse par les règles normales de retry et peut revenir dans le failure transport ; l’épuisement des tentatives peut ensuite conduire à son abandon selon le cycle et la configuration.

php bin/console messenger:failed:show 20 -vv
php bin/console messenger:stats

L’absence dans la file ne prouve pas, à elle seule, que le métier est terminé. Vérifiez la conséquence attendue dans la source de vérité : facture créée, statut modifié, fichier généré ou appel externe enregistré. Si le handler a produit une partie de ces effets avant de lever l’exception, une nouvelle exécution peut demander une réparation manuelle ou une protection d’idempotence avant toute autre tentative.

Si l’erreur est permanente, UnrecoverableMessageHandlingException permet d’indiquer qu’une nouvelle tentative automatique n’a pas de sens ; avec un failure transport configuré, le message reste néanmoins visible pour analyse. À l’inverse, RecoverableMessageHandlingException force les nouvelles tentatives et peut ignorer max_retries. Cette seconde exception doit être réservée à une indisponibilité réellement transitoire, avec un délai adapté, sous peine de créer la boucle que l’on cherche justement à éviter.

Éviter que la file d’échec ne devienne une boîte noire

Une file d’échec utile doit être visible avant qu’elle ne grossisse. Une alerte sur le nombre de messages, leur ancienneté et leur classe permet de distinguer un incident isolé d’une panne systémique. La commande messenger:stats donne le volume des transports qui savent fournir cette information, tandis que messenger:failed:show --stats détaille les classes dans les versions qui proposent ce filtre.

Les logs gagnent aussi à porter la classe du message, un identifiant métier stable, le nom du transport, le nombre de tentatives et la classe de l’exception, sans sérialiser aveuglément toutes les données. Cette corrélation raccourcit le diagnostic et permet de voir si plusieurs messages échouent pour une même cause.

Enfin, traitez la file comme un stock à gouverner, pas comme une corbeille. Une courte procédure d’exploitation doit préciser qui analyse, qui peut rejouer ou supprimer, comment garder la trace d’une décision et quel seuil déclenche une alerte. Lorsqu’un incident survient, cette discipline rend la relance ciblée presque banale : on retrouve le bon message, on comprend son échec, on corrige, on le rejoue une fois et on vérifie le résultat.

Questions fréquentes

01

Le retry relance-t-il toute la file ?

Pas si vous fournissez un identifiant. messenger:failed:retry 20 cible ce message, tandis que la commande sans identifiant parcourt la file et que --force retire les confirmations.

02

Pourquoi utiliser -vv ?

Ce niveau augmente la verbosité de Console et expose davantage de contexte pendant l’inspection ou la relance. Il complète les logs structurés, mais ne doit pas être publié sans retirer les données sensibles.

03

Que faire si plusieurs failure transports existent ?

Ajoutez --transport=nom_du_transport aux commandes show, retry ou remove. Conservez ce nom avec l’identifiant pendant toute l’intervention.

04

Peut-on supprimer un message irrécupérable ?

Oui, avec messenger:failed:remove ID, mais seulement après avoir compris l’échec et documenté l’abandon. Une suppression efface un symptôme ; elle ne corrige pas le handler ni les effets déjà produits.

Partager cet article

Partager
L

Écrit par

larevuegeek

À lire aussi

Commentaires (0)

Connectez-vous pour laisser un commentaire.