Nous avons récemment eu à repenser une application Node.js de timeline temps réel, basée sur les WebSockets afin de tenir une charge plus élevée.
L’application timeline
Fonctionnellement, l’application timeline est relativement simple: elle consiste à afficher un flux de message publiés par des contributeurs en temps réel pour les internautes présent sur la page. Pour cela l’application se base sur socket.io pour la partie websocket, et supporte à peu près 15 000 connexions simultanées.
Afin d’augmenter la capacité de l’application, nous avons décidé de la rendre scalable horizontalement. C’est dire, répartir la charge sur un nombre X de serveurs communiquant entre eux, par exemple, par le biais de Redis.
Pour cela socket.io propose un store redis qui permet aux différentes instances de communiquer entre elles. Malheureusement les performances de ce store sont plutôt désastreuses car le store que propose socket.io est beaucoup trop verbeux et écrit absolument tous les évènements que reçoit un serveur sur un seul channel redis. L’application devenait inutilisable autour de 8 000 connexions. Il était donc inenvisageable de l’utiliser en production.
Nous avons donc décidé rapidement de passer une autre solution que socket.io. Après pas mal de recherche nous avons fait notre choix sur Faye, une implémentation du protocole de Bayeux, bien documenté et proposant aussi d’utiliser redis comme “store”. Après test, cette solution s’est révélée bien plus performante que socket.io.
Tests de charge
Une des problématiques rapidement rencontrée sur ce projet a été de tester la charge de notre application: comment simuler 15 000 connexions simultanées ?
En faisant le tour des solutions de benchmark de websocket (thor, …) ,nous n’avons pas trouvé la solution qui nous permettait de faire les tests que nous souhaitions. Siege, ab ne le propose pas encore,Gatling, Jmeter, Tsung ont des plugins web-socket mais l’utilisation et le reporting ne sont pas des plus clair.
La solution ?
Websocket-bench
Nous avons donc décidé de développer notre propre outil de benchmark de websocket (Socket.io ou Faye), au nom très original : websocket-bench.
Cet outil se base sur les clients Node que proposent Faye et Socket.io. Il peut être facilement étendu à l’aide de “generator” (module Node), afin de rajouter la logique de votre application. Par exemple dans le cas de notre application, en se connectant, un client doit envoyer un message au serveur pour valider la connexion.
Ci dessous un exemple de générateur qu’on a pu utiliser lors de nos tests de charge.
Cet outil, lancé sur des instances Amazon, nous a permis d’exécuter nos tests de charge.
Un exemple : la commande ci dessous va lancer 25 000 connexions, à raison de 1000 connexions par seconde en utilisant le generateur “generator.js” :
Nombre de clients connectés sur Graphite
Au delà de 25 000 connexions, l’instance Amazon (large) qui lançait les tests ne tenait plus. Une solution pour tester un nombre plus élevés de connexions serait d’utiliser plusieurs machine de tests, peut être à l’aide de bees with machin guns et ainsi d’utiliser plusieurs instances pour lancer les tirs de charge.
Bonnes pratique de test de charge
Lors de votre test de charge (et pour la prod), n’oubliez pas d’augmenter le nombre maximal de descripteurs de fichiers coté client ET coté injecteur (ulimit -n 256000 par exemple dans la conf de supervisor, et dans le terminal avant de lancer le benchmark).
Surveillez votre conntrack (si firewall iptables), augmentez votre plage locale de port, et si vous êtes amenés à tester plus de 25K connexions, utilisez plusieurs machines et/ou plusieurs IP sources différentes.
Comment contribuer au projet ?
N’hésitez pas à remonter d’éventuels bug via les issues ou à contribuer au projet l’aide de pull request github (https://github.com/BedrockStreaming/websocket-bench)