Plugin de tchat Peertube : retour rapide sur un test de montée en charge.

, par  John Livingston
[English] [français]

 Introduction

Ce vendredi 30 décembre 2022 l’équipe de Au Poste, le média lancé par David Dufresne, a organisé un test de live Peertube. C’était l’occasion pour Framasoft, Octopuce et moi-même de tester comment se comporte Peertube, et aussi mon plugin de tchat, en cas de montée en charge rapide.

C’est quelque chose que j’attendais depuis longtemps. En effet, en tant que développeur indépendant, il m’est compliqué de réunir plusieurs centaines de personnes pour faire des tests. David Dufresne, lui, a une communauté de plusieurs centaines de personnes qui le suivent sur Twitch. Communauté dont une partie significative cherche, pour des raisons politiques, à s’affranchir des GAFAMs. Ce test grandeur nature était donc très attendu, des « deux côtés de l’écran ».

J’ai donc pour ma part eu quelques échanges avec David et son équipe la semaine précédente. De son coté, il a également échangé avec Octopuce, l’hébergeur de son instance, et Framasoft, qui pilote le projet Peertube.

Petit aparté : à la base je devais me contenter d’être présent dans le tchat, pour répondre aux questions. Mais celui-ci était tellement actif qu’il était difficile d’y suivre la conversation. J’ai donc fini par rejoindre le live en cours de route. Je partagerais le lien vers la rediffusion quand celle-ci sera disponible publiquement. Nous y avons eu plein d’échanges intéressants. Tantôt sur des aspects politiques, tantôt sur des aspects techniques.

 Retour d’expérience

Octopuce a publié un billet de blog très complet, qui analyse en détail ce qui s’est passé du coté des serveurs : Test de charge d’un Peertube en Live avec Auposte.

Du coté du plugin de tchat, nous avons rencontré quelques soucis. Pour certains, je m’y attendais. Pour d’autres, non, pas du tout. Je voudrais revenir rapidement, dans cet article, sur ce qui s’est passé, et expliquer où ça a - d’après moi - coincé. Certains symptômes sont, je pense, un poil plus compliqué que ce qui est expliqué dans l’article d’Octopuce.
Cet article va également être l’occasion d’expliquer les idées que j’ai en tête pour améliorer tout ça (certaines choses étaient déjà prévues, d’autres sont nouvelles).

 La technique

Nous allons maintenant étudier ce qui s’est passé lors de ce test de montée en charge. Je vais détailler les points techniques qui expliquent, selon moi, les métriques observées.

Je pense que les quelques problèmes rencontrés sont dû à une accumulation de petites choses (plus ou moins anticipables), qui au final ont causé quelques problèmes.

 1. L’architecture

L’architecture du plugin de tchat Peertube est un peu spéciale. L’objectif, c’est de pouvoir installer le plugin depuis l’interface web de Peertube, sans aucune config à faire sur le serveur.

Je ne peux donc :

  • ni utiliser de paquet officiel (debian, ...),
  • ni considérer que les ports nécessaires à XMPP sont ouverts sur le serveur
  • ni accéder à une base de donnée (le système de plugin de Peertube ne donne qu’un accès basique à la base de donnée PostgreSQL, il ne me semble pas « propre » d’y ajouter des tables depuis un plugin)

En conséquence, j’ai fait quelques choix qui ne sont pas optimaux et j’en suis conscient.
Et il faut bien noter que j’ai une très longue TODO-list, avec déjà beaucoup de pistes d’améliorations. Ce n’est qu’une question de temps.

Une partie de cette TODO-list est décrite dans cet article 2023 sera riche en nouveautés, qui est disponible en plusieurs langues.

Le reste est réparti dans des tickets sur github. Ces tickets sont également rangés dans un projet github.

Concernant le front-end, j’utilise le logiciel ConverseJS qui permet de se connecter à des serveurs XMPP en Javascript depuis un navigateur web.

Détaillons maintenant ce que les trois contraintes listées plus haut impliquent coté code.

 1.A AppImage

En backend, le plugin de tchat utilise le serveur XMPP Prosody.
Ma contrainte est de pouvoir utiliser Prosody sans rien installer sur le serveur. Sans accès « root ».

J’ai donc créé une AppImage de Prosody basée sur les paquets Debian stable. Cette AppImage contient Lua (le language dans lequel est codé Prosody), Prosody lui-même, et un petit script « launcher » qui permet de lancer soit prosody soit prosodyctl (pour ne pas avoir 2 AppImages différentes).

L’AppImage est créé avec appimage-builder, à partir de cette config : appimage yml

Ensuite, c’est Peertube qui va générer un fichier de conf Prosody (à partir des paramètres du plugin), puis lancer cette AppImage. Elle n’est pas lancée en mode démon, mais en mode « processus enfant ». Comme ça, je suis sûr que si Peertube est stoppé, Prosody aussi.

 1.B Proxy

