Construire des systèmes temps réel sur PostgreSQL : au-delà de l'ORM

Équipe Atlas Verified
·
13 février 2026
·
8 min de lecture
Construire des systèmes temps réel sur PostgreSQL : au-delà de l'ORM

Pour les mises à jour temps réel, la réponse habituelle en 2025 est un service géré : Firebase Realtime Database, Supabase Realtime, Pusher, Ably ou une couche WebSocket hébergée. Ils masquent la complexité de l'envoi des mises à jour aux clients connectés. Ils fonctionnent bien — jusqu'à ce qu'ils échouent.

Chez Atlas Verified, nous avons commencé avec une plateforme de base gérée incluant des abonnements temps réel. Avec la croissance, nous avons atteint les limites de cette abstraction : contraintes de pools de connexions, fiabilité du temps réel, besoins de requêtes spatiales PostGIS et de déclencheurs personnalisés nous ont tous poussés à reprendre le contrôle de la couche de base de données.

Nous avons migré vers PostgreSQL auto-hébergé et construit le temps réel avec son mécanisme natif LISTEN/NOTIFY associé aux Server-Sent Events. Cet article présente la décision, l'architecture et nos enseignements.

Pourquoi nous avons quitté la plateforme gérée

Les plateformes de bases gérées offrent une proposition convaincante : authentification, sécurité au niveau des lignes, abonnements temps réel, API générées automatiquement et tableau de bord — prêts à l'emploi. Pour les produits débutants, c'est précieux. On peut créer une application fonctionnelle sans écrire de code serveur.

Les problèmes apparaissent lorsque l'application mûrit et que vos exigences s'écartent des hypothèses de la plateforme.

Limites de connexion. Les plateformes gérées acheminent souvent les connexions via un gestionnaire de pools imposant ses limites. Avec plusieurs services serveur nécessitant leurs propres pools — serveur API, tâches d'arrière-plan, écouteur temps réel — les connexions disponibles s'épuisent vite. Nous contournions les limites du gestionnaire au lieu de résoudre les vrais problèmes du produit.

Fiabilité du temps réel. Le temps réel de la plateforme reposait sur la capture des changements dans le journal d'écriture anticipée de PostgreSQL. Cette architecture élégante créait des défaillances incontrôlables. La réplication WAL a une latence inhérente. Le service prenait parfois du retard et livrait des mises à jour périmées. La reconnexion après interruptions réseau était peu fiable. Le débogage exigeait l'accès à une infrastructure hors de notre contrôle.

Exigences d'extensions. Notre application traite des documents contenant des données géographiques : emplacements portuaires, itinéraires, positions des navires. PostGIS — extension spatiale de PostgreSQL — est essentiel. La plateforme le prenait en charge, mais nous avions besoin de versions et configurations spécifiques parfois indisponibles.

Logique de déclencheurs personnalisée. Nous avions besoin de déclencheurs produisant des événements propres à l'application, au-delà de notifications génériques « ligne modifiée ». Quand le statut d'un document change, il faut prévenir précisément le client connecté qui l'a téléversé. La plateforme opérait au niveau de la table ; nous avions besoin du niveau de l'entité.

La migration était une décision pragmatique : nous passions plus de temps à contourner les contraintes qu'à construire nous-mêmes les fonctions, au-delà de questions de coût ou de principe.

LISTEN/NOTIFY : le mécanisme publication/abonnement caché de PostgreSQL

PostgreSQL inclut un mécanisme natif de publication/abonnement depuis la version 9.0, sortie en 2010. Malgré ses plus de 15 ans, LISTEN/NOTIFY est remarquablement sous-utilisé. La plupart des développeurs ignorent son existence.

Le mécanisme est simple. Toute connexion peut exécuter LISTEN sur un canal nommé pour s'abonner. Toute connexion peut exécuter NOTIFY pour envoyer un message à tous les écouteurs du canal. La charge utile est une chaîne limitée à 8,000 octets.

Ce qui rend LISTEN/NOTIFY puissant pour les applications temps réel :

Aucune infrastructure supplémentaire. Aucun courtier de messages distinct à déployer, surveiller ou dimensionner. Avec PostgreSQL, vous avez publication/abonnement. Sans Redis, RabbitMQ ou Kafka — pour cet usage précis.

