Antoine Benkemoun

09 sept, 2013

Accélérer des sites web avec nginx et memcache

Posté par Antoine dans la catégorie Architecture|Libre|Réseau|Système|Web

site_internetLes performances de sites web sont un sujet très complexe qui peut être abordé sous de nombreux angles. Ce sujet est tout à fait critique du fait que les moteurs de recherche prennent en compte cette métrique pour le référencement des sites. Dans le cas de sites e-commerce, les performances peuvent influer assez fortement le taux de conversion des visites en achat comme l’a montré Amazon.

Afin d’optimiser les performances d’un site, il est souvent préférable de s’intéresser au code et au fonctionnement interne du site afin de détecter les améliorations possibles. Mais, parfois, pour des raisons diverses et variées, vous n’avez soit pas la possibilité soit pas les connaissances vous permettant de mettre les mains dans le code. C’est à cette situation que nous allons nous intéresser aujourd’hui.

Le concept

L’idée est d’utiliser nginx en tant que serveur HTTP frontal et de le coupler à memcache en tant que système de mémoire cache ultra-rapide. Cette manipulation se déroulera en deux parties. Tout d’abord, il faut configurer nginx pour qu’il aille chercher les pages et contenus statiques dans memcache. Ensuite, il faudra remplir la base de données de memcache en pages HTML et contenu statique.

En réussissant à faire cela, nous allons pouvoir fortement réduire le temps de chargement de chaque page mais aussi accroitre considérablement la quantité de requêtes que notre infrastructure pourra traiter en parallèle. Ceci s’explique assez simplement par le fait qu’il est beaucoup moins couteux d’aller chercher une information en base de données stockée en mémoire que d’exécuter toute une pile applicative interfacée avec une base de données type SQL.

Installation et configuration de memcached

L’installation du démon memache, à savoir memcached, est particulièrement simple. Vous pouvez utiliser votre gestionnaire de paquet préféré. Le fichier de configuration est très simple à utiliser, il suffit d’indiquer la quantité de mémoire que vous souhaitez allouer memcached ainsi que l’IP à laquelle vous l’attacherez.

Dans mon cas, j’ai installé memcached directement sur le serveur frontal et l’ai rendu accessible sur une IP interne firewallée de sorte à ce que seuls les serveurs concernés y aient accès. Vous trouverez un exemple de fichier de configuration ici.

Configuration de nginx

En ce qui concerne la configuration de nginx, vous trouverez un exemple ci-dessous.

server {
  listen 80;
  server_name sitetroprapide.fr;
  access_log  /var/log/nginx/access.log;
  error_log  /var/log/nginx/error.log;

  location / {
    if ($request_method != GET)
    {
      return 500;
    }
 default_type text/html;
 add_header "Content" "text/html; charset=utf8";
 charset utf-8;
 set $memcached_key $http_host$uri;
 memcached_pass 10.1.1.1:11211;
 error_page 500 404 405 = @bypass;
 }

 location @bypass {
 proxy_pass http://sitetroprapide-prod;
 proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
 proxy_redirect off;
 proxy_buffering off;
 client_max_body_size 20M;
 proxy_set_header Host $http_host;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 }

}

Les parties spécifiques à memcache sont les lignes 15 à 17. La ligne 15 avec la directive set est probablement celle qui mérite le plus votre attention. En effet, cette ligne définit la « clé » que nginx ira demander à memcache afin de pour la distribuer aux clients. En fonction des sites que vous souhaitez héberger et du type de contenu, vous devez paramétrer cette ligne de sorte à ce que deux contenus différents n’aient pas la même clé sinon il y aura conflit. La configuration proposé utilisera des clés de type « www.sitetroprapide.fr/ » et « www.sitetroprapide.fr/page-1.html ». La ligne 16 avec la directive memcached_pass correspond au serveur memcache que vous souhaitez utiliser.

