Salut à tous, voici le second TP sur la mise à jour du site. Dans celui ci on va voir comment on peut optimiser le cache NginX et PHP5-FPM avec WordPress à l’aide de mécanisme de cache. C’est quoi le cache ? si votre serveur c’est un chirurgien très connu et très occupé, le cache c’est la secrétaire médicale qui répond au téléphone, renvoie les factures et fait remplir les mêmes formulaires à tous les clients patients. Pour ceux qui ont lu mon article sur Varnish, c’est la même chose. Et, mine de rien, on va installer 3 systèmes de cache dans l’infra mise en place dans le 1er TP :
- un cache des binaires dans PHP avec OPCache
- un cache des données entre PHP et MySQL avec REDIS
- un cache web pour NGINX avec FASTCGI-cache
On ajoutera une pointe optimisation et on terminera en comparant les performances par rapport à l’ancien site.
Optimiser le cache NginX et PHP5-FPM
Cache-cache avec PHP
OPCache
PHP est un langage interprété, ce qui signifie qu’il est compilé à chaque requête. C’est très inefficace pour un serveur web qui passe son temps à répondre aux mêmes requêtes entre 2 mises à jour du site. Il est possible d’activer une option pour dire à PHP de conserver une copie en mémoire du code compilé et ainsi gagné un temps précieux quand une requête a déjà été exécutée. C’est « simple comme bonjour » à mettre en place et ça améliore les perfs de manière non négligeable.
Pour activer cet OPcache dans PHP5-FPM, éditer le fichier /etc/php5/fpm/php.ini à la recherche des lignes suivantes. Et passer enable à 1 et indiquer combien de RAM doit être alloué à ce cache.
[opcache] # ; Determines if Zend OPCache is enabled opcache.enable=1 # ; The OPcache shared memory storage size. opcache.memory_consumption=128
Et on termine en redémmarant php5-fpm pour qu’il prenne en compte l’activation de ce cache.
service php5-fpm restart
REDIS
Bon avec OPCache on a stocké les binaires compilé pour PHP à l’exécution. mais la pas les données que ces binaires traitent. Et ces données peuvent être assez volumineuses. Notamment les requête sur la base de données par exemple. Pour le cas de site avec beaucoup de contenu ça peut faire une vrai différence. Pour cacher tout ça on va installer REDIS et le client PHP associé :
apt-get install redis-server php5-redis
La configuration se fait tout seule, on a juste à spécifier combien de RAM allouer à REDIS dans le fichier /etc/redis/redis.conf en éditant le ligne
maxmemory 128mb
Alors dans les tutos que j’ai vu sur le net pour que WordPress utilise REDIS, il suffit d’installer le plugin Redis Object Cache dans WordPress et l’activer dans les réglages du plugin.
Dans mon cas ça n’a pas suffit, il a également fallu que je rajoute quelques lignes dans le fichier wp-config.php de WordPress.
/*Paramétrage du Cache REDIS*/ define('WP_CACHE_KEY_SALT', 'geekeries.org'); # clé unique pour le site si vous avez plusieurs site avec REDIS sur le même serveur define('WP_CACHE', true); # voir https://codex.wordpress.org/Editing_wp-config.php#Cache
Comme pour OPCache on redémarre ensuite Redis et PHP5-FPM pour prendre en compte la config.
service redis-server restart service php5-fpm restart service nginx restart
A noter pour finir sur cette partie que d’autres extensions comme W3 Total Cache savent exploiter un serveur REDIS.
NGINX FASTCGI-Cache
Bon on a mis en cache nos données et notre code PHP, il ne reste plus que le contenu des pages retourné par NGINX. Dans le TP précédent, NGINX était pas trop compliqué : on aurait pu même se dire que ça marche comme Apache2. Mais je vous ai dit dans l’annonce de changement du serveur que j’avais changé le système de cache Varnish pour FastCGI-Cache intégré dans NginX. Pourquoi ? simplement parce que les tests que j’ai lu dessus montre qu’il est légèrement plus performant que Varnish et aussi je me suis rendu compte après coup que : Fast-CGI -Cache s’est vachement plus simple à déployer que Varnish (compares avec l’article sur Varnish et Apache si vous ne me croyez pas).
Pour configurer ce cache on doit commencer par l’activer dans la configuration globale de NginX dans le fichier /etc/nginx/nginx.conf, en ajoutant la ligne suivante dans le bloc « http ». Comme ci dessous :
http{ [...] fastcgi_cache_key "$scheme$request_method$host$request_uri"; [...] }
NGINX FastCGI-Cache en RAM
Un de problème de FastCGI-Cache c’est qu’il utilise des fichiers sur le disque pour travailler (ce qui est très con pour un système de cache). Ce n’est pas du tout efficace surtout par rapport à Varnish qui va taper à fond dans la RAM. On ne peut pas gérer des paramètres dans NginX pour lui faire mettre tout ça en RAM. Par contre, on peut dire au système d’exploitation de créer une arborescence fichiers en RAM « volatile », puis à NginX d’utiliser des fichiers dans cette arborescence pour son cache.
Pour créer un point de montage sur un système de fichier en RAM ; créer d’abord les dossiers suivants :
mkdir /ramcache/ mkdir /ramcache/wordpress
Puis éditer votre fichier /etc/fstab pour y lier un système de fichier dans 128Mo de RAM, par exemple en ajoutant la ligne suivante :
echo 'tmpfs /ramcache/wordpress tmpfs defaults,size=128M 0 0' >> /etc/fstab
Une fois que vous avez fait ça il reste plus qu’a configurer votre site pour lui dire d’utiliser le cache dans ce dossier. Il faut ajouter la ligne suivante en haut de votre fichier /etc/nginx/sites-available/wordpress, juste avant la balise server { ouvrante :
fastcgi_cache_path /ramcache/wordpress levels=1:2 keys_zone=WORDPRESS:100m inactive=60m; server{ [...]
Si vous avez plusieurs sites, il est impératif d’avoir un fichier de cache par site web (et idéalement dans un tmpfs dédié) et des valeurs de keys_zone différentes par site.
Pour terminer avec le cache, il faut ajouter les instructions suivante avant la fin de votre bloc server { } du site dans /etc/nginx/sites-availables/wordpress.
server{ [...] #Il existe un tas de page qu'on ne veut pas retrouver en cache : les pages d'admins, les requêtes HTTP utilisant "POST" # donc on définie un variable $no_cache pour indiquer quand il doit bypasser le cache. set $no_cache 0; # Les requêtes POST ne doivent pas être sauvegardée en cache. if ($request_method = POST) { set $no_cache 1; } # Ni les URL qui incluent une requête. if ($query_string != "") { set $no_cache 1; } # Tout ce qui touche aux pages d'admins de wordpress, xmlrpc, les feed RSS ou le sitemap if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml|[a-z0-9_-]+-sitemap([0-9]+)?.xml") { set $no_cache 1; } # Si les utilisateurs sont authentifié sur le site ou s'ils ont commenté récemment pas de cache non plus. if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") { set $no_cache 1; } # si vous avez une version de Nginx qui intègre le module fastcgi_cache_purge (assez récente ou compilé par vous même avec le module) # cette ligne permet de purger le cache du site en appelant une URL spécifique. location ~ /purge(/.*) { fastcgi_cache_purge WORDPRESS "$scheme$request_method$host$1"; } # Tous les fichiers css, javascript et les images reste en cache local du navigateur pendant un an. location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ { expires 365d; } # Idem pour les Pdf mais seulement un mois location ~* \.(pdf)$ { expires 30d; } # Et on met le cache sous condition de la variables $no_cache à 0 fastcgi_cache_bypass $no_cache; fastcgi_no_cache $no_cache; # poitneur vers la keys_zone WORDPRESS défini au début du fichier fastcgi_cache WORDPRESS; # ici on défini la durée de validité du cache pour les différent code HTTP. # donc les page ayant reçut un "code 200" (succès de la requête) seront conservées en cache 60 minute fastcgi_cache_valid 200 60m; [...] }
Et voilà pour le cache FASTCGI. Par rapport à Varnish c’est 200 fois plus simple, par contre Varnish semble être plus puissant dans pas mal de cas. Par exemple Varnish peut continuer de répondre quand Apache/NGINX est down. C’est un des défaut de ce système de cache interne au serveur Web, si NGINX est planté il n’y a plus personnes pour afficher la façade en trompe l’oeil.
GZIP
Dernière optimisation côté serveur web : autoriser la compression des réponses avec GZIP. Ça permet de réduire la taille des réponses retourné et de gagner un peu de bande passante contre un peu de temps CPU côté client et serveur pour dé-gzipper les données.
Les lignes nécessaires sont déjà présentes et commentées dans /etc/nginx/nginx.conf et il suffit de retirer le # pour activer la fonction Gzip du serveur.
gzip on; gzip_disable "msie6"; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_buffers 16 8k; gzip_http_version 1.1; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
On va juste en rajouter une ligne pour lui dire de ne pas compresser les fichiers de moins de 256 octets car le gain temps de calcul/temps d’envoi serait tout simplement inexistant.
gzip_min_length 256;
Et voilà, mine de rien on a bien réduit les temps de réponse de tout ce petit monde.
Comparaison : Varnish vs. FAST-CGI-Cache : « c’est qui qui a la plus… ?
Je vous fais un tableau comparatif pour un chargement initial dans le navigateur avec ces optimisations de cache NginX et PHP5-FPM.
Serveur | Time to First Byte | Début du rendu | Fin du rendu | Fin du chargement | webpagetest |
Ancien : Mysql/PHP5/Apache2/Varnish | 0.234s | 0.904s | 2.818s | 2.852s | test |
Nouveau Mysql/PHP5-FPM/NginX/FAST-CGI-Cache | 0.079s | 0.696s | 1.057s | 1.076s | test |
Note : les temps de réponse étant forcément lié au positionnement géographique du serveur et du client, ici j’ai utilisé le service webpagetest avec une machine à Paris et sur chrome. De plus au fil des tests il est devenu évident que le nombre et la taille des images sur la page d’accueil impacte de façon linéaire le temps du rendu complet.
Je vous laisse comparer mais ça dépote là… j’ai repris les chiffres que j’avais dans après l’article sur Varnish et bon… j’ai presque gagné 2 seconde là… Alors attention il ne faut pas tout attribuer à ma belle architecture de cache (même si j’aimerai bien). En effet entre les deux machines il ne faut pas oublié qu’il y a une différence de 2 CPU, 14Go de RAM, 500GO de disque et 900Mb/s de bande passante réseau et 6000km de distance en moins (et autant de routeur sur le chemin). Au final, je soupçonne que ça joue plus que le cache ici.
J’ai aussi fait tourner un bon gros apache-benchmark depuis l’ancien serveur sur le nouveau. J’ai arrêté les tests à 10 000 requêtes à 1000 requêtes par seconde et les résultats ne sont pas dégueulasses, mais un peu trop ésotérique pour tenir dans l’article.
Enfin, en termes d’optimisations je suis globalement assez content aussi, j’ai plus beaucoup de ligne dans le rouge en testant le site sur gtmetrix (le rapport depuis Londre est là) et comme webpagetest me met beaucoup de lignes au vert aussi.
Conclusion
Alors comme je l’ai dit, je ne suis pas certain que les bon résultats du nouveau serveur soient à 100% lié au passage de Varnish et NGINX/FASTCGI-Cache, ou aux diverses optimisations du cache NginX et PHP5-FPM que j’ai mis en place. Par contre, je suis forcé de constater que le nouveau serveur tient plutôt (très) bien la charge tout en ayant des temps de réponses très raisonnables quand il n’est pas ras la gorge.
L’archi a l’air stable (tant qu’on ne croise pas les effluves) et il reste probablement plein de truc à ajuster pour gagner encore quelques précieuses millisecondes qui entre en jeu dans un bon score chez Google.
D’ailleurs, vu les gains de temps de réponse, j’essayerai de vous faire un post dans une semaine ou deux sur l’évolution de la fréquentation du site depuis le changement de serveur. Je suis curieux de voir de ce que ça peut donne…