Vu ma contrainte sur les ports qui pourraient être bloqués par un pare-feu, je dois tout proxyfier par Peertube. C’est moche, mais ça marche « out of the box ».

Comment ça fonctionne ?
J’utilise des connections Websocket ou BOSH, qui passe par 2 reverse proxy successifs :

  • d’abord le nginx qui est devant Peertube
  • puis Peertube lui-même

Peertube, c’est du NodeJS. Donc j’utilise une lib qui permet de « reverse-proxyfier » en NodeJS. Elle n’est pas optimale, et je devrais probablement coder la couche proxy à la main pour améliorer les performances.

NB : je prévois d’avoir des paramètres avancés dans le plugin, qui permettrons d’optimiser la config, notamment en donnant un accès direct au serveur XMPP depuis l’extérieur (mais ça demandera un accès root au serveur pour y configurer le nécessaire).

 1.C Base de donnée

Pas de base de donnée => Prosody utilise le module « storage=internal ». C’est à dire que tout est stocké sous forme de fichiers : les utilisateurs, l’historique des tchats, ...

 2. Virtual Hosts et Composants XMPP

Dans la configuration Prosody générée, on trouve :

  • un virtual host pour les utilisateur⋅rices Peertube connecté. Si j’ai un compte Peertube « @john instance.tld », alors j’aurais un user XMPP « john instance.tld »
  • un virtual host pour les utilisateur⋅rices anonymes. Les personnes qui n’ont pas de compte peuvent suivre le tchat juste en choisissant un pseudo. Dans le test du 30 décembre, nous avions plus de 400 personnes dans cette situation
  • un composant de « salon », pour gérer les salons de discussion

 3. Modules Prosody spécifiques

J’ai des modules Prosody spécifiques. Il y en a plusieurs : ce sont ceux qui ont « livechat » dans leur nom ici :
Modules Prosody du plugin de tchat.

On y trouve :

  • un module qui permet de lister les salons (pour les afficher dans un outils de modération)
  • un module de test, juste pour vérifier que Peertube arrive à communiquer avec Prosody, et inversement. C’est utilisé dans un outil de diagnostic inclus dans le plugin.
  • deux modules de vcards : un pour récupérer les avatars Peertube des personnes connectées, un pour générer un avatar aléatoire pour les users anonymes

J’utilise aussi des modules existants :

  • mod_muc_http_default pour vérifier que les rooms ont le droit d’exister, et charger leurs paramètres par défaut
  • mod_auth_http pour authentifier les users Peertube connectés

Tous ces modules peuvent être amenés à faire des requêtes d’API web vers Peertube.

Et c’est donc là qu’on trouve une partie de ce qui explique les anomalies remontées par Octopuce : à cause d’un manque dans le système de plugin peertube, quand Prosody doit appeler une API Peertube, je suis obligé de repasser par nginx... Un ticket est ouvert, il faut maintenant attendre qu’une solution d’implémentation soit choisie et réalisée.
Note : l’implémentation vient d’être faite, il n’y a plus qu’à attendre la prochaine version de Peertube.

En attendant, ça peut s’optimiser en configurant à la main un settings avancé du plugin (mais c’est mal documenté pour le moment, je le reconnais).

 4. Les vCards

Un des problèmes qui avait déjà été identifié, c’est que j’ai activé l’utilisation des vCards dans ConverseJS. Les vCards sont des « cartes de visites » pour les utilisateur⋅rices. Je m’en sers pour récupérer les avatars.

Donc, dès qu’une personne se connecte... Le front-end ConverseJS va demander les vCards de toutes les autres personnes déjà connectée. Et quand une nouvelle personne rejoint, les personnes déjà présentes vont demander la vCard de cette nouvelle personne.

Pour les vCards des personnes connectées à Peertube, ça va aller chercher l’avatar sur Peertube (mais dans le test de la semaine dernière, il n’y avait que 4 ou 5 personnes avec des comptes Peertube, c’est donc un cas marginal ici).

Pour les vCards des anonymes, ça va choisir un avatar aléatoire (dans une liste de quelques dizaines) et le remonter.
Le code qui fait ceci est ici : mod_vcard_peertubelivechat.
Je n’avais jamais fait de LUA avant, donc il y a peut être des maladresses dans mon code.

La semaine dernière, on a 400 personnes qui sont arrivées en même temps...
Donc... 400 personnes qui demandent 400 vCards....
Donc... ça ramait... Donc des personnes ont raffraichi la page en boucle au lieu d’attendre 5 ou 6 secondes que le tchat s’affiche... Donc...

Puis chaque avatar fait environ 4.2Ko... (format JPG).
Ce JPEG va être encodé en Base64 avant d’être envoyé via le protocole XMPP. On arrive alors à un peu plus de 6Ko par avatar.
400 * 400 * 6Ko = 960Mo, d’un coup. Sans compter l’enveloppe XMPP et les autres données des vCards.