Garanties transactionnelles. Les messages NOTIFY sont livrés uniquement à la validation de la transaction englobante. Si une transaction insère une ligne et envoie une notification, puis est annulée, la notification n'est jamais livrée. Les courtiers externes n'offrent pas cette propriété sans orchestration soigneuse.

Intégration aux déclencheurs. Les déclencheurs PostgreSQL peuvent appeler pg_notify pendant leur exécution. La base peut donc prévenir automatiquement les écouteurs lors de changements précis, sans code applicatif dans le chemin d'écriture.

Les limites sont réelles et doivent être comprises :

Aucune persistance. Si personne n'écoute lors d'un NOTIFY, le message est perdu. Ni relecture, ni file de messages, ni garantie de livraison aux clients déconnectés. Les écouteurs doivent gérer reconnexion et récupération de l'état.

Taille des charges utiles. La limite de 8,000 octets empêche l'envoi de grands objets. Nous envoyons le minimum — nom de table, type d'opération et clé primaire — et laissons le client récupérer les données complètes au besoin.

Périmètre d'une seule base. LISTEN/NOTIFY fonctionne dans une instance PostgreSQL unique. Il ne s'étend pas aux réplicas ou clusters. Pour les architectures où toutes les écritures vont sur un primaire unique, ce n'est pas une limite.

Notre architecture : déclencheurs, SSE et multiplexage des canaux

Voici comment les éléments s'articulent.

Les déclencheurs de base de données sont installés sur les tables nécessitant du temps réel : documents, messages, conversations, notifications, vérifications d'expédition et autres. Lors d'une insertion, modification ou suppression, le déclencheur appelle pg_notify avec une charge JSON comprenant table, type d'opération, clé primaire et éventuelles colonnes de filtrage.

Une connexion dédiée d'écoute dans notre serveur API exécute LISTEN sur le canal et reçoit tous les événements. C'est une connexion PostgreSQL dédiée de longue durée, distincte du pool de requêtes, uniquement consacrée à l'écoute.

Un bus d'événements reçoit les notifications et les dirige vers les clients selon leurs abonnements. Un client abonné aux documents d'un identifiant d'expédition précis ne reçoit que les notifications de cette expédition.

Les Server-Sent Events livrent les notifications au navigateur. Chaque client ouvre une connexion SSE au serveur API, en précisant canaux et filtres. Le bus rapproche les notifications des abonnements et transmet les événements correspondants par les bonnes connexions.

Pourquoi SSE plutôt que WebSockets

Nous restons satisfaits de cette décision. Les Server-Sent Events sont plus simples que WebSockets sur tous les aspects importants pour notre usage.

Unidirectionnels par conception. Nos besoins vont du serveur au client : le serveur indique les changements de données. Le client n'a pas besoin d'envoyer des messages temps réel — il utilise des requêtes HTTP ordinaires. Le modèle unidirectionnel de SSE correspond à notre besoin réel.

Natifs HTTP. Les connexions SSE sont des requêtes HTTP standard avec un type de contenu précis. Elles traversent mandataires HTTP, répartiteurs de charge et CDN sans configuration spéciale. WebSockets exige HTTP Upgrade, parfois mal géré par les mandataires et totalement bloqué par certains pare-feu d'entreprise.

Reconnexion automatique. L'API EventSource du navigateur gère nativement la reconnexion. Si la connexion tombe, il se reconnecte automatiquement avec temporisation exponentielle. Avec WebSockets, vous devez écrire cette logique — et vous tromper au moins une fois.

Le seul domaine où WebSockets l'emporte — la communication bidirectionnelle — ne nous est pas nécessaire. Pour les messages temps réel du client au serveur, WebSockets convient. Pour les notifications du serveur au client, SSE convient.

Construire une couche de requêtes sans ORM

En quittant la plateforme gérée, nous avons aussi quitté son API de requêtes générée. Il nous fallait une couche d'accès à la base pour notre serveur API.

Les options habituelles sont les ORM comme Prisma, TypeORM ou Drizzle, ou le SQL brut avec un pilote. Nous avons choisi un générateur léger de requêtes offrant une API chaînable, qui génère et exécute du SQL brut sous-jacent.

