Movim, mode d’emploi — Première partie : l’architecture

86
13
août
2018
XMPP

Je travaille maintenant depuis presque dix ans sur Movim et je n’ai jamais réellement eu l’opportunité d’expliquer comment le projet fonctionne. Prenons un peu le temps de mettre tout ça par écrit !

Cette présentation sera divisée en plusieurs articles. Je vais commencer par présenter le projet de façon globale avant d’aller progressivement vers son fonctionnement interne et ses détails techniques.

Sommaire

J’utilise les plates‐formes sociales depuis pas mal d’années maintenant et je suis toujours surpris de voir que malgré les milliards de dollars investis par les géants du Web elles fonctionnent toutes plus ou moins selon la même architecture.

Même avec toutes les technologies qui sont apparues ces dernières années les choses se présentent toujours plus ou moins de la même façon : vous publiez du contenu via des requêtes AJAX (ou au travers d’un Websocket), c’est sauvegardé dans une base de données et vos contacts vont périodiquement envoyer une requête au serveur pour voir si il y a quelque chose de nouveau à afficher.

C’est au final assez facile à construire et si vous souhaitez le faire vous‐même il existe de nombreux cadriciels qui offrent tous les outils nécessaires pour réaliser cette architecture aisément (typiquement Laravel, Ruby on Rails, Django, Symfony ou Zend, connectés à une base de données SQL, une API REST, une jolie interface et hop vous avez un réseau social).

Mais pourquoi donc devoir attendre pour récupérer les publications des autres contacts ?

Nous avons depuis des décennies maintenant des technologies de messagerie instantanée qui nous permettent d’envoyer, en temps réel, tout autour du globe, du contenu sans le moindre souci. Pourquoi ne pouvons‐nous pas faire de même avec les réseaux sociaux ?

Et c’est ici que réside l’idée de base du projet Movim.

Créons un réseau social temps réel

Donc, l’une des erreurs à éviter quand le projet a démarré, c’était de réinventer la roue. Vous verrez que Movim est principalement composé de technologies basiques et déjà éprouvées.

Pour fabriquer un réseau social fonctionnant en temps réel, il me fallait transférer du contenu instantanément sur le réseau. Il devait donc être transporté au sein d’un système « connecté » et donc construit sur des sockets (contrairement aux systèmes construits sur des requêtes).

Cela exclut déjà toutes les récentes technologies sociales standardisées par le W3C (bye bye ActivityPub et WebSub), ces dernières étant construites sur HTTP. Loin de moi l’idée de remettre en question ces standards, ce sont des solutions sérieuses et parfaitement valides, mais pas pour les besoins de Movim.

Je cherchais donc un protocole qui soit :

  • temps réel ;
  • standard (avec des RFC sérieuses) ;
  • et si possible déjà éprouvé et largement déployé.

Cela nous amène donc à XMPP (je ne vais pas expliquer en détail comment le protocole est construit, vous pouvez trouver plus d’informations sur la page Wikipedia et sur le site officiel de la fondation).

Logo de XMPP

Dans la pratique, XMPP nous offre quelques précieux avantages :

  • c’est un protocole temps réel (super !), les données sont échangées dans le format XML (oui, je vous vois déjà venir, vous les défenseurs du JSON ! Mais gardons de côté ce différend pour plus tard ;)) sous forme de paquets (appelés « stanzas » dans le cas présent) envoyés en TCP et chiffrés avec TLS ;
  • XMPP offre un cadriciel très simple et générique qui comprend de (très !) nombreuses extensions que l’on peut combiner afin de développer la solution désirée (voici pour l’exemple celles utilisées pour construire Movim) ; et parce que XMPP est basé sur XML, il est très facile de l’étendre au moyen d’espaces de noms (namespaces) existants : prenez Atom, ajoutez‐y Pubsub et voilà, vous obtenez un système de publication de contenu complet, temps réel, pour vos articles avec pièces jointes, bien défini et tout à fait spécifié ;
  • vous pouvez faire bien plus que de simples choses « sociales » avec XMPP ; pas besoin de composer avec dix autres protocoles, XMPP offre déjà tout pour faire du clavardage et des salons de discussion, de la vidéoconférence, des solutions de publication-abonnement et beaucoup d’autres choses. Et cela offre l’avantage de conserver un code assez concis puisqu’il n’y a qu’un unique protocole dans le back‐end ;
  • XMPP est fédéré et utilise un réseau un peu similaire à celui du courrier électronique ; les comptes sont créés sur les serveurs (c’est pourquoi les identifiants sont également similaires : nomutilisateur@serveur.tld), les clients se connectent ensuite à ces serveurs XMPP ; en plus de ça, vous pouvez avoir plusieurs clients connectés, en même temps, à votre compte et ils seront tous synchronisés en temps réel :) ;
  • il y a déjà une grande communauté, avec des serveurs sérieux qui peuvent gérer des millions de connexions simultanément sans aucun problème (ejabberd <3).

