Pour gérer vos consentements :

Olivier Chouraki, MADGIC : « MySQL est rapidement devenu le goulot d'étranglement de notre architecture »

Publié par La rédaction le | Mis à jour le

Créateur de Madgic, une solution d'optimisation d'affichage des bannières publicitaires sur mobile, Olivier Chouraki évoque ses choix en matière d'architecture informatique et sa volonté de détacher du modèle LAMP (Linux, Apache, MySQL, PHP) aujourd'hui inadapté à son activité.

Créateur de Madgic, une solution d'optimisation d'affichage des bannières publicitaires sur mobile, Olivier Chouraki évoque ses choix en matière d'architecture informatique et sa volonté de détacher du modèle LAMP (Linux, Apache, MySQL, PHP) aujourd'hui inadapté à son activité.

 

Silicon.fr - Quelle est l'activité de Madgic ? A quel volume de données êtes-vous confronté ?

Olivier Chouraki -  MADGIC optimise les revenus publicitaires des éditeurs d'applications et de sites mobiles. Comme nous prenons une marge très réduite, nous devons gérer des volumes très importants pour être rentables. Nous gérons actuellement un inventaire mensuel de plus de 10 milliards de publicités sur mobile, dans 200 pays. Nous avons lancé notre activité et notre site www.madgic.com en septembre 2011, notre plateforme recevait alors environ 20 millions de demandes de publicité (ad request) par mois. Nous avons multiplié la capacité de notre plateforme par 500 en un an, en multipliant le coût de nos serveurs seulement par 3.

 

Silicon.fr - Comment avez-vous découvert que le modèle LAMP traditionnel n'était plus adapté à votre activité ?

 

Olivier Chouraki -  Nous avions choisi le modèle LAMP pour notre projet précédent, un réseau social mobile financé par la publicité, et nous avons petit à petit créé notre technologie d'optimisation de la publicité mobile en utilisant la même technologie, sans y réfléchir, puis nous avons découvert progressivement qu'elle n'était pas adaptée à notre nouveau métier, ni aux volumes que nous devions gérer. Confrontés à des volumes de plus en plus importants, nous nous sommes heurtés à des limitations de MySQL, PHP, puis Apache.

 

Nous utilisions un seul serveur. Notre première réaction a été d'augmenter sa puissance. C'était la solution la plus facile et rapide à mettre en oeuvre, et la moins coûteuse, mais ça n'a multiplié notre capacité que par 2 ou 3. Nous utilisons maintenant des serveurs 24 cores dotés de 48G de RAM et de disques 15k tours/minute ou SSD. Nous avons ensuite découpé notre ad server en 2 parties, 1 back-end MySQL et plusieurs front-end PHP, ce qui nous a permis de multiplier les machines dans une certaine limite. Nous avons aujourd'hui 4 serveurs « front » et nous pouvons encore en ajouter quelques-uns.

 