La ligne 17 avec la directive error_page permet de renvoyer la requête dont le contenu n’a pas été trouvé dans memcache vers les serveurs applicatifs. Grace à cette directive, vous aurez également la possibilité de générer des erreurs afin d’éviter le cache memcache dans certains cas comme c’est le cas pour les requêtes autres que des GET (cf. le block if à la ligne 8).

Mettre les données dans memcache

Vous l’aurez probablement compris, les étapes proposées plus haut permettent à nginx d’aller chercher du contenu dans memcache mais pas d’en stocker dedans. Afin de mettre les données dans memcache, vous aurez plusieurs solutions.

Tout d’abord, vous pouvez modifier votre site afin qu’il stocke le contenu des pages dans memcache. Cette solution est la plus élégante mais nous force à modifier l’application ce que nous souhaitions éviter depuis le début.

Ensuite, une autre solution est de charger les pages du site dans memcache en utilisant un script externe. C’est la solution que j’ai choisi. Vous trouverez un exemple de script que j’ai développé pour l’occasion en Python. Ce script insère dans memcache toutes les pages qui ont lien à partir de la page d’accueil ainsi que tous les contenus associés. Ce n’est probablement pas un exemple d’élégance pour du développement Python mais ça fait ce que je voulais lui faire faire.

Problématiques

Avant de vous montrer un exemple des bénéfices tirés de cette configuration, il me parait indispensable de parler des problématiques induites.

Lorsque vous chargez une page avec une clé dans memcache, le serveur frontal servira toujours cette page sans jamais aller consulter vos serveurs applicatifs. Cela signifie que si vous servez des pages ayant la même URL que vos utilisateurs soient identifiés ou non, memcache distribuera systématiquement la même page. Dans ce cas-là, il faudra adapter la configuration nginx afin de ne pas aller chercher les informations dans memcache si le cookie d’authentification est présent.

Pour résumer, il ne faut pas perdre de vue ce que fait cette configuration car ce n’est pas une solution miracle. C’est une solution très bien adaptée aux sites tels que des sites d’actualité où seulement une faible proportion des utilisateurs sont identifiés. Elle peut également s’appliquer aux sites de e-commerce afin d’accélérer le chargement des pages pour les nouveaux utilisateurs qui ne se sont pas encore identifiés par contre dès qu’ils s’identifieront, les performances redeviendront standards.

Résultats

Voici deux exemples de graphiques présentant le temps de chargement des pages pour deux sites sur lesquels j’ai implémenté cette configuration. L’amélioration est assez flagrante.

resultat2resultat1

Le script de mise à jour du contenu est exécuté toutes les heures afin d’éviter d’impacter trop fortement les pages non mises en cache. C’est une fréquence de mise à jour acceptable car ce sont des sites relativement peu mis à jour mais cela va dépendre en fonction de cas. Il reste encore des améliorations à faire au niveau du script afin de pouvoir fournir une interface permettant de rafraichir le cache de manière plus automatisée.

Bilan

Pour résumer, il est possible d’accélérer de manière très significative le chargement des pages web en utilisant nginx coupé à memcache. Néanmoins, cette configuration nécessite un mécanisme de chargement des données qui colle le mieux possible au type de site mis en cache. Il faudra donc se pencher sur chaque cas afin d’étudier les possibilités de mise en cache et de trouver la meilleure approche à adopter.

11 Commentaires to "Accélérer des sites web avec nginx et memcache"

1 | scar

septembre 9th, 2013 at 15 h 53 min

Avatar

Bonjour,

Quel est le gain de performance dans l’utilisation de memcache par rapport à l’utilisation d’un dossier monté en RAM (celui contenant les fichiers statiques) ?
Je me pose la question car aujourd’hui les serveurs dédiés sont souvent fournis avec une quantité astronomique de RAM disponible.

scar.

2 | Antoine

septembre 9th, 2013 at 16 h 00 min

Avatar

Bonjour,
J’ai testé une configuration de ce type il y a quelques années sur les offres RPS d’OVH car les temps d’accès au disque étaient vraiment mauvais. Ca améliorait les choses mais assez peu au final. Je pense que c’était lié au fait que les applications ont des dépendances type gem/plugins qui vont rester eux sur disque ce qui va fortement réduire les bénéfices d’une telle approche.
Donc, de mon expérience, cette configuration est beaucoup plus rapide.