C’est aussi un gros avantage pour Movim. Je n’ai pas à m’occuper de tous les problèmes de réseau. C’est « juste » un client simple et stupide qui se connecte aux serveurs XMPP, en obtient du contenu et leur en envoie.

Si vous comparez cette solution avec d’autres solutions de réseau fédéré telles que Mastodon ou Diaspora, il y a là une grande différence : pour Movim, les comptes sont sur un serveur distinct. Je n’ai donc pas besoin de créer une autre API pour communiquer avec Movim. Tout le monde peut échanger avec Movim en implémentant simplement XMPP (et il y a déjà beaucoup de bibliothèques et de solutions dans la nature pour le faire).

OK, nous avons choisi le protocole, maintenant nous devons construire le back‐end.

Un peu d’histoire

J’ai créé Movim en 2008 afin d’apprendre à programmer et essayer de construire par la même occasion une plate‐forme sociale sur laquelle j’aimerais échanger avec mes proches. Ayant choisi d’apprendre le langage PHP par la même occasion j’ai tout naturellement choisi celui‐ci pour commencer les travaux.

Avec les connaissances acquises au cours de mes études, l’aide d’un ami (Etenil, si tu me lis…) et de ce que j’ai pu apprendre de façon autodidacte j’ai progressivement amélioré Movim et rajouté les nombreuses fonctionnalités qui me paraissaient intéressantes pour construire une plate‐forme sociale digne de ce nom.

Puis au cours de l’été 2014 j’ai décidé d’entièrement réécrire le cœur de Movim afin d’en faire une plate‐forme entièrement temps réel. En effet, jusqu’ici le projet était construit sur une « émulation » de temps réel construite au dessus de HTTP via les connections XMPP (en utilisant l’extension BOSH).

Ces changements apportèrent également une importante réécriture de l’architecture interne et d’une refonte de l’interface utilisateur.

À cette période un nouveau cadriciel commençait à faire parler de lui au sein de la communauté. ReactPHP permettait en effet de construire très facilement des architectures entièrement en temps réel en PHP et offrait tout ce que je recherchais pour cette réécriture.

Un démon, quelques tuyaux et voilà !

ReactPHP est un ensemble d’outils s’interconnectant entre eux et permettant de créer toutes sortes d’architectures fonctionnant entièrement en temps réel.

ReactPHP is handling most of the core features of Movim

J’utilise à ce jour les modules suivants :

  • react/event-loop, le cœur de React, s’occupant de la gestion de toutes les entrées‐sorties ;
  • react/dns, un résolveur DNS asynchrone ;
  • react/promise-timer, pour exécuter périodiquement des évènements ;
  • react/socket, pour se connecter à XMPP en utilisant des sockets TCP (avec une couche de chiffrement TLS par dessus) ;
  • react/child-process, pour lancer et gérer des sous‐processus ;
  • react/stream, afin de connecter toutes ces choses ensemble ;
  • react/zmq, une bibliothèque ZeroMQ pour pouvoir gérer efficacement les communications au sein de Movim.

Il m’a fallu ensuite quelques mois de recherche avant de trouver une architecture stable, qui n’a que peu changé par la suite.

Rentrons un peu dans les détails.

Toute la structure de Movim est prise en charge par un démon central (qui s’appelle daemon.php, c’est fou !). Ce démon s’occupe de tous les WebSockets des navigateurs des utilisateurs (mobiles et bureau) et lance un sous‐processus pour chaque utilisateur connecté. Finalement, il se comporte comme un routeur tout bête qui transfère les messages entre les WebSockets des utilisateurs et leurs processus respectifs

Cette architecture offre plusieurs avantages :

  • les sessions utilisateur sont isolées et n’influent pas sur les performances les unes des autres ;
  • elles peuvent être contrôlées plus facilement (tuer une session ne supprimera pas les autres) ;
  • le démon principal est minimal (il agit comme un simple routeur).