Mais notre problématique n'était pas seulement de supporter un volume croissant d'ad requests, nous devions aussi répondre à ces ad requests plus rapidement. Il n'était pas envisageable d'exécuter une ou plusieurs requêtes MySQL pour chaque ad request entrante. MySQL est rapidement devenu à la fois le goulot d'étranglement de notre architecture, limitant notre capacité, et la composante principale de notre temps de réponse, réduisant nos performances. Nous avons réduit notre temps de réponse moyen de 1 seconde à 0,1 seconde en utilisant la base de données noSQL Redis pour gérer un système de caches en mémoire. Nous avons commencé par utiliser Redis pour diviser par 100 le nombre de lectures MySQL, simplement en conservant les résultats des requêtes MySQL pendant un certain temps dans des caches gérés en mémoire sur chaque serveur « front » à la place du cache interne de MySQL que nous désactivons avec SQL_NO_CACHE. Nous avons perfectionné peu à peu le rafraîchissement de ces caches, jusqu'à mettre en place des files d'attente de requêtes MySQL à exécuter pour rafraîchir les caches. Ce système nous a permis d'exécuter les requêtes MySQL de lecture en mode asynchrone, leur temps d'exécution étant ainsi un temps « caché » n'ayant plus aucun impact sur nos temps de réponse. De plus, ce système de files d'attente, elles aussi gérées en mémoire à l'aide de listes Redis et de processus PHP tournant en tâche de fond, nous a permis de lisser les pics de charge MySQL en limitant le nombre de requêtes exécutées en parallèle.
Nous avons ensuite mis en place un système similaire pour exécuter les requêtes MySQL en écriture de façon asynchrone. Notre système d'optimisation des revenus publicitaires repose avant tout sur des statistiques, nous avons besoin de mesurer de nombreuses performances et de conserver un historique de données très important, ce qui nécessitait plusieurs milliers d'écritures MySQL par seconde. Tout d'abord nous avons regroupé ces écritures MySQL par bloc de 100 enregistrements en utilisant la clause VALUES de MySQL. Ensuite nous avons mis en place un système de compteurs en mémoire, en utilisant cette fois des compteurs Redis et des listes d'attente, avec des processus asynchrones tournant en tâche de fond et vidant périodiquement ces caches dans MySQL. Ce système a permis d'effectuer les écritures elles aussi en temps caché en mode asynchrone, de lisser la charge MySQL, et d'éviter les verrouillages et les « deadlocks ».
Notre nouvelle architecture utilise donc des caches en lecture avec des files d'attente pour les mettre à jour et des caches en écriture avec des files d'attentes pour les vider, les caches et les files d'attente sont gérés par Redis et par des processus PHP qui tournent en tâche de fond. Cette architecture, beaucoup plus modulaire, nous permet même de couper notre serveur MySQL pendant plusieurs dizaines de minutes pour des travaux de maintenance ou des évolutions sans aucun impact sur nos serveurs front-end et notre activité.
Il nous a fallu environ 6 mois pour maîtriser Redis. Nous avions essayé d'utiliser memcache auparavant mais sans grand succès. Redis n'utilise qu'un seul thread (CPU) ce qui le rend très rapide, mais nécessite d'exécuter plusieurs serveurs Redis en parallèle, car chaque processus sature au-dessus de quelques centaines de requêtes par seconde. Il nous a donc fallu mettre en place des mécanismes pour répartir nos données dans plusieurs caches Redis et retrouver dans lequel elles sont rangées, générer des clés uniques permettant de retrouver nos données dans chaque cache, des outils pour surveiller les performances et la saturation éventuelle de tous nos caches, pour les arrêter, les relancer, ajuster leur nombre à nos besoins, compresser et décompresser les données stockées dans les caches, les formater, etc.

 

Silicon.fr - MySQL a montré ses limites mais êtes-vous satisfait par PHP ?

 

Olivier Chouraki -  Concernant PHP, nous sommes satisfaits de la productivité que nous arrivons à obtenir avec ce langage. Le code est maintenable et évolutif à condition d'avoir un peu de méthode, de bien le documenter et le structurer en exploitant les possibilités de programmation orientée objet de PHP. Mais le mode de fonctionnement de PHP est très contraignant : pour chaque requête que nous recevons, et nous pouvons en recevoir plusieurs milliers en une seconde, un script PHP doit être chargé en mémoire, lancé, s'exécuter de façon indépendante des autres scripts PHP, sans pouvoir partager de données avec eux, répondre à cette ad request, puis se terminer. Ceci peut convenir pour des sites web qui servent des pages HTML à des utilisateurs, mais n'est pas très bien adapté à nos besoins.
Nous avons pu contourner facilement, grâce à APC, le fait que le language PHP ne soit pas compilé et que chaque script PHP doive être chargé en mémoire à chaque ad request. Le fait de ne pas pouvoir conserver de données en mémoire avant et après l'exécution d'un script PHP et de ne pas pouvoir partager ces données entre plusieurs scripts PHP a été résolu par l'utilisation de Redis comme expliqué précédemment.
L'utilisation de PHP pour exécuter des processus parallèles fonctionnant en tâche de fond a posé des problèmes plus complexes. Nous n'avons besoin que de quelques dizaines de processus pour mettre à jour et vider nos caches Redis, mais nous utilisons plusieurs milliers de processus en parallèle pour envoyer à une cinquantaine de réseaux publicitaires des millions d'ad requests sortantes afin de répondre aux ad requests entrantes de nos éditeurs.
Le premier problème qui s'est posé est celui de la mémoire excessive consommée par les processus PHP. Nous avons pu le surmonter en utilisant une version plus récente de PHP, en recompilant PHP avec uniquement les modules dont nous avions besoin, en augmentant la mémoire de nos serveurs et en limitant la mémoire allouée au lancement de chaque script PHP. Nous avons aussi dû mettre en place diverses méthodes pour réduire le nombre et la durée des ad requests sortantes, et bien sûr pour surveiller tout cela.
Le dernier casse-tête posé par PHP concernait l'exécution de milliers d'instances du même script en parallèle. Unix fournit pour cela un mécanisme appelé « fork » qui permet à un programme de se subdiviser en mémoire et de générer ainsi des milliers de processus « fils ». Il nous a fallu plusieurs mois pour maitriser ce mécanisme, mais nous sommes finalement arrivés à un blocage : il n'était pas possible d'allouer au processus PHP père la mémoire importante nécessaire pour gérer ses milliers de processus fils, tout en allouant à chacun de ces fils la mémoire très réduite dont il avait besoin. La limite de mémoire était commune au processus PHP père et à ses fils. Nous avons finalement réécrit en moins d'une journée un « contrôleur » en shell Unix qui remplace avantageusement l'utilisation du fork, beaucoup plus simple, plus robuste, plus performant et plus économique en mémoire. Aujourd'hui nous sommes conscients que PHP n'est pas le meilleur langage pour ce que nous faisons et nous envisageons de migrer progressivement certains modules de notre architecture vers des langages plus modernes.

 