API familière. Notre code existant utilisait la bibliothèque cliente de la plateforme. Une API compatible a limité les modifications lors de la migration. Les requêtes pouvaient passer par notre générateur avec une syntaxe identique.

Aucune magie. Les ORM génèrent du SQL invisible et parfois incontrôlable. Face à une requête lente, il faut lire le SQL généré, souvent peu ressemblant à l'appel ORM d'origine. Notre générateur produit un SQL prévisible : chaque méthode chaînée correspond à une clause précise et la requête est toujours inspectable.

Jointures intégrées. Les données commerciales sont relationnelles. Une expédition possède des documents, qui possèdent des nœuds, qui possèdent des contrôles. Exprimer des sélections imbriquées et obtenir un résultat imbriqué sans écrire à chaque fois une requête JOIN complexe améliore considérablement l'expérience de développement.

Le résultat est une couche de quelques centaines de lignes, traitant la grande majorité de nos interactions et produisant un SQL toujours lisible et optimisable.

Budget des pools de connexions

Avec PostgreSQL auto-hébergé, vous gérez le budget de connexions. max_connections fixe le plafond absolu et chaque connexion consomme de la mémoire.

Considérer les connexions comme un budget — une ressource finie partagée entre services — évite une catégorie de problèmes affectant les équipes qui ne les anticipent pas.

Chaque service déclare son maximum de connexions. La somme doit rester largement inférieure à la limite PostgreSQL, avec une marge pour les connexions ponctuelles : sessions psql, scripts de migration, outils de surveillance. Cela paraît évident, mais la plupart attendent l'épuisement en production. En budgétant en amont, nous avons évité cette alerte particulière à 3 heures du matin.

Migrations sûres à grande échelle

Migrer vers PostgreSQL auto-hébergé a nécessité de déplacer des centaines de tables, fonctions, déclencheurs et index. Mais le plus grand défi était un système de migration continuant à fonctionner avec l'évolution de l'application.

Notre système automatique fonctionne ainsi : les fichiers horodatés se trouvent dans un dossier dédié. Un exécuteur le parcourt, compare à une table des migrations appliquées et exécute celles en attente dans l'ordre. Chaque migration est transactionnelle : en cas d'échec, elle est annulée proprement et bloque le déploiement jusqu'à correction.

L'exécuteur s'active automatiquement à chaque déploiement, avant le démarrage. Les changements de schéma font donc partie du processus, sans étapes manuelles. Un développeur crée un fichier SQL horodaté, le pousse dans le dépôt et le prochain déploiement l'applique.

Décisions clés pour la sécurité : les migrations utilisent des garde-fous idempotents pour résister aux applications partielles. Chacune doit être un script SQL autonome sans intervention humaine. En cas d'échec, toute la transaction est annulée : l'application ne démarre pas avec un schéma partiellement modifié.

Ce que nous avons gagné

PostgreSQL auto-hébergé avec temps réel personnalisé a représenté un investissement important. Mais les bénéfices sont considérables :

Fiabilité. Les notifications arrivent quelques millisecondes après le changement. Aucun retard de réplication WAL ni panne tierce sur le chemin critique.

Facilité de débogage. En cas de problème, nous traçons tout : déclencheur activé, notification envoyée, routage du bus, livraison SSE. Chaque étape est dans notre code, nos journaux et notre infrastructure.

Souplesse. Ajouter le temps réel à une table nécessite une seule migration SQL installant un déclencheur. Ni configuration de plateforme, ni tableau de bord tiers, ni ticket d'assistance.

Performance. Les connexions PostgreSQL directes avec pool optimisé surpassent celles routées par une plateforme gérée. La latence des requêtes a nettement diminué après migration.

La contrepartie est la responsabilité opérationnelle. Nous gérons la base, les sauvegardes, la réplication et la capacité. Pour une équipe à l'aise avec PostgreSQL, c'est gérable. Pour les autres, les abstractions de la plateforme peuvent valoir leurs limites.


Atlas Verified fonctionne sur PostgreSQL auto-hébergé avec une infrastructure temps réel personnalisée, traitant quotidiennement des milliers de vérifications de conformité commerciale dans la chaîne mondiale.

Cet article vous a plu ?

Partagez-le avec votre réseau