Cela présente néanmoins un inconvénient : la consommation de mémoire est supérieure. Le code est chargé plusieurs fois entre les différents sous‐processus. Il faut compter environ 10 à 20 Mio par utilisateur connecté. La situation s’est bien améliorée depuis les dernières versions puisque la consommation mémoire de Movim lui‐même a été réduite en retirant des dépendances et en mettant à jour PHP (PHP 7.0+). La consommation mémoire reste un point d’amélioration pour les versions à venir puisqu’elle pourrait devenir un goulot d’étranglement. Le cadriciel peut facilement supporter des milliers de connexions simultanées mais la mémoire vive manquera bien avant d’y arriver.

Chacun des ces sous processus se connecte au serveur XMPP de l’utilisateur puis prend en charge toutes ses communications. Il se connecte aussi à la base de données SQL commune qui fait office de cache pour chaque compte et permet l’échange de données entre eux (via la découverte de ressources publiques par exemple)

The Movim simplified architecture

Pour finir, ces processus gèrent aussi tout ce qui est relatif au frontal, mais cette partie sera expliquée plus en détail dans un article à venir.

Optimisations

Certaines optimisations ont été faites pour améliorer les performances globales de cette architecture. En voici les trois principales, celles qui je pense, ont eu le plus grand impact sur le projet.

Analyseur de flux XML

Une connexion XMPP peut être ramenée à un flux XML bidirectionnel. Le client envoie des requêtes XML (appelées stanzas) et analyse (d’un point de vue lexical) les requêtes entrantes. Le point important ici est que les requêtes entrantes font partie d’un seul et même « document » XML. À l’origine, Movim détectait chacune de ces stanzas et les analysait séparément.

L’analyseur a donc été remanié pour travailler en flux (voir analyseur syntaxique XML). Cela permet à Movim de préparer les stanzas entrantes et de lancer les événements qui leur sont relatifs dès que les premières balises XML sont reçus dans le socket. Le petit changement a vraiment amélioré les performances XMPP globales du projet. En particulier pendant la phase de connexion puisque Movim peut maintenant traiter plusieurs milliers de stanzas en quelques secondes.

Hello ZeroMQ !

Les communications entre le démon principal et les sous‐processus (qu’on appelle « linkers » dans Movim) sont à l’origine de simples stdin/stout. Cela a créé des problèmes de tampons et de performance et j’ai finalement choisi d’utiliser un outil spécifique à la tâche : ZeroMQ.

À chaque fois que le démon principal invoque un linker pour un utilisateur, il crée deux flux IPC dédiés (un pour les messages entrants et un pour les messages sortants) puis gère tout ce qui les traverse.

Cela m’a permis d’éviter la création de certains tampons dédiés au transit de messages et ainsi d’améliorer les performances globales, en particulier pour la partie interface utilisateur.

ZeroMQ est aussi très léger et est déjà disponible et empaqueté sur la majeure partie des distributions GNU/Linux.

De Modl à Eloquent

Movim dépendait au début d’un ORM spécifiquement créé pour le projet (Modl). La version qui arrive (0.14) utilisera la bibliothèque reconnue Eloquent (utilisée par le cadriciel Laravel).

Ce changement m’a permis d’utiliser des fonctionnalités comme le eager loading et le lazy loading, mais aussi d’écrire des migrations proprement et d’optimiser certaines requêtes.

Plus d’informations dans l’article dédié.

Ce que j’ai appris

Après toutes ces années à travailler sur ce project temps réel, je peux maintenant tirer certaines conclusions sur les choix et changements que j’ai réalisés :

  • PHP n’est pas un problème la plupart du temps : PHP est rapide, vraiment rapide ; la plupart des optimisations que j’ai obtenues étaient liées à la façon dont je gérais les flux et leur contenu ainsi qu’aux requêtes dans la BDD ; passer à PHP 7 puis aux versions suivantes a un peu amélioré les performances, mais le gain a été négligeable par rapport à ce qui a découlé des autres changements réalisés dans le code ;
  • chercher le goulot d’étranglement : quand on travaille en temps réel, même si certaines parties sont gérées par des promesses et autres systèmes asynchrones, il y a toujours du code synchrone et donc bloquant ; il faut s’assurer que ce code n’est pas « trop lent ». Dans Movim, par exemple, les requêtes BDD sont toujours considérées comme bloquantes (c’est une autre optimisation qui pourrait être apportée…) ce qui fait qu’une requête qui prend 200 ms à être déclenchée va retarder l’exécution du reste du code de 200 ms ;
  • tester en « conditions réelles » : je pensais que Movim était rapide jusqu’au moment où j’ai vu des utilisateurs galérer avec (Nik, si tu me lis…) ; certains utilisateurs de Movim avaient beaucoup plus de salons de discussion et avaient souscrit à beaucoup plus de flux que prévu, ce qui a créé de gros ralentissements, en particulier pendant la phase de connexion ; grâce à quelques optimisations bien pensées (et parfois très simples), les choses sont revenues à la normale ;
  • et peut‐être la plus importante de toutes : Keep It Simple!

