Le problème
Un service central reçoit des événements et doit les livrer : notifications, webhooks sortants. Les destinataires tombent, redémarrent, dépassent les délais. Deux échecs classiques :
- La perte silencieuse. L’envoi échoue, on loggue, on passe. Le message n’existe plus, et personne ne s’en aperçoit avant que ça compte.
- Le retry en mémoire. Mieux, jusqu’au premier redéploiement, où la file disparaît avec le processus.
Le pattern
Traiter la livraison comme un état persisté :
- Chaque livraison est une ligne dans PostgreSQL : événement, destination, nombre d’essais, échéance du prochain essai, statut.
- Backoff exponentiel. Chaque échec repousse l’échéance suivante de plus en plus loin. On laisse au destinataire le temps de se remettre au lieu de le marteler.
- Un worker réclame ce qui est dû, c’est-à-dire les livraisons dont l’échéance est passée, de façon transactionnelle : l’état ne ment jamais.
- Reprise gratuite. Après un crash ou un déploiement, la file est toujours là. Le worker reprend exactement où l’état l’indique. Combiné au graceful shutdown, un redéploiement ne coupe rien à mi-chemin.
- Borne et visibilité. Au-delà d’un nombre d’essais, la livraison passe en échec définitif : visible, requêtable en SQL, jamais évaporée.
Pourquoi pas un broker ?
RabbitMQ ou un équivalent fournit tout ça nativement, au prix d’une pièce stateful critique supplémentaire à exploiter, sauvegarder et superviser. Sur une plateforme mono-nœud dont la base est déjà sauvegardée et connue, le compromis penche pour PostgreSQL : les garanties viennent des transactions, et l’outillage de diagnostic est du SQL. Le jour où le débit l’exige, l’interface de publication permet de changer d’implémentation sans toucher au reste.
Les leçons
- L’idempotence est la moitié du pattern. Un retry peut livrer deux fois, le destinataire doit pouvoir l’encaisser.
- Le backoff protège les deux bouts. Le destinataire respire, et l’émetteur ne brûle pas ses ressources sur un service injoignable.
- Un échec définitif doit se voir. Une file qui avale les erreurs sans bruit reproduit le problème initial, avec une étape de plus.