Silicon.fr - Depuis le remplacement d'Apache par NGINX, dans quelle mesure avez vous amélioré votre activité ?

 

Olivier Chouraki -  Une fois le goulot d'étranglement résorbé au niveau de MySQL et de notre back-end, et le traitement des requêtes fortement accéléré, nous avons pu recevoir beaucoup plus de trafic sur nos serveurs front-end, jusqu'à ce qu'ils saturent à leur tour. La consommation de CPU par Apache et le nombre de connexions simultanées étaient trop élevés. Nous avons donc entrepris de remplacer Apache par un serveur Web plus moderne comme lighthttpd ou Nginx, réputés plus rapides et moins gourmands en CPU et en mémoire, nous permettant ainsi de gagner en scalabilité. Un point important était de séparer la gestion des requêtes http et les traitements PHP en utilisant PHP en mode fastcgi ou fcgi.
Nous avions besoin de pouvoir supporter 800 requêtes par seconde au lieu de 400. Le remplacement d'Apache par Nginx pour répondre aux ad requests de nos éditeurs nous a « seulement » permis de doubler la capacité de nos ad servers. Dans notre cas, le passage d'Apache à Nginx a eu un effet limité car nous ne servons aucun contenus statiques comme des pages HTML ou des images, uniquement des contenus dynamiques générés en PHP.

 

En complément de cette migration, nous avons pu réduire le nombre de connexions simultanées en utilisant systématiquement l'option « keep alive ». Nous avons aussi gagné en temps de réponse et en coûts de bande passante grâce à la compression des données échangées. Avant de migrer vers Nginx nous avons essayé de passer en version « mpm worker » d'Apache censée avoir la même architecture et les mêmes performances qu'Nginx, mais nous n'avons vu aucune amélioration et n'avons pas vraiment réussi à mettre en place une configuration stable. Il nous a fallu environ 3 mois pour tester différentes solutions, mesurer les résultats avec Apache bench, mettre au point et optimiser la configuration Nginx et le script d'installation correspondant et faire la migration d'Apache vers Nginx. Nous utilisons maintenant Nginx pour nos traitements critiques qui sont simples et optimisés à l'extrême, mais nous avons conservé Apache pour les besoins un peu plus compliqués et l'affichage des pages de notre site web.

 

Silicon.fr - Estimez-vous que le modèle LAMP est dépassé ?

Olivier Chouraki -  Il est clair que LAMP ne nous suffit plus. En résumé : nous ne voyons pas de raison pour l'instant de renoncer à Linux. Nous pensons conserver Apache et Nginx en parallèle pour des besoins différents. Nous allons probablement garder aussi MySQL pour certains besoins en complément de Redis, mais utiliser Hadoop pour notre reporting. Et à moyen terme nous ne devrions plus utiliser PHP que pour nos sites Web.

 

A lire : Dossier : Le Big Data va t'il forcer le modèle L.A.M.P. à muter ?

 

La rédaction vous recommande