Améliorer les performances de Drupal avec Nginx

Compteur vitesse voiture

Il y a quelques mois encore, j'utilisais le serveur web Apache pour les sites Drupal que j'héberge. Apache étant relativement gourmand en ressources, je me suis penché sur les alternatives possibles pour proposer mes sites internet avec un chargement plus rapide, tout en réduisant l'empreinte mémoire du serveur Web.

Nginx ou Varnish comme reverse proxy cache

Dans un premier temps, je me suis naturellement tourné vers l'accélérateur http Varnish qui dispose d'un bonne intégration avec Drupal via le module Varnish HTTP Accelerator Integration. Positionné comme reverse proxy, Varnish permet de mettre en cache les pages pour les visiteurs anonymes et ainsi décharger le serveur web. Varnish permet également, si besoin, de mettre en place une répartition de charge vers plusieurs serveurs web. On peut trouver, sur le site de lullabot, une très bonne et complète explication de la configuration de Varnish pour Drupal.

Dans un deuxième temps, je me suis documenté sur le serveur Nginx qui monte. Nginx est un serveur Web asynchrone qui se targue d'être plus léger et plus rapide que les principaux serveurs Web actuels. La part de marché du serveur Web Nginx est en constante progression. En avril 2013 environ 15% des sites internet mondiaux utilisent Nginx (source : April 2013 Web Server Survey), soit plus de 85 millions de sites Web. Nginx est d'abord un serveur web dit statique (il ne sert que des fichiers statiques), et couplé à php5-fpm, Nginx peut servir tout site web dynamique écrit en PHP. Nginx peut être également utilisé en tant que reverse proxy avec un système de cache (et donc en tant qu'accélérateur http tout comme Varnish), et permet aussi de faire de la répartition de charge. Le système de cache de Nginx peut être utilisé aussi au niveau du serveur web lui-même. A mon sens, c'est un atout indéniable car cela permet de n'avoir à maintenir qu'un seul système (le serveur web) au lieu de deux systèmes (le serveur web et le reverse proxy cache).

Pour choisir pour quel système j'allais opter, j'ai donc comparé les deux solutions (Varnish 3.0 et Nginx 1.2.7 en tant que reverse proxy et accélérateur http) en termes de performances. Sur un petit serveur (1 part GANDI, soit 1 coeur et 512 Go de mémoire), voici les résultats des tests de performance réalisés avec Apache Bench pour 10 000 requêtes et 50 connexions simultanées (ab -c 50 -n 10000 xxx)

Varnish

Concurrency Level:      50
Time taken for tests:   3.926 seconds
Complete requests:      10000
Total transferred:      194013820 bytes
HTML transferred:       188508320 bytes
Requests per second:    2547.44 [#/sec] (mean)
Time per request:       19.628 [ms] (mean)
Time per request:       0.393 [ms] (mean, across all concurrent requests)
Transfer rate:          48265.47 [Kbytes/sec] received

Nginx

Concurrency Level:      50
Time taken for tests:   2.016 seconds
Complete requests:      10000
Non-2xx responses:      288
Total transferred:      105316944 bytes
HTML transferred:       100413424 bytes
Requests per second:    4961.26 [#/sec] (mean)
Time per request:       10.078 [ms] (mean)
Time per request:       0.202 [ms] (mean, across all concurrent requests)
Transfer rate:          51025.83 [Kbytes/sec] received

Nginx est donc capable d'assurer 4 961 requêtes par seconde, contre 2 547 requêtes par seconde pour Varnish, soit près du double.

Nginx semble donc plus performant que Varnish et permet de faire tout ce que propose Varnish, et bien plus. Il présente l'avantage de n'avoir qu'une seule solution à maintenir pour implémenter un serveur web, un reverse proxy, un système de cache, et un répartiteur de charge. L'inconvénient est qu'il dispose d'une documentation et d'un support moins prolifique que le serveur web Apache et l'accélérateur Varnish. Mais cet inconvénient tend à s'amoindrir de plus en plus, avec une communauté très active, et au vu du nombre croissant de sites Internet propulsés avec Nginx. Le Wiki de Nginx est très bien documenté, et on trouve aujourd'hui facilement des ressources et des exemples pour configurer Nginx quelque soit le CMS utilisé.

Guide rapide pour configurer Nginx pour Drupal

Nginx est présent dans les dépôts des principales distributions. Pour debian 6 Squeeze, le dépôt dotdeb.org propose plusieurs packages des dernières versions stables de Nginx, dont le package nginx-extras qui dispose de tous les modules activés, dont notamment les modules upload-progress et cache-purge, qui sont particulièrement intéressants. L'installation est très classique après avoir ajouter dotdeb dans ses dépôts (apt-get install nginx-extras php5-fpm). Vous pouvez bien sûr compiler nginx pour disposer d'une version qui correspond parfaitement à vos besoins.

Si la documentation pour Nginx est moins prolifique sur le web que pour Apache et Varnish, on trouve néanmoins de nombreux tutoriaux pour la configuration de Nginx et PHP5-FPM.

Nginx dispose de son propre groupe Drupal où vous pourrez trouver une configuration de référence de Nginx pour Drupal maintenu par perusio sur GitHub. Très bien documentée, cette configuration est plus complexe mais plus complète et sécurisée que les nombreux exemples qu'on peut trouver ici ou là sur le Web, exemples beaucoup plus simples (à l'instar des virtualhosts de Apache) mais qui n'exploitent pas toute la puissance de Nginx. Cette configuration implémente le support de Boost, le support du module Filefield Nginx Progress pour la barre de progression (upload progress), entre autres, et vous permet de mettre en place rapidement un système de cache au niveau du serveur Web (ou du reverse proxy) que ce soit pour les visiteurs anonymes ou pour les utilisateurs authentifiés.

Performances de Drupal avec Nginx vs Apache

Avant de basculer définitivement sur Nginx, j'ai réalisé quelques tests de performance du serveur web Nginx 1.2.7 comparé à Apache 2.2.16, avec un site Drupal fraichement installé. Les serveurs Nginx et Apache ont été configurés de façon identique notamment au niveau du nombre de clients maximum et au niveau du nombre de requêtes maximum par processus. Il faut noter que ces tests ont été réalisés avec la version 2.2.16 du serveur Apache. La nouvelle version 2.4 d'Apache, sortie il y a un an, semble être beaucoup plus performante que celle testée ici, surtout sur des serveurs disposant d'un nombre conséquent de processeurs (mais les benchmarks réalisés sont parfois assez contradictoires). Plusieurs configurations de Drupal ont été testées :

  • Drupal avec son cache natif
  • Drupal avec le module Boost
  • Le temps de chargement pour un utilisateur authentifié

Les mesures ci-dessous ont été réalisées sur un serveur VPS Gandi (1 part, soit un processeur et 512 Go de mémoire) en utilisant les outils apachebench et httperf.

Drupal avec son cache natif

Nginx

ab -n 100 -c 30 xxx

Server Software:        nginx
Concurrency Level:      30
Time taken for tests:   2.775 seconds
Complete requests:      100
Total transferred:      2529500 bytes
HTML transferred:       2490200 bytes
Requests per second:    36.04 [#/sec] (mean)
Time per request:       832.394 [ms] (mean)
Time per request:       27.746 [ms] (mean, across all concurrent requests)
Transfer rate:          890.28 [Kbytes/sec] received


httperf --client=0/1 --server=xxx --port=80 --uri=/ --send-buffer=4096 --recv-buffer=16384 --num-conns=100 --num-calls=10
Maximum connect burst length: 1

Total: connections 100 requests 1000 replies 1000 test-duration 48.946 s
Connection rate: 2.0 conn/s (489.5 ms/conn, <=1 concurrent connections)
Connection time [ms]: min 470.6 avg 489.5 max 522.2 median 488.5 stddev 9.5
Connection time [ms]: connect 0.2
Connection length [replies/conn]: 10.000

Request rate: 20.4 req/s (48.9 ms/req)
Request size [B]: 74.0

Reply rate [replies/s]: min 20.0 avg 20.4 max 20.8 stddev 0.2 (9 samples)
Reply time [ms]: response 46.8 transfer 2.1
Reply size [B]: header 450.0 content 24902.0 footer 2.0 (total 25354.0)
Reply status: 1xx=0 2xx=1000 3xx=0 4xx=0 5xx=0

Apache

ab -n 100 -c 30 xxx

Server Software:        Apache/2.2.16
Concurrency Level:      30
Time taken for tests:   28.364 seconds
Complete requests:      1000
Total transferred:      25346000 bytes
HTML transferred:       24902000 bytes
Requests per second:    35.26 [#/sec] (mean)
Time per request:       850.918 [ms] (mean)
Time per request:       28.364 [ms] (mean, across all concurrent requests)
Transfer rate:          872.66 [Kbytes/sec] received


httperf --client=0/1 --server=xxx --port=80 --uri=/ --send-buffer=4096 --recv-buffer=16384 --num-conns=100 --num-calls=10
Maximum connect burst length: 1

Total: connections 100 requests 1000 replies 1000 test-duration 52.261 s
Connection rate: 1.9 conn/s (522.6 ms/conn, <=1 concurrent connections)
Connection time [ms]: min 499.0 avg 522.6 max 591.0 median 518.5 stddev 19.4
Connection time [ms]: connect 0.6
Connection length [replies/conn]: 10.000

Request rate: 19.1 req/s (52.3 ms/req)
Request size [B]: 74.0

Reply rate [replies/s]: min 18.2 avg 19.2 max 19.6 stddev 0.5 (10 samples)
Reply time [ms]: response 46.9 transfer 5.3
Reply size [B]: header 453.0 content 24902.0 footer 2.0 (total 25357.0)
Reply status: 1xx=0 2xx=1000 3xx=0 4xx=0 5xx=0

Les performances que ce soit avec Nginx ou Apache sont similaires (avec un très léger avantage pour Nginx, 36 req/s contre 35 req/s). Le facteur limitant ici ne se situe pas au niveau du serveur Web mais bien au niveau de Drupal et des requêtes PHP executées.


Drupal avec le module Boost

Nginx

ab -n 10000 -c 30 xxx

Server Software:        nginx
Concurrency Level:      30
Time taken for tests:   2.275 seconds
Complete requests:      10000
Total transferred:      253780000 bytes
HTML transferred:       250020000 bytes
Requests per second:    4395.52 [#/sec] (mean)
Time per request:       6.825 [ms] (mean)
Time per request:       0.228 [ms] (mean, across all concurrent requests)
Transfer rate:          108934.95 [Kbytes/sec] received


httperf --client=0/1 --server=xxx --port=80 --uri=/ --send-buffer=4096 --recv-buffer=16384 --num-conns=1000 --num-calls=30
Maximum connect burst length: 1

Total: connections 1000 requests 30000 replies 30000 test-duration 5.971 s
Connection rate: 167.5 conn/s (6.0 ms/conn, <=1 concurrent connections)
Connection time [ms]: min 4.2 avg 6.0 max 13.0 median 4.5 stddev 2.6
Connection time [ms]: connect 0.1
Connection length [replies/conn]: 30.000

Request rate: 5024.0 req/s (0.2 ms/req)
Request size [B]: 74.0

Reply rate [replies/s]: min 5017.2 avg 5017.2 max 5017.2 stddev 0.0 (1 samples)
Reply time [ms]: response 0.2 transfer 0.0
Reply size [B]: header 405.0 content 25002.0 footer 0.0 (total 25407.0)
Reply status: 1xx=0 2xx=30000 3xx=0 4xx=0 5xx=0

Apache

ab -n 1000 -c 30 xxxx

Server Software:        Apache/2.2.16
Concurrency Level:      30
Time taken for tests:   0.753 seconds
Complete requests:      1000
Total transferred:      25291000 bytes
HTML transferred:       25002000 bytes
Requests per second:    1327.92 [#/sec] (mean)
Time per request:       22.592 [ms] (mean)
Time per request:       0.753 [ms] (mean, across all concurrent requests)
Transfer rate:          32797.26 [Kbytes/sec] received


httperf --client=0/1 --server=xxx --port=80 --uri=/ --send-buffer=4096 --recv-buffer=16384 --num-conns=100 --num-calls=10
Maximum connect burst length: 1

Total: connections 100 requests 1000 replies 1000 test-duration 1.148 s

Connection rate: 87.1 conn/s (11.5 ms/conn, <=1 concurrent connections)
Connection time [ms]: min 6.2 avg 11.5 max 14.1 median 11.5 stddev 1.3
Connection time [ms]: connect 0.1
Connection length [replies/conn]: 10.000

Request rate: 870.8 req/s (1.1 ms/req)
Request size [B]: 74.0

Reply rate [replies/s]: min 0.0 avg 0.0 max 0.0 stddev 0.0 (0 samples)
Reply time [ms]: response 1.1 transfer 0.1
Reply size [B]: header 260.0 content 25002.0 footer 0.0 (total 25262.0)
Reply status: 1xx=0 2xx=1000 3xx=0 4xx=0 5xx=0

Nginx est largement plus performant avec 4 357 req/s que le serveur Apache qui délivre 1 327 req/s. Les mesures avec httperf sont encore plus probantes. Pour délivrer des fichiers statiques (les pages html générées par le module Boost), Nginx démontre clairement sa supériorité.

Le temps de chargement d'une page pour un utilisateur authentifié

Nginx

Page load times : 2.85 s

temps de chargement de page avec Nginx

Apache

Page load times : 5.4 s

temps de chargement de page avec Apache

Là encore, Nginx est deux fois plus rapide pour charger une page (assez conséquente avec plusieurs photos) que le serveur Apache

Le système de cache de Nginx

Si le serveur Nginx démontre des performances bien meilleures, notamment pour les fichiers statiques, toute la puissance de Nginx réside dans son système de cache intégré qu'on peut aussi bien utiliser avec le reverse proxy de Nginx que le serveur Web lui-même.

Les performances du même site Drupal, mais cette fois sans le module boost, avec Nginx et son système de cache activé, sont impressionnantes.

ab -n 10000 -c 30 xxx

Server Software:        nginx
Concurrency Level:      30
Time taken for tests:   2.437 seconds
Complete requests:      10000
Total transferred:      252670000 bytes
HTML transferred:       249020000 bytes
Requests per second:    4103.34 [#/sec] (mean)
Time per request:       7.311 [ms] (mean)
Time per request:       0.244 [ms] (mean, across all concurrent requests)
Transfer rate:          101248.99 [Kbytes/sec] received


httperf --client=0/1 --server=xxx --port=80 --uri=/ --send-buffer=4096 --recv-buffer=16384 --num-conns=1000 --num-calls=30
Maximum connect burst length: 1

Total: connections 1000 requests 30000 replies 30000 test-duration 6.044 s
Connection rate: 165.5 conn/s (6.0 ms/conn, <=1 concurrent connections)
Connection time [ms]: min 4.2 avg 6.0 max 11.7 median 4.5 stddev 2.6
Connection time [ms]: connect 0.1
Connection length [replies/conn]: 30.000

Request rate: 4963.7 req/s (0.2 ms/req)
Request size [B]: 74.0

Reply rate [replies/s]: min 4970.1 avg 4970.1 max 4970.1 stddev 0.0 (1 samples)
Reply time [ms]: response 0.2 transfer 0.0
Reply size [B]: header 405.0 content 25002.0 footer 0.0 (total 25407.0)
Reply status: 1xx=0 2xx=30000 3xx=0 4xx=0 5xx=0

Avec un potentiel de 4 103 req/s avec apachebench (et 4 963 req/s avec httperf), Nginx et son système de cache présente des performances similaires pour un site Drupal sans Boost qu'avec un site Drupal avec Boost. Implémenter le cache au niveau du serveur web lui-même peut présenter toutefois des inconvénients en terme de gestion plutôt que de l'implémenter au niveau du site Internet. Il est plus facile pour un webmestre de pouvoir gérer le cache (et le vider) s'il est géré au niveau du site lui-même. Si le cache est implémenté au niveau du serveur web, sa gestion demande des compétences différentes et peut être plus ardues, ou peut être même impossible si le webmestre ne dispose pas d'accès ssh au serveur lui-même.

Booster Drupal avec le cache de Nginx

Plusieurs techniques peuvent être utilisées pour mettre en oeuvre le système de cache de Nginx.

La technique du microcache consiste à ne mettre en cache les pages du site que pour une durée très courte (quelques minutes), afin de ne servir les pages depuis le cache qu'en cas de grosse affluence de visiteurs sur le site Internet. Cette technique permet d'absorber très facilement un pic de visites, sans que le webmestre du site ait à se soucier de la gestion du cache au niveau du serveur Web.

Une autre technique, que nous allons appeler macrocache, consiste à mettre en cache les pages du site pour une durée beaucoup plus longue (plusieurs jours), en automatisant la purge sélective des pages mises en cache lorsqu'elles sont mises à jour sur le site Internet (modification ou ajout d'un contenu par exemple). Ce système rend alors obsolète l'utilisation de modules tels que Boost et présente l'avantage de ne plus avoir à gérer de système de cache au niveau du site Internet lui-même. Cette tache est assurée par le serveur web, à l'instar de ce que fait Varnish en tant que reverse proxy cache. Un autre atout de cette technique est de toujours proposer à vos visiteurs un temps de chargement des pages de votre site web très rapide.

Une question ? Besoin d'aide pour migrer sur Nginx ? Laissez un commentaire ou contactez-moi.

Ajouter un commentaire