PostgreSQL LISTEN/NOTIFY peut alimenter des tableaux de bord et des alertes en temps réel avec une configuration minimale. Découvrez où il est adapté, ses limites et quand ajouter un broker.

Les « mises à jour en direct » dans une interface produit signifient généralement que l'écran change peu après qu'un événement se soit produit, sans que l'utilisateur rafraîchisse. Un chiffre augmente sur un tableau de bord, une pastille rouge apparaît dans une boîte de réception, un admin voit une nouvelle commande ou une notification s'affiche disant « Build finished » ou « Payment failed ». L'important, c'est le timing : ça donne l'impression d'être instantané, même si ça prend une ou deux secondes.
Beaucoup d'équipes commencent par le polling : le navigateur demande au serveur « y a-t-il du nouveau ? » toutes les quelques secondes. Le polling fonctionne, mais il a deux inconvénients courants.
D'abord, ça semble lent car l'utilisateur ne voit les changements qu'au prochain poll.
Ensuite, ça peut coûter cher car vous effectuez des vérifications répétées même quand rien n'a changé. Multipliez cela par des milliers d'utilisateurs et ça devient du bruit.
PostgreSQL LISTEN/NOTIFY existe pour un cas plus simple : « dis-moi quand quelque chose a changé. » Au lieu de demander en boucle, votre application peut attendre et réagir quand la base de données envoie un petit signal.
C'est adapté aux UIs où un petit coup de pouce suffit. Par exemple :
Le compromis est simplicité vs garanties. LISTEN/NOTIFY est facile à ajouter car c'est déjà dans Postgres, mais ce n'est pas un système de messagerie complet. La notification est un indice, pas un enregistrement durable. Si un écouteur est déconnecté, il peut manquer le signal.
Une approche pratique consiste à laisser NOTIFY réveiller votre appli, puis votre appli relit la vérité dans les tables.
Pensez à LISTEN/NOTIFY comme à une simple sonnette intégrée dans votre base de données. Votre appli peut attendre que la sonnette sonne, et une autre partie du système peut la déclencher quand quelque chose change.
Une notification a deux parties : un nom de canal et un payload optionnel. Le canal est comme une étiquette de sujet (par exemple orders_changed). Le payload est un court message texte que vous joignez (par exemple un id de commande). PostgreSQL n'impose aucune structure, donc les équipes envoient souvent de petites chaînes JSON.
Une notification peut être déclenchée depuis le code applicatif (votre serveur API exécute NOTIFY) ou depuis la base de données elle-même via un trigger (un trigger exécute NOTIFY après un insert/update/delete).
Côté réception, votre serveur ouvre une connexion à la base et exécute LISTEN channel_name. Cette connexion reste ouverte. Lorsqu'un NOTIFY channel_name, 'payload' se produit, PostgreSQL pousse un message à toutes les connexions écoutant ce canal. Votre appli réagit alors (rafraîchir le cache, récupérer la ligne modifiée, pousser un événement WebSocket au navigateur, etc.).
NOTIFY est mieux compris comme un signal, pas comme un service de livraison :
Utilisé ainsi, PostgreSQL LISTEN/NOTIFY peut alimenter des mises à jour UI en direct sans ajouter d'infrastructure supplémentaire.
LISTEN/NOTIFY brille quand votre UI a seulement besoin d'un petit coup de pouce indiquant qu'une chose a changé, pas d'un flux d'événements complet. Pensez « rafraîchir ce widget » ou « il y a un nouvel élément » plutôt que « traiter chaque clic dans l'ordre ».
Ça fonctionne mieux quand la base de données est déjà votre source de vérité et que vous voulez que l'UI reste synchronisée. Un modèle courant : écrire la ligne, envoyer une petite notification contenant un ID, et laisser l'UI (ou une API) récupérer l'état le plus récent.
LISTEN/NOTIFY suffit généralement quand la plupart de ces conditions sont vraies :
Un exemple concret : un dashboard interne affiche « tickets ouverts » et une pastille pour les « nouvelles notes ». Quand un agent ajoute une note, votre backend l'écrit dans Postgres et NOTIFY ticket_changed avec l'id du ticket. Le navigateur le reçoit via WebSocket et refait la requête pour cette carte de ticket. Pas d'infra supplémentaire, et l'UI paraît en direct.
LISTEN/NOTIFY peut être génial au début, mais il a des limites nettes. Ces limites apparaissent quand vous traitez les notifications comme un système de messagerie au lieu d'un simple « tapotement sur l'épaule ».
La plus grosse lacune est la durabilité. Un NOTIFY n'est pas un job en file. Si personne n'écoute à ce moment-là, le message est perdu. Même quand un écouteur est connecté, un crash, un déploiement, un problème réseau ou un redémarrage de la base peut couper la connexion. Vous n'obtiendrez pas automatiquement les notifications « manquées ».
Les déconnexions sont particulièrement pénibles pour les fonctionnalités orientées utilisateur. Imaginez un tableau de bord qui montre de nouvelles commandes. Un onglet navigateur se met en veille, le WebSocket se reconnecte, et l'UI semble « bloquée » parce qu'elle a raté des événements. On peut contourner cela, mais la solution n'est plus « juste LISTEN/NOTIFY » : vous reconstruisez l'état en interrogeant la base et vous utilisez NOTIFY seulement comme indice pour rafraîchir.
Le fan-out est un autre problème fréquent. Un événement peut réveiller des centaines ou milliers d'écouteurs (beaucoup d'instances applicatives, beaucoup d'utilisateurs). Si vous utilisez un canal bruyant comme orders, chaque écouteur se réveille même si un seul utilisateur est concerné. Cela peut créer des pics de CPU et de pression sur les connexions au pire moment.
La taille du payload et la fréquence sont les derniers pièges. Les payloads NOTIFY sont petits, et des événements haute fréquence peuvent s'accumuler plus vite que ce que les clients peuvent traiter.
Surveillez ces signes :
À ce stade, gardez NOTIFY comme un « poke », et déplacez la fiabilité vers une table ou un vrai message broker.
Un schéma fiable avec LISTEN/NOTIFY consiste à traiter NOTIFY comme un nudge, pas comme la source de vérité. La ligne en base est la vérité ; la notification dit à votre appli quand aller regarder.
Effectuez l'écriture dans une transaction, et n'envoyez la notification qu'après que le changement de données soit commit. Si vous notifiez trop tôt, les clients peuvent se réveiller et ne pas trouver les données.
Un arrangement courant est un trigger qui se déclenche sur INSERT/UPDATE et envoie un petit message.
NOTIFY dashboard_updates, '{"type":"order_changed","order_id":123}'::text;
Le nommage des canaux fonctionne mieux quand il correspond à la façon dont les gens pensent le système. Exemples : dashboard_updates, user_notifications, ou par tenant comme tenant_42_updates.
Gardez le payload petit. Mettez des identifiants et un type, pas des enregistrements complets. Une forme utile par défaut est :
type (ce qui s'est passé)id (ce qui a changé)tenant_id ou user_idCela réduit la bande passante et évite de révéler des données sensibles dans les logs de notification.
Les connexions tombent. Prévoyez-le.
À la connexion, exécutez LISTEN pour tous les canaux nécessaires. À la déconnexion, reconnectez avec un backoff court. À la reconnexion, faites LISTEN à nouveau (les abonnements ne persistent pas). Après reconnexion, faites un refetch rapide des « changements récents » pour couvrir les événements potentiellement manqués.
Pour la plupart des mises à jour UI en direct, le refetch est le plus sûr : le client reçoit {type, id} puis demande au serveur l'état le plus récent.
Le patch incrémental peut être plus rapide, mais il est plus facile de se tromper (événements hors ordre, échecs partiels). Un bon compromis : refetcher de petites portions (une ligne de commande, une carte de ticket, un compteur) et laisser les agrégats plus lourds sur un timer court.
Quand vous passez d'un tableau d'administration à de nombreux utilisateurs regardant les mêmes chiffres, de bonnes habitudes comptent plus que du SQL malin. LISTEN/NOTIFY peut toujours bien fonctionner, mais il faut façonner le flux des événements de la base vers les navigateurs.
Une base commune : chaque instance applicative ouvre une connexion longue qui LISTEN, puis pousse les mises à jour aux clients connectés. Ce schéma « un écouteur par instance » est simple et souvent suffisant si vous avez un petit nombre de serveurs et que vous tolérez des reconnexions occasionnelles.
Si vous avez beaucoup d'instances (ou des workers serverless), un service d'écoute partagé peut être plus simple. Un petit process écoute une seule fois, puis fan-out les mises à jour au reste de votre stack. Il vous offre aussi un endroit unique pour ajouter du batching, des métriques et du backpressure.
Pour les navigateurs, vous poussez typiquement via WebSockets (bidirectionnel, idéal pour les UIs interactives) ou Server-Sent Events (SSE) (unidirectionnel, plus simple pour les dashboards). De toute façon, évitez d'envoyer « rafraîchir tout ». Envoyez des signaux compacts comme « order 123 changed » afin que l'UI ne refetch que le nécessaire.
Pour éviter que l'UI ne se mette en boucle, ajoutez quelques garde-fous :
Le design des canaux compte aussi. Au lieu d'un canal global, partitionnez par tenant, équipe ou fonctionnalité pour que les clients reçoivent seulement les événements pertinents. Par exemple : notify:tenant_42:billing et notify:tenant_42:ops.
LISTEN/NOTIFY semble simple, c'est pourquoi les équipes le déploient vite puis sont surprises en prod. La plupart des problèmes viennent du fait de le traiter comme une file de messages garantie.
Si votre appli se reconnecte (déploiement, coupure réseau, bascule DB), tout NOTIFY envoyé pendant la déconnexion est perdu. La solution est de considérer la notification comme un signal, puis de reverifier la base.
Un modèle pratique : stockez l'événement réel dans une table (avec un id et created_at), puis à la reconnexion récupérez tout ce qui est plus récent que votre dernier id vu.
Les payloads LISTEN/NOTIFY ne sont pas destinés à de gros blobs JSON. Les gros payloads ajoutent du travail, du parsing et des risques de limites atteintes.
Utilisez des indices très concis comme "order:123". Ensuite l'appli lit l'état depuis la base.
Une erreur fréquente est de concevoir l'UI autour du contenu du payload comme si c'était la source de vérité. Cela rend les changements de schéma et les versions client pénibles.
Gardez une séparation claire : notifiez qu'une chose a changé, puis récupérez les données actuelles avec une requête normale.
Les triggers qui NOTIFY à chaque changement de ligne peuvent inonder votre système, surtout pour les tables très actives.
Notifiez seulement sur des transitions signifiantes (par ex. changements de statut). Si vous avez des mises à jour très bruyantes, batcher les changements (un notify par transaction ou par fenêtre temporelle) ou retirez ces mises à jour du chemin de notification.
Même si la base peut envoyer des notifications, votre UI peut quand même flancher. Un dashboard qui rerender à chaque événement peut geler.
Debouncez les mises à jour côté client, regroupez les rafales en un seul rafraîchissement et préférez « invalider et refetcher » plutôt que d'appliquer chaque delta. Par exemple : la pastille de notification peut se mettre à jour instantanément, mais la liste déroulante se rafraîchit au maximum une fois toutes les quelques secondes.
LISTEN/NOTIFY est excellent quand vous voulez un petit signal « quelque chose a changé » pour que l'appli puisse refetcher des données fraîches. Ce n'est pas un système de messagerie complet.
Avant de baser votre UI dessus, répondez à ces questions :
LISTEN/NOTIFY est souvent suffisant.Une règle pratique : si vous pouvez traiter NOTIFY comme un nudge (« va relire la ligne ») plutôt que comme le payload lui-même, vous êtes dans la zone sûre.
Exemple : un dashboard admin affiche de nouvelles commandes. Si une notification est manquée, le prochain poll ou un rafraîchissement de page affiche quand même le bon compteur. C'est un bon cas. Mais si vous envoyez des événements « facturer cette carte » ou « expédier ce colis », en rater un peut provoquer un incident réel.
Imaginez une petite application commerciale : un tableau de bord affiche le chiffre d'affaires du jour, le total des commandes et une liste des « commandes récentes ». En parallèle, chaque commercial doit recevoir une notification rapide quand une commande dont il est responsable est payée ou expédiée.
Une approche simple est de considérer PostgreSQL comme source de vérité, et d'utiliser LISTEN/NOTIFY seulement comme la tape sur l'épaule indiquant qu'une chose a changé.
Quand une commande est créée ou que son statut change, votre backend fait deux choses dans la même requête : il écrit/la met à jour et ensuite envoie un NOTIFY avec un petit payload (souvent juste l'id de commande et le type d'événement). L'UI ne dépend pas du payload NOTIFY pour les données complètes.
Un flux pratique ressemble à ceci :
NOTIFY orders_events avec {"type":"status_changed","order_id":123}.Cela garde NOTIFY léger et limite les requêtes coûteuses.
Quand le trafic augmente, des failles apparaissent : un pic d'événements peut submerger un écouteur unique, des notifications peuvent être manquées à la reconnexion, et vous aurez besoin d'une livraison et d'un replay garantis. C'est souvent le moment d'ajouter une couche plus fiable (une table outbox plus un worker, puis un broker si nécessaire) tout en gardant Postgres comme source de vérité.
LISTEN/NOTIFY est génial quand vous avez besoin d'un petit signal « quelque chose a changé ». Il n'est pas conçu pour être un système de messagerie complet. Quand vous commencez à dépendre des événements comme source de vérité, il est temps d'ajouter un broker.
LISTEN/NOTIFYSi l'un de ces points apparaît, un broker vous évitera bien des problèmes :
LISTEN/NOTIFY n'enregistre pas les messages pour plus tard. C'est un signal push, pas un log persistant. Parfait pour « rafraîchir une tuile », risqué pour « lancer une facturation » ou « expédier un colis ».
Un broker vous donne un vrai modèle de flux de messages : files (travail à faire), topics (broadcast à beaucoup), rétention (conserver les messages de minutes à jours) et accusés de réception (un consommateur confirme le traitement). Cela vous permet de séparer « la base a changé » de « tout ce qui doit arriver parce que ça a changé ».
Vous n'avez pas besoin de l'outil le plus complexe. Des options courantes sont Redis (pub/sub ou streams), NATS, RabbitMQ et Kafka. Le bon choix dépend de si vous avez besoin de files simples, d'un fan-out à de nombreux services ou de la possibilité de rejouer l'historique.
Vous pouvez migrer sans tout réécrire. Un schéma pratique consiste à garder NOTIFY comme signal d'éveil pendant que le broker devient la source de livraison.
Commencez par écrire une « event row » dans une table dans la même transaction que votre changement métier, puis laissez un worker publier cet événement sur le broker. Pendant la transition, NOTIFY peut toujours indiquer à votre couche UI de vérifier les nouveaux événements, tandis que des workers background consomment depuis le broker avec retries et audit.
De cette façon, les dashboards restent réactifs et les workflows critiques cessent de dépendre de notifications en best-effort.
Choisissez un écran (une tuile de dashboard, un compteur de pastille, un toast « nouvelle notification ») et branchez-le de bout en bout. Avec LISTEN/NOTIFY vous pouvez obtenir un résultat utile rapidement, à condition de garder le périmètre restreint et de mesurer ce qui se passe sous charge réelle.
Commencez par le modèle fiable le plus simple : écrivez la ligne, committez, puis émettez un petit signal indiquant qu'une chose a changé. Dans l'UI, réagissez au signal en récupérant l'état le plus récent (ou la portion nécessaire). Cela garde les payloads petits et évite des bugs subtils lorsque les messages arrivent hors ordre.
Ajoutez de l'observabilité basique tôt. Vous n'avez pas besoin d'outils sophistiqués pour commencer, mais vous devez avoir des réponses quand le système devient bruyant :
notify et les picsGardez les contrats simples et documentés. Décidez des noms de canaux, des noms d'événements et de la forme du payload (même si c'est juste un ID). Un petit « catalogue d'événements » dans votre repo évite la dérive.
Si vous construisez rapidement et voulez garder la stack simple, une plateforme comme Koder.ai (koder.ai) peut vous aider à livrer la première version avec une UI React, un backend Go et PostgreSQL, puis itérer au fur et à mesure que vos besoins deviennent plus clairs.
Utilisez LISTEN/NOTIFY quand vous avez seulement besoin d'un signal rapide indiquant qu'une chose a changé, par exemple pour rafraîchir le compteur d'une pastille ou une tuile de tableau de bord. Traitez la notification comme un indice pour relire les données réelles dans les tables, pas comme la source de vérité.
Le polling interroge le serveur selon un calendrier, donc les utilisateurs voient souvent les changements en retard et votre serveur effectue des requêtes même quand rien n'a changé. LISTEN/NOTIFY pousse un petit signal au moment du changement, ce qui paraît généralement plus réactif et évite beaucoup de requêtes vides.
Non, c'est best-effort. Si l'écouteur est déconnecté pendant un NOTIFY, il peut manquer le signal car les notifications ne sont pas stockées pour une relecture ultérieure.
Gardez-le petit et traitez-le comme un indice. Un format pratique est une petite chaîne JSON avec un type et un id, puis laissez votre application interroger Postgres pour l'état courant.
Le modèle courant consiste à envoyer la notification seulement après que l'écriture soit commitée. Si vous notifiez trop tôt, un client peut se réveiller et ne pas trouver la nouvelle ligne.
Le code applicatif est en général plus simple à comprendre et à tester car il est explicite. Les triggers sont utiles lorsque plusieurs writers touchent la même table et que vous voulez un comportement cohérent quelle que soit la source du changement.
Préparez-vous aux reconnexions comme comportement normal. À la reconnexion, exécutez de nouveau LISTEN pour les canaux nécessaires et faites un refetch rapide de l'état récent pour couvrir ce que vous avez pu manquer hors ligne.
Ne laissez pas chaque navigateur se connecter à Postgres. Une configuration typique est une connexion longue par instance backend qui LISTEN, puis votre backend relaie les événements aux navigateurs via WebSocket ou SSE et l'UI refait les requêtes nécessaires.
Utilisez des canaux plus ciblés pour que seuls les consommateurs concernés se réveillent, et batcher les rafales bruyantes. Un débounce de quelques centaines de millisecondes et la coalescence des mises à jour en double empêchent votre UI et votre backend de surchauffer.
Passez à une solution plus robuste lorsque vous avez besoin de durabilité, de retries, de groupes de consommateurs, d'ordonnancement garanti ou d'audit/replay. Si manquer un événement peut provoquer un incident (facturation, expédition, workflows irréversibles), utilisez une table outbox + worker ou un broker dédié plutôt que de compter seulement sur NOTIFY.