14 points par GN⁺ 2025-01-09 | 5 commentaires | Partager sur WhatsApp
  • 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

 
jhj0517 2025-01-10

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.. 😭

 
bbulbum 2025-01-10

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 hang quelque part et attendre la réponse, donc ça paraît un peu étrange.

 
joyfui 2025-01-09

« 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.

 
GN⁺ 2025-01-09
Avis Hacker News
  • Le long polling a ses propres problèmes

    • Second Life utilise un canal de long polling HTTPS entre le client et le serveur
    • Côté client, libcurl est utilisé, et des timeouts peuvent se produire
    • Si le serveur essaie d’envoyer un message entre un timeout et la requête suivante, une condition de course peut se produire et le message peut être perdu
    • Un serveur Apache est placé en frontal pour bloquer les requêtes inutiles, mais des timeouts peuvent se produire
    • Les middleboxes et serveurs proxy peuvent ne pas aimer le long polling
    • Beaucoup d’éléments n’aiment pas garder une connexion HTTP ouverte longtemps
    • Au final, cela devient un canal de messages peu fiable, qui nécessite des numéros de séquence pour détecter les doublons et peut perdre des messages
    • La section du graphique indiquée comme "loop" dans l’article original ne mentionne pas la gestion des timeouts
    • Avec le long polling, il faut envoyer des données toutes les quelques secondes pour maintenir la connexion
  • C’est un plaisir d’utiliser Phoenix et LiveView au quotidien

    • On utilise des WebSockets, donc il n’y a pas à s’en soucier
  • Je me demande s’il y a un avantage technique par rapport à l’utilisation des Server-Sent Events (SSE)

    • Les deux gardent une connexion HTTP ouverte et ont l’avantage de rester du HTTP simple
    • Les SSE semblent mieux adaptés quand on peut diffuser en continu des mises à jour ou des résultats
    • Un cas d’usage approprié pourrait être de surveiller tous les identifiants de tâche pour le compte d’un client donné
  • Cet article relie "Websocket" et "Long-polling" comme s’il s’agissait de décisions indépendantes

    • Un serveur long-polling peut gérer des clients websocket avec un peu de travail supplémentaire
    • Si l’architecture existante est basée sur websocket, prendre en charge des clients long-polling nécessite deux couches de serveurs
  • Une manière plus simple d’utiliser setTimeout dans Node.js

    • Utiliser import { 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

    • Il faut gérer les nouvelles tentatives et suivre les connexions annulées côté client
    • Dans l’exemple de code, la boucle qui interroge les données de façon répétée semble maladroite
  • Les Server-Sent Events ou les WebSockets ne remplacent pas tous les cas d’usage du long polling

    • La limite de connexions des SSE revient souvent comme problème
    • Les WebSockets ne sont pas fiables dans la plupart des environnements
    • Le problème consistant à détecter les changements côté backend et à les propager au bon client reste entier
  • Il est préférable d’utiliser la fonctionnalité de notifications asynchrones de Postgres

    • Le serveur peut faire un LISTEN sur un canal et PG peut déclencher TRIGGER et NOTIFY lors de changements de données
  • Je ne sais pas si le long polling a encore du sens avec des timeouts courts et des requêtes terminées proprement

    • Si HTTP/2 ou QUIC ne sont pas utilisés, cette astuce peut encore avoir du sens
  • Il est rafraîchissant qu’on rappelle une alternative relativement simple aux WebSockets

    • J’ai travaillé dans une startup qui avait choisi les WebSockets, et il était difficile de tester sur les Wi‑Fi d’hôtels et de restaurants
 
luminance 2025-01-10

J’aimerais essayer d’utiliser les WebSockets avec Elixir, le framework Phoenix et LiveView.