Facebook et le graphe social : LAMP et Memcache

Facebook… En voilà une architecture qui fait rêver et qui laisse songeur au vue du volume de connexions simultanées et de données stockées. J’ai récemment regardé une vidéo très intéressante d’une conférence données par Aditya Agarwal (Director of Engineering chez Facebook) durant le QCon SF 2008 (San Fransisco) sur l’architecture de Facebook et plus exactement la couche logicielle utilisée, basée sur le modèle LAMP (Linux, Apache, MySQL, PHP). C’est essentiellement de MySQL et PHP que la conférence traite sur ce modèle. Une belle part est également faite à Memcache. Memcache, cache mémoire réseau qui n’est plus à présenter, et qui a été optimisé par les développeurs de Facebook pour l’occasion. Une brève présentation d’outils « maison » tels que Thrift, Scribe, … est également effectuée en fin de conférence.

Vous trouverez cette vidéo en suivant ce lien : Facebook: Science and the Social Graph.

La conférence est en anglais et dure 1h00. Je vous conseille de prendre ce temps car les sujets abordés sont forts intéressants.

Je me propose de faire un bref récapitulatif des points de l’architecture Facebook qui m’ont semblé importants lors de cette présentation de Aditya Agarwal et d’en tirer les éléments que nous pouvons appliquer dans nos architectures, même à moindre échelle.

Rappel de la problématique du graphe social
La difficulté de l’exploitation d’un graphe social est que la majeure partie des données sont actives en permanence car chaque utilisiteur peut consulter les données d’un autre, même si celui-ci n’est pas connecté. De plus, un utilisateur a en général un nombre de « connexions » ou relations non négligeable. Ce qui implique de devoir gérer un volume de données conséquent et accédé en permanence et de mettre à jour toutes ces informations afin que chacun puisse accéder à ses informations et celles de ses relations en « temps réel ». Cela implique des problématiques telles que la difficulté de partitionner les données du fait que « tout le monde est connecté », de conserver un maximum d’informations en cache mémoire (RAM) pour accélérer les requêtes, … Et je ne parle pas du traitement de tous les accès concurrents !

MySQL ou « comment stocker un graphe social ? »
Tout d’abord MySQL est utilisé comme une base clé – valeur. Les données sont distribuées massivement et de manière aléatoire sur un large éventail d’instances logiques qui elles-mêmes sont réparties sur un ensemble de noeuds physiques. Chaque donnée possède un « global ID » (identifiant unique) par lequel elle est accédée. Il n’y a pas de JOIN (jointure SQL) au niveau des requêtes du fait de la distribution des données. Outre le fait que la majeure partie des données est distribuée (et donc qu’aucun lien logique ne les rapproche), « quelques » méta données sont stockées sur une logique « par utilisateur » et sont rassemblées sur un même serveur (ou ensemble de serveurs mais regroupées selon des critères logiques, donc permettant d’effectuer des jointures) : ce sont les méta informations des utilisateurs telles que le profile, … Meta informations que l’on retrouve en cache (Memcache), mais nous verrons cela après.

Il est à noter également que les bases sont optimisées afin d’accéder plus rapidement aux données récentes ou fréquemment accédées tandis que les plus anciennes sont « archivées » (mais toujours disponibles). Il s’agit du partitionnement ou sharding vertical que j’ai décrit dans l’article Sharding et optimisation des accès aux données.

Il est à noter que le moteur de base de données MySQL utilisé par Facebook a subi quelques modifications (mais peu, notamment en comparaison de Memcache), notamment afin de faire de la réplication entre plusieurs data-center et de gérer au mieux la consistence des données. On entend à nouveau parler de la consistence éventuelle des données… A ce propos, je vous invite à lire cet article Eventually Consistent – Revisited de Werner Vogels (CTO – Amazon.com) sur son weblog « All Things Distributed ». Il explique notamment que la consistence éventuelle, c’est le fait que pour construire des systèmes fiables (comprendre disponibles – haute disponibilité) distribués à une échelle mondiale, cela demande un compromis entre consistance (intégrité des données à un instant « t » entre plusieurs bases) et disponibilité.

On note également que les objects sont faiblement typés afin de faciliter leur utilisation.

Memcache ou « comment soulager la base ? »
Memcache (ou Memcached, « d » pour daemon – démon), une hashtable haute performance distribuée (accès via interface réseau) stockée en mémoire (RAM), pour Facebook c’est 25TB de données et des temps de réponse moyens < 200 micro-secondes. Memcache permet de soulager les bases MySQL, voire plutôt de les sauver, et d’avoir des temps de réponse bien meilleurs. Y sont stockées beaucoup de données et notamment des objets PHP « pré-calculés » et sérialisés. Les requêtes sont effectuées via les « global ID » en « multi-get » (sur de telles volumétries, oubliez les get). Les chiffres annoncés en termes de hit rate pour Memcache sont supérieurs à 95%.