Et c’est sans compter la charge CPU et disque sur Prosody, qui était apparemment à 100%. Je pense que les vCard en étaient la cause (comme indiqué dans l’article d’Octopuce, Prosody est mono-processus).

Donc forcément, timeout... donc personnes qui raffraichissent en boucle.... donc...

J’ai dans ma TODO-list le fait calculer les avatars en front-end, et d’arrêter de demander les vCards. Ça devrait grandement aider.
J’hésite même à complètement désactiver les avatars, pour les remplacer par une couleur de pseudo. Cf XEP-0392.

 5. Historique du tchat, prune, ...

Jusqu’au test de vendredi, je pensais que tout se passerais bien niveau « taille de l’historique » des salons.
Je n’ai donc pas chercher à optimiser, et j’ai paramétré ConverseJS pour qu’il remonte l’historique quand on rejoint le tchat.

Bon bah... 400 personnes d’un coup, X messages * 400 à remonter. Tout ça étant stocké dans des fichiers (cf storage=internal dans la conf Prosody), bah... Ça ne pouvait pas bien se passer.

Autre problème, complètement inattendu celui-là : je ne « prune » (nettoie) pas les messages dans ConverseJS.
Donc au bout de quelques centaines de messages, le JS du navigateur se met à ramer sévère. Ça lag à chaque pression de touche.

Donc... Des gens ont rafraichis la page un peu tout le temps pendant le live. Et donc... Cf points ci-dessus.

Je vais donc changer tout ça très prochainement.

 6. Charge réseau dû au téléchargement de ConverseJS

La façon dont j’ai intégré ConverseJS dans le plugin fait que :

  • ConverseJS entier est inclus, même les modules dont je n’ai pas besoin
  • ça passe par nginx, puis NodeJS, pour charger les fichiers.

Donc à chaque fois qu’une personne a ouvert la page, ou raffraichi... Ça a téléchargé plusieurs Mo en passant par Nginx puis NodeJS. Ça doit expliquer une autre partie du trafic réseau démesuré relevé par Octopuce.

À noter que j’ai déjà commencé à changer tout ça. Si tout va bien, dans la prochaine version du plugin, ConverseJS sera optimisé, et chargé plus efficacement.

 7. Join/Quit et activité

Truc très con... J’ai activé l’affichage des messages de join/quit dans les salons (« Juliette a rejoins le salon »), ainsi que les messages d’activité (« Patrice est entrain d’écire »)... 400 personnes qui join/quit plusieurs fois, et qui floodent pour tester... Bah là aussi, ça en fait du trafic (et du JS qui rame).

Je vais retirer ça. Peut être juste laisser les join/quit pour les modérateurs. Mais les messages « Jack est entrain d’écrire » n’ont aucun intérêt, si ce n’est générer des centaines de messages XMPP pour rien.

 8. Changements de pseudos

Pour que les personnes sans compte Peertube (ici 400 personnes) puissent voir le tchat en live, avant même de choisir un pseudo, je leur assigne un pseudo aléatoire de la forme « Anonymous 123456 ».

Pour pouvoir chatter, iels doivent d’abord choisir un pseudonyme... Engendrant donc des centaines de changements de pseudo (et autant de messages XMPP).
Et ConverseJS qui va afficher « Anonymous_123456 devient Camille » à chaque fois...
Beaucoup de trafic, et beaucoup de JavaScript qui tourne pour pas grand chose.

Je vais probablement masquer les messages de ce type. Au moins quand le pseudo de départ est « Anonymous_123456 ». Je réfléchi d’ailleurs aussi à retirer celles et ceux qui n’ont pas encore choisi de pseudo de la liste des connecté⋅es, pour n’y laisser qu’un compteur « X anonymes ». Ça ne pourra qu’alléger la charge dans le navigateur web.

 Conclusions

Comme indiqué dans le titre, cet article est un « retour rapide » (enfin, rapide...). J’ai noté ici ce qui me venait spontanément à l’esprit durant sa rédaction. Il est possible que j’ai oublié d’autres choses. Quoi qu’il en soit, j’avais tout noté et rangé sur github (en anglais).

J’ai encore beaucoup d’axes d’amélioration pour ce plugin. Ne reste plus qu’à s’y mettre !

 Remerciements

Je voudrais en profiter pour remercier la communauté XMPP. Plusieurs personnes travaillant sur Prosody, ConverseJS, les XEP, etc. observent mon travail, et proposent régulièrement leur aide.
Merci à vous pour votre aide, cela me conforte dans le choix de technologie que j’ai fait en commençant ce projet.

Merci aussi à David, son équipe, Octopuce, et Framasoft. C’était un chouette live, et riche en enseignements.