3 | scar

septembre 9th, 2013 at 16 h 08 min

Avatar

Ok, merci pour la réponse aussi rapide.
Et au fait : très bon article, merci.

4 | Yoran

septembre 10th, 2013 at 9 h 29 min

Avatar

@scar
Sur un GNU/Linux, le fait de poser tes fichiers dans un dossier en RAM n’apporte que peu de chose car toute la RAM inutilisée est exploitée par l’OS pour du cache, notamment sur le système de fichier. Donc le gain est négligeable par rapport au fait de laisser à l’OS de quoi faire son travail.

Dans la même idée, j’avais comme toi pensé que mettre une base de donnée sur une ramdisk pouvait fortement la booster. En réalité le même principe que précédemment rend ce postulat caduc. Sauf que cette fois c’est la base de données qui assure la mise en cache de ce qui peut l’être et que c’est à la personne qui la configure de s’assurer qu’elle dispose d’un espace mémoire suffisant. L’OS jouant quant à lui son rôle sur le système de fichier sous-jacent.

Après même si le gain est faible, cela peut dans certains cas très critiques (beaucoup de hits/secondes) être intéressant malgré tout. Mais il faut vraiment que l’étude montre qu’une telle artillerie vaille le coup.

Pour revenir à Memcache, l’avantage de ces systèmes de cache programmables (car il y a en plein d’autres que Memcache) est avant tout fonctionnel. Il permet de mettre en cache des portions de page par exemple, qui sont « calculées » une fois pour toute. Un travail que ni le service HTTP, ni l’OS ne peut faire correctement évidemment. Seul le développeur peut savoir que tel bloc induit des milliers de requêtes et/ou un fort temps de traitement et ainsi décider de mettre le résultat en mémoire pour ne pas avoir à refaire cela à chaque fois. Grâce à cela on obtient des gains réels de performances.

5 | Yoran

septembre 10th, 2013 at 9 h 43 min

Avatar

Sinon, article très intéressant, merci !

Je me pose cependant une question sur tes deux sites. Tu utilises quelle technologie pour la fabrication des pages (python, php, statique, etc.) ? Car de mon expérience (PHP/Drupal), sauf pour un trafic réellement important où les gains de coût aux marges sont significatifs, le temps de processing PHP représente le plus grande part du traitement global pour la construction d’une page, reléguant le coût de la couche HTTP à la catégorie anecdotique. Pour obtenir les gains que tu exposes dans tes graphs, j’imagine en tout cas que ce n’est pas du Drupal derrière, ou alors que ton traffic est très fortement anonyme. Et dans le second cas, il est encore plus intéressant en terme de performance de générer une copie statique du site pour les anonymes et laisser directement nginx servir de simples fichiers HTML. Ou, pour la version de luxe, mettre en place un reverse-proxy comme varnish. Ce qui n’empêche pas d’utiliser un système de cache programmé comme Memcache pour accélérer la construction initiale des pages.

Au passage, tu le sais sûrement, mais on peut noter que memcache est un système client/serveur et que le coût d’accès à ce service n’est rentable que si l’on est dans une architecture disposant plusieurs frontaux web partageant ce même espace de cache. Si tout ce trouve sur la même machine, il est beaucoup plus rentable de fonctionner en mémoire partagée entre les différentes instances de client http, en passant par exemple par APC (si le projet est en PHP) qui offre le même service que memcache (lecture/écriture de blobs en mémoire partagée) mais sans le coût de communication avec un service externe.

Ceci étant dit, on peut aussi se demander si ces systèmes de cache présentent un si grand intérêt par rapport au fait de simplement stocker ces objets ou ces portions de markup… dans la base de données. En effet, si l’on valide la capacité de mise en cache de l’OS et du SGBD, le gain de vitesse a de forte chance d’être marginal par rapport à un memcache tout en permettant une infrastructure plus simple. Un jour il faudra que je fasse de plus sérieux tests sur ce point.