J’ai l’impression que beaucoup de projets accordent trop d’importance au principe DRY — Don’t Repeat Yourself). Parfois il n’est pas nécessaire d’importer une bibliothèque entière, écrire une fonction qui correspond exactement à la situation peut faire l’affaire. Il est préférable de garder aussi peu de dépendances que possible.

Questionnez‐vous sur ce dont votre projet a besoin pour fonctionner. Est‐ce que tout est vraiment indispensable ?

Et pour finir, n’ayez pas peur d’un grand ménage de printemps de temps en temps (j’ai passé 50 heures à remplacer Modl par Eloquent) pour simplifier et nettoyer votre code si nécessaire.

Du coup, Movim est rapide ?

Movim est rapide. Dans certains cas, Movim est même plus rapide que certains clients XMPP natifs comme Pidgin ou Gajim. Movim est aussi plus rapide que certaines autres plates‐formes de discussion en ligne, de par son back‐end, mais aussi grâce à la façon dont son frontal est mis en œuvre. J’en reparlerai dans un prochain article.

Sur mon compte (400 contacts, 50 salons de discussion) s’authentifier et obtenir une interface réactive ne prend qu’une poignée de secondes, surtout sachant que les données viennent d’un serveur tiers (votre serveur XMPP) et sont re‐synchronisées pour certaines lorsque vous vous connectez.
Si vous voulez voir le résultat par vous‐même, faites un tour sur le site officiel. ;)

That’s all folks!

