- Il fallait gérer des mises à jour en temps réel à grande échelle sur un backend basé sur Node.js/TypeScript
- PostgreSQL était utilisé comme backend, avec des centaines de nœuds workers qui devaient vérifier en continu l’arrivée de nouvelles tâches, tandis que les agents devaient recevoir les mises à jour d’état d’exécution et de chat
- L’exploration a commencé avec les WebSockets, mais a abouti à une solution « à l’ancienne » étonnamment efficace
→ "HTTP Long Polling avec Postgres"
Le problème : des mises à jour en temps réel à grande échelle
- Mises à jour des nœuds workers :
- Il y a des centaines de nœuds workers exécutant les SDK Node.js/Golang/C#
- Ils devaient être informés dès qu’une nouvelle tâche était disponible, ce qui nécessitait une stratégie de requêtes qui ne fasse pas tomber la base de données Postgres
- Synchronisation de l’état des agents :
- Les agents avaient besoin de mises à jour en temps réel sur l’état des exécutions et des chats, et il fallait les diffuser efficacement
Comparaison entre long polling et WebSocket
- Le short polling ressemble à un train qui part strictement selon un horaire fixe : il part à intervalles réguliers, qu’il y ait des passagers ou non
- Le long polling consiste à laisser le serveur attendre avant de répondre, puis à renvoyer immédiatement la réponse dès que des données apparaissent ; sinon, il renvoie une réponse à l’expiration du délai
- Autrement dit, c’est comme un train qui « attend puis part dès qu’il y a des données ». Il ne part à vide que si aucun passager n’arrive dans un certain délai (TTL)
- Cela offre deux avantages : départ immédiat quand il y a des données (passagers), et utilisation efficace des ressources quand il n’y en a pas
- WebSocket maintient une connexion permanente pour échanger des données dans les deux sens
- En raison des environnements d’entreprise, de l’infrastructure, des problèmes de pare-feu, etc., le long polling était plus simple et plus compatible à mettre en place que les WebSockets
Détails d’implémentation du long polling
- La fonction
getJobStatusSync joue un rôle central
- Elle prend des paramètres comme
jobId, owner, ttl, etc., et interroge de manière répétée l’état d’une tâche donnée pendant une certaine durée
- Les requêtes répétées continuent jusqu’à ce que l’une des conditions suivantes soit remplie
- l’état de la tâche devienne
success ou failure
- le
ttl (timeout) expire
- La base de données est interrogée toutes les 500 ms ; si le résultat n’est pas encore final, on attend puis on recommence
- En cas de dépassement du délai, une erreur est levée ; en cas de succès, le résultat est renvoyé
Optimisation de la base de données
- Des index appropriés ont été ajoutés dans Postgres afin de minimiser le coût des requêtes
- Exemple :
CREATE INDEX idx_jobs_status ON jobs(id, cluster_id);
Avantages du long polling
- Facilité de maintenance du monitoring : il est possible de réutiliser tel quel l’existant en matière de logs et de monitoring basés sur HTTP
- Simplicité de l’authentification : nul besoin d’implémenter un nouveau mode d’authentification, l’auth HTTP existante peut être réutilisée
- Compatibilité avec l’infrastructure : aucune configuration spécifique n’est nécessaire côté pare-feu ou load balancer, le trafic est traité comme du HTTP classique
- Simplicité opérationnelle : même lors d’un redémarrage du serveur, il n’y a pas d’état de connexion particulier à gérer, et le débogage est plus facile
- Implémentation client simple : il suffit d’ajouter une logique de retry à une structure standard de requête-réponse HTTP
Comparaison avec ElectricSQL
- ElectricSQL est une solution qui synchronise les données Postgres avec le frontend
- Elle offre une architecture qui garantit le temps réel en utilisant HTTP plutôt que WebSocket
- En pratique, si vous n’avez pas besoin d’un contrôle extrême ou d’une architecture de bas niveau pour gérer des mises à jour en temps réel, ElectricSQL est recommandé
Pourquoi nous avons choisi le raw long polling
- Le mécanisme de transmission des messages n’est pas un simple détail d’implémentation, c’est un élément central du produit
- Impossible de faire dépendre une fonctionnalité clé d’une bibliothèque tierce, même excellente
- Exigences
- Contrôle du cœur du produit : il fallait contrôler entièrement le mécanisme de transmission des messages. Ce n’est pas de l’infrastructure, c’est le produit lui-même
- Suppression des dépendances externes : minimiser les dépendances externes pour simplifier l’auto-hébergement
- Contrôle bas niveau : contrôler directement le mécanisme de polling et la gestion des connexions
- Contrôle maximal : pouvoir affiner les détails, comme l’implémentation d’intervalles de polling dynamiques
- Simplicité du code : concevoir quelque chose de simple pour que les utilisateurs puissent facilement comprendre et modifier la codebase
- En définitive, le choix d’une implémentation simple de HTTP Long Polling a permis d’obtenir contrôle direct et simplicité
Points d’attention lors de l’implémentation du long polling
- Réglage du TTL : le serveur doit impérativement imposer un TTL maximal, et empêcher qu’un TTL demandé par le client ne le dépasse
- Prise en compte des timeouts d’infrastructure : le TTL doit être suffisamment plus court que les timeouts configurés sur les load balancers, serveurs edge, proxys, etc.
- Intervalle de polling de la base : ajouter un délai d’environ 500 ms permet de réduire la charge sur la base
- Stratégie de backoff (optionnelle) : augmenter progressivement l’intervalle de polling peut permettre d’utiliser plus efficacement les ressources système
Quand envisager WebSocket
- WebSocket n’est pas mauvais en soi, et peut être utile dans d’autres cas
- lorsqu’il faut surveiller de nombreuses connexions avec état et échanger en continu des événements complexes
- lorsqu’on dispose de suffisamment de temps et de ressources pour résoudre les problèmes d’authentification, d’infrastructure et d’observabilité
- Il existe toutefois une complexité réelle : il faut construire soi-même la partie exploitation et logging, la gestion de la reconnexion, les mécanismes d’authentification, etc.
WebSockets : une autre option possible
- Le long polling convenait à nos besoins, mais les WebSockets méritaient tout de même d’être sérieusement envisagés
- Les WebSockets ne sont pas mauvais en soi ; ils demandent simplement beaucoup d’attention et de gestion
- Principaux défis des WebSockets et pistes de résolution
- Visibilité : comme les WebSockets sont basés sur l’état, il faut ajouter des logs et du monitoring pour les connexions persistantes
- Authentification : il faut implémenter un nouveau mécanisme d’authentification pour les connexions WebSocket
- Infrastructure : il faut configurer correctement l’infrastructure — load balancers, pare-feu, etc. — pour prendre en charge WebSocket
- Gestion opérationnelle : gestion des connexions et reconnexions WebSocket, ainsi que des expirations de connexion et des erreurs
- Implémentation client : implémenter une bibliothèque WebSocket côté client, avec gestion de la reconnexion et de l’état incluse
5 commentaires
Dans le serving de modèles de ML, nous utilisons une architecture de « short polling » au sens mentionné ici, et je me demande beaucoup quelle approche serait la plus efficace. D’après ce que j’ai pu voir en me renseignant à droite à gauche, on dit que le short polling est en général plus sûr à cause du coût important de la gestion des reconnexions avec WebSocket ou SSE, donc c’est ce que j’ai choisi au final.. 😭
Le long polling semble être évité parce qu’il donne une impression un peu hacky. Dans le navigateur, on a sans doute l’impression que les requêtes ne se terminent jamais, et il y a parfois des sites dont le chargement ne finit pas ; dans ces cas-là, je me demande si tout le contenu n’a pas pu être chargé, donc je n’aime pas trop ça.
Dans l’application aussi, au final, il faudra bien mettre un
hangquelque part et attendre la réponse, donc ça paraît un peu étrange.« L’agent doit recevoir les mises à jour d’état d’exécution et de chat »
En voyant ça, j’ai tout de suite pensé à SSE, et effectivement, il y a aussi beaucoup de mentions de SSE dans les avis sur Hacker News.
Avis Hacker News
Le long polling a ses propres problèmes
libcurlest utilisé, et des timeouts peuvent se produireC’est un plaisir d’utiliser Phoenix et LiveView au quotidien
Je me demande s’il y a un avantage technique par rapport à l’utilisation des Server-Sent Events (SSE)
Cet article relie "Websocket" et "Long-polling" comme s’il s’agissait de décisions indépendantes
Une manière plus simple d’utiliser
setTimeoutdans Node.jsimport { setTimeout } from "node:timers/promises"; await setTimeout(500);J’aime le long polling, c’est facile à comprendre et, du point de vue du client, cela fonctionne comme une connexion très lente
Les Server-Sent Events ou les WebSockets ne remplacent pas tous les cas d’usage du long polling
Il est préférable d’utiliser la fonctionnalité de notifications asynchrones de Postgres
LISTENsur un canal et PG peut déclencherTRIGGERetNOTIFYlors de changements de donnéesJe ne sais pas si le long polling a encore du sens avec des timeouts courts et des requêtes terminées proprement
Il est rafraîchissant qu’on rappelle une alternative relativement simple aux WebSockets
J’aimerais essayer d’utiliser les WebSockets avec Elixir, le framework Phoenix et LiveView.