6 | Antoine

septembre 10th, 2013 at 9 h 43 min

Avatar

Merci Yoran. Je ne l’aurais pas mieux expliqué :P

7 | Antoine

septembre 10th, 2013 at 9 h 49 min

Avatar

Pour répondre à ta question, les sites dont je montre les graphs dans l’article sont développés en Ruby on Rails. C’est un écosystème assez différent du PHP et beaucoup de fonctionnalités « annexes » de PHP sont inclues par défaut dans la pile applicative.
Les résultats sont très bon car le code du backend est très très lent. C’est donc plus une mesure palliative qu’autre chose.
Les approches que tu proposes sont tout à fait valides et, je pense, plus élégantes que ce que j’ai mis en place. Néanmoins, elles nécessitent des modifications dans le code ce que je ne pouvais pas faire ici.
Là actuellement le plus gros problème relatif à cette configuration c’est qu’il y a un trop gros delta entre le temps de chargements des pages cachées et des pages non-cachées. La différence entre 250ms et 12s fait assez mal.
Je suis en train de regarder du coté de Varnish pour voir si il n’y a pas la possibilité de faire quelque chose de similaire.

8 | Yoran

septembre 10th, 2013 at 14 h 57 min

Avatar

Ah bé ça ne m’en parle pas, comme je le disais plus haut, moi d’est du Drupal… Donc ces temps sont presque une norme à mes yeux, d’où l’active recherche de solutions ;-)
Maintenant du coup, ton gros gain de vitesse c’est memcache qui te l’offre, le frontal nginx doit avoir un impact marginal non ? (même si tout est bon à prendre ;-)

9 | Yoran

septembre 10th, 2013 at 14 h 59 min

Avatar

Note pour les Drupalien qui suivraient la conversation, le module boost est une excellent alternative à varnish pour qui ne veut pas trop s’enquiquiner avec le VCL. Cela crée à la volée une copie statique du site servi du coup par le frontal web sans activer la couche PHP. C’est redoutablement efficace et simple à mettre en oeuvre.

10 | Antoine

septembre 10th, 2013 at 15 h 03 min

Avatar

Dans mon cas, le frontal nginx était une donnée car il sert de frontal pour tous les sites web qu’on héberge. Ca nous permet de réduire nos besoins en IP publiques et de pouvoir adresser toutes les machines virtuelles en IP privées.
nginx fait tellement mieux son travail qu’Apache2 en frontal que l’impact sur les performances se laisse oublier. Par rapport à HAProxy, c’est beaucoup plus configurable ce qui est également appréciable. Bref, je suis fan de nginx j’avoue.

11 | Yoran

septembre 10th, 2013 at 23 h 53 min

Avatar

J’avoue mal connaître nginx qui est plus souvent présenté pour ses performances que pour sa configurabilité. Il va donc falloir que je regarde cela de plus prés et voir s’il présente pour mes usages un avantage sur apache qui est certes une tannée à configurer, mais qui jusqu’à maintenant a toujours permis de répondre au moindre besoin tordu (authentification à la noix, webdav, proxy, dossiers à paramétrage spécial, ré-écriture d’url à la volée, et j’en passe)

Ajoutez votre commentaire


  • howaryoo: Merci pour cet article et les commentaires. Je ne me fais donc pas trop d'illusions sur le remboursement de la caution. J'ai une ligne depuis plusie
  • Joel: le must reste le serveur dédié avec infogérance comprise style metahoster (http://www.metahoster.com/) mais il faut évidemment compter des frais e
  • gerard: Bonjour ! Très intéressant cette article ; je cherche une formation de longue durée ( 3 ou 5 mois ) pour maîtriser ces technologies sur la virt

A propos de ce blog

Ce blog a pour objectif de partager des informations avec tous les internautes. Comme indiqué dans le titre, je traiterais de différrents sujets gravitant autour de la sécurité informatique, de la virtualisation, de la l'administration système et du réseau.