Aller plus loin

  • # Retour utilisatrice

    Posté par (page perso) . Évalué à 10.

    J'ai commencé à utiliser Movim un peu par hasard, parce que j'avais besoin de communiquer sur un projet et après avoir fait les médias sociaux classiques, on m'a dit "va voir Movim, c'est cool".

    Et c'est effectivement très cool. Pour moi l'une des grandes réussite de ce logiciel c'est son visuel : c'est beau, c'est propre, c'est attractif ; on ne cherche pas trop lors de la première prise en main pour trouver comment poster un message, paramétrer un peu son compte, et le fait que ce soit lié à XMPP est transparent ; pour tout dire il m'a fallu quelques semaines avant de percuter que mon identifiant Movim pouvait me servir sur XMPP (ou que j'aurais pu me servir de mon ancien compte XMPP sur movim… à ce moment de ma vie, XMPP était un truc très vague pour moi).

    J'apprécie bien le fait que ça propose aussi une façon différente de gérer son réseau : ce n'est pas un facebook-like, il y a des spécificités movim et c'est intéressant. C'est plus adapté à un vrai réseau (comprendre : échanger avec ses contacts) qu'à du "média" (c'est à dire procrastiner en suivant des flux d'informations) : contrairement à Diaspora, je ne fais pas vraiment de découvertes de nouvelles sources via Movim. Ce qui remet un peu en cause mon objectif initial puisque la com pour mon projet se réduit sur Movim à mes contacts et que je touche moins d'inconnus que si je fais la même communication sur Diaspora, tout en étant très bien adapté à la communication vers un public déjà impliqué dans le projet en question.

    Ça donne vraiment du sens à toutes les possibilités de XMPP, en les enrobant dans un joli paquet. Maintenant que je commence à avoir plus de contacts sur XMPP, c'est fort sympathique.

    Merci pour ce beau travail :)

  • # Beau projet

    Posté par (page perso) . Évalué à 2. Dernière modification le 14/08/18 à 16:32.

    c'est une excellente introduction à ton projet, qui je l'espère, lui fera prendre encore davantage la lumière.
    C'est toujours intéressant de connaitre les coulisses. A titre perso, je n'ai jamais réussi à me connecter sur movim (j'ignore pourquoi) mais c'est un projet que j'adore ne serait-ce que pour la promotion du protocole XMPP :)

    Grand merci à toi.

    • [^] # Re: Beau projet

      Posté par (page perso) . Évalué à 2. Dernière modification le 14/08/18 à 20:46.

      Même chose pour moi je n'ai jamais réussi à me connecter à Movim avec mon compte XMPP. Existe-t-il une documentation avec quelques pistes sur ce qui pourrait causer ça ?

  • # Un peu de pinaillage

    Posté par (page perso) . Évalué à 4.

    Très bonne dépêche. J'attends la suite avec impatience.

    PHP n’est pas un problème la plupart du temps : PHP est rapide, vraiment rapide ; la plupart des optimisations que j’ai obtenues étaient liées à la façon dont je gérais les flux et leur contenu ainsi qu’aux requêtes dans la BDD ; passer à PHP 7 puis aux versions suivantes a un peu amélioré les performances, mais le gain a été négligeable par rapport à ce qui a découlé des autres changements réalisés dans le code.

    Du coup je dirai plutôt "PHP est lent, mais ce n'est pas forcément un problème/visible" (et c'est pareil pour Perl/Python/Ruby…). Même si un bout de code pourrait être potentiellement 100 fois plus rapide, ce n'est pas gênant s'il s'exécute "instantanément". Comme tu le dis c'est surtout l'architecture qui va être prégnante dans bien des cas (comme ici).

    J’ai l’impression que beaucoup de projets accordent trop d’importance au principe DRY — Don’t Repeat Yourself). Parfois il n’est pas nécessaire d’importer une bibliothèque entière, écrire une fonction qui correspond exactement à la situation peut faire l’affaire. Il est préférable de garder aussi peu de dépendances que possible.

    Le fait de choisir de se lier à une bibliothèque ou d'écrire une fonction spécifique n'a rien à voir avec le "Don’t Repeat Yourself" (qui va consister à trouver et factoriser les motifs récurrent de ton code). Tu parles peut-être de "Not Invented Here" ?

    et peut‐être la plus importante de toutes : Keep It Simple!

    Ça c'est une réflexion personnelle ; une chose me dérange avec l'expression KISS, c'est que c'est très subjectif. Des tas de gens s'en réclament, tout en lui faisant dire des choses très différentes. C'est un peu comme se réclamer d'utiliser "le bon sens" ; au final tout le monde pense avoir le bon, et pourtant tout le monde ne pense pas pareil.

    Par exemple, tu n'as pas mis en place l'architecture la plus simple possible (ce qui selon une certaine interprétation du KISS serait une violation du principe) ; tu as créé une architecture suffisamment complexe pour que ça marche bien en fonction des critères qui te semblaient importants (performances, sécurité…). Après on est d'accord, avoir un code le plus simple possible pour un niveau de fonctionnalité donné est clairement une vertu.

  • # Go ?

    Posté par (page perso) . Évalué à -6.

    Golang ne serait pas plus rapide pour cette application ?

    • [^] # Re: Go ?

      Posté par (page perso) . Évalué à 4.

      Probablement, mais comme plein d'autres langages compilés. Après, tous ont leur spécificité. Personnellement, je ne vois que peu d'intérêt à Go comparé au C/C++. D'autres, comme Rust, apportent de "nouveaux" (relativement) concepts qui garantissent à niveau similaire de performances un bon déroulement du code dès lors qu'il compile (gestion obligatoire des exceptions, gestion de mémoire "safe").

      Pour un projet comme Movim gérant beaucoup de processus et de flux, ne pas perdre de la mémoire à droite à gauche est un plus. Pouvoir garantir cela sans besoin de garbage collector (ramasse-miètes), c'est clairement un avantage de Rust sur Go.

      Mon objectif ici n'est pas de faire la publicité d'une technologie promise ; il n'y a pas de solution miracle. Mais quitte à recommander de réécrire un projet dans un autre langage, on peut y chercher plus que de la rapidité brute :)

  • # PHP n’est pas un problème la plupart du temps

    Posté par (page perso) . Évalué à 2.

    Personnellement, je pense que le problème n'est pas/plus que PHP est lent ou si mal foutu que ça. Le PHP moderne (namespaces, traits…) a apporté plein de bonnes choses (notamment à travers les PSR) qui font de PHP un langage excellent pour gérer pas mal de problèmes dans un environnement web limité (juste PHP à disposition).

    Pourtant, même si j'adore PHP il y a des trucs qui me rebutent de plus en plus, notamment le système de types. Le typage n'est pas strict par défaut et le typage des paramètres de fonctions n'est pas obligatoire. Ça faciliterait trop le debugging :-/

  • # Réseau social d'entreprise

    Posté par . Évalué à 5.

    Bonjour,

    Je garde un œil sur movim depuis quelques temps, sachant que je devrai sans soute mettre en place un tel serveur un jour ou l'autre (sans doute en fin d'année pour déploiement 2019) dans la collectivité pour laquelle je travaille…

    Quel serveur XMPP préconises-tu ? quelle config serveur pour ~ 1000 utilisateurs inscrits, sans doute 400 à 500 actifs ?

    J'ai installé un moment OpenFire, mais ejabberd me fait de l’œil aussi…

    Courage et super travail !

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.