Concernant l’aspect customisation de Memcache, il a été modifié en profondeur, notamment en remplaçant les accès protocolaires TCP par des UDP, impliquant l’ajout d’un contrôle de cohérence au niveau applicatif afin de prévenir les pertes de données et de combler la lacune laissée par le passage de TCP à UDP. Le Kernel du système a également été modifié, …

A la question d’un invité sur « qui a fait ces modifications ? » que ce soit pour Memcache ou bien MySQL, on notera la réponse : « des gens qui connaissent ». C’est dit : ne reproduisez pas à la maison ce que vous venez de voir à la conférence ! Plus sérieusement, les modèles décrits sont applicables à nos cas d’utilisation plus modestes, cependant il n’est pas conseillé (ni forcément nécessaire pour nos cas) d’effectuer des modifications aussi en profondeur. On notera que la version UDP de Memcache devra être (est ?) à disposition de la communauté. Cependant est-ce que nous pourrons l’utiliser telle quel sachant toutes les modifications annexes apportées par Facebook (Kernel, …) ?

LAMP is not perfect
Effectivement, LAMP ne s’applique pas à tous les cas et n’est pas exempt d’inconvénients. Commençons par PHP, stateless et pas si rapide, en tout cas moins que des langages compilés. De plus, certaines fonctionnalités sont présentes dans d’autres langages et non dans PHP. D’où le développement par Facebook de services accédés par PHP : chaque service est développé dans le langage (JAVA, Erlang, Python, …) optimisé pour la tâche qui lui incombe. Cependant, ces services (nécessaires) induisent la présence d’autres failure points éventuels et ajoute un coût de déploiement et de maintenance.

Les outils « maison »
La présentation s’achève par une rapide présentation, disons plutôt citation, faute de temps, des outils « maison » tels que Thrift, Scribe, SMC (Service Management Console), … Vous pouvez retrouvez certains de ces outils et notamment Thrift et Scribe et bien d’autres encore (Cassandra, …) sur le Site des développeurs de Facebook, dans la rubrique Community – Open Source.

Conclusion
Cette conférence, comme je vous l’ai précisé, est en anglais et dure 1h00, mais elle vaut le coût de part les sujets abordés forts intéressants. On appréciera cette incursion dans l’architecture logicielle de Facebook et plus généralement des réseaux sociaux et des applications bâties sur l’exploitation de graphes sociaux et dont le leitmotiv est « scalabilité ! ».

Frédéric FAURE

6 commentaires pour Facebook et le graphe social : LAMP et Memcache

  • Une petite précision concernant PHP : il a été aussi le fruit de customisation.
    Point non négligeable, mais que j’ai omis. Je répare donc cet oubli en précisant les customisations apportées par les développeurs Facebook à leurs outils PHP :

    - optimisation Opcode,
    - utilisation et amélioration (lazy loading, …) de APC,
    - développement d’extensions customisées (client Memcache, sérialisation, logging/statistiques/monitoring, méchanisme de capture d’évènements asynchrones, …).

  • Superbe synthèse, merci !

  • Hello,

    Ca a l’air si facile expliqué comme ça ;o)

    Quand tu dis : hit rate pour Memcache sont supérieurs à 95%. C’est le taux de réponses sur le taux de demandes à Memcache ?
    Qu’advient-il des 5% de hits ratés ? Faut-il retenter la requête jusqu’à obtenir un résultat ?
    N’y a-t-il aucune solution permettant de précompiler le PHP pour optimiser les perfs ?

    ChrYStophe

  • En fait, on calcule l’efficacité d’un cache avec son rapport hit/miss. Les requêtes aboutissent dans tous les cas (enfin normalement ;ob). Le hit rate, c’est le nombre de requêtes correspondant à un item qui est déjà en cache (et donc ramenant une valeur) sur le nombre total de requêtes effectuées sur le cache. Si l’objet souhaité n’est pas en cache, c’est un miss, l’objet est alors récupéré à partir de la source de données (base, …) puis setté en cache. La prochaine requête sur le même objet sera donc un hit, sauf si l’objet a été évicté dû à l’expiration de son TTL (Time To Live) ou bien faute de suffisament de mémoire disponible (Cf. Algorithmes de remplacement des lignes de cache pour lecture). 95% est un excellent hit rate !

    Pour améliorer les perfs, c’est APC (Alternative PHP Cache) que je citais. Il permet de cacher le code préalablement interprété et compilé.

  • PS : le hit rate est un indicateur plus fonctionnel que technique !

  • C’est exactement les Entity beans (EJB 1 et 2) qui sont décrites par ce méchanisme de cache avec un TTL. Bizarrement ils étaient réputés pour leurs grande consommation de ressources. Visiblement: les EJB 1 et 2 étaient surtout mal utilisés, ce qui leur a couté leur disparition dans la version 3…

    > ChrYStophe : « N’y a-t-il aucune solution permettant de précompiler le PHP pour optimiser les perfs ? »
    la solution la plus simple serait de passer à un language compilé :-)

    Cordialement,
    Zied Hamdi

Répondre

 

 

 

Vous pouvez utiliser ces balises HTML

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>