Optimisation serveur et blog Wordpress, les Bases!

Publié le 22 novembre 2008 par Jaxx

Wordpress est l’un des “script” de gestion de contenu les plus connus et adopté de part le monde, essentiellement utilisé comme moteur de Blog. Il est publié sous licence libre, et est disponible pour être installé par ses propres soins, mais aussi comme plateforme autonome au travers de Wordpress.com .

Il est construit avec le langage PHP, et utilise MySQL comme base de données… ce sera d’ailleurs notre point de départ pour cet article consacré aux bases de l’optimisation d’un blog basé sur Wordpress mais surtout du serveur sur lequel il est hébergé.

Je vais donc vous narrer mes récentes manipulations sur mon serveur et sur mon blog, trop longtemps “laissé en l’état”, et qui m’ont permis de passer d’un temps moyen de chargement de 6~9 secondes… à parfois moins de 3 secondes!

Car s’il en va de l’aisance de navigation de vos visiteurs, c’est aussi de la stabilité de votre serveur dont il sera question.

Il existent pas mal de méthodes, combinables ou non, touchant tout ou parti des services, ou carrément spécifiques à Wordpress, mais je ne vous donnerez que quelques pistes sur ce dernier point :-) et je partirais du cas où vous louez un serveur linux dédié (vu les prix, ça se démocratise!) ce qui commence a être un cas fréquent pour une poignée de bloggeurs en haut du Wikio, et que vous savez l’administrer ou à défaut, que vous n’avez pas peur de vous documenter.

Alors dans l’ordre:

  • MySQL, puisqu’il est intensément utilisé
  • Apache, rapidement, et quelques paramètres PHP
  • PHP lui-même, il fait à 99% toujours la même chose le bougre!
  • Externalisation d’éléments statiques, quand il y a plus léger qu’Apache pour soulager!
  • et Wordpress, on lui fait rien?

Le premier point disais-je:

Wordpress est MySQL-ivore

Et oui, ceux qui connaissent la plateforme Wordpress le savent, les contenus, le contenant, le moindre widget ou compteur de lectures iTunes ou de sondage génèrent souvent plusieurs requêtes dans la Base de Données.

Il faut donc: 1) réduire ce nombre de requêtes, et 2) “mémoriser” les résultats des demandes récurrentes pour éviter de les requêter trop souvent (le principe d’une Mémoire-Cache!)

En premier lieu, autant que possible: soulagez visuellement votre site en supprimant les plugins inutiles, et en utilisant des statistiques externes à votre site pour les amoureux des stats (GoogleAnalytics, GetClicky, Woopra, ou Wordpress.com-Stats)… Cela soulagera de quelques requêtes au moins, et au delà du temps de chargement brut de votre page, évitera le chargement d’éléments qui peuvent plomber le chargement “ressenti” par la personne qui consulte votre site, même quand la page est en grande partie affichée (je pense en particulier aux widgets établis à coup de javascript, affichant une liste de lecture musicale, une pub, ou une mappemonde des visiteurs récents :-) )… le sablier continue de tourner pendant ce temps! Je reconnais qu’il s’agit de concessions à faire.

Ensuite, pour améliorer la mise en cache des requêtes MySQL, on va utiliser, ou reparamétrer ce dernier, puisqu’il possède lui-même des fonctions de cache assez puissantes en ne demandant que peu de manipulations:

Dans /etc/mysql/my.cnf : vous trouverez les paramètres query_cache_limit et query_cache_size qui pourraient être assez faiblardes selon votre OS/distribution/version (depuis certaines les ont augmentés, mais timidement):

query_cache_limit = 4M # était à 1M, taille maximale de l'objet
query_cache_size  = 64M # était à 16M
max_connections = 200 # c'est selon, je me la pête un peu là :-)

Si vous avez du trafic un varié et très souvent en lecture, ce sont les premières valeurs à modifier. La variable key_buffer_size est souvent suffisante, mais si vous avez beaucoup de tables indexés vous pourrez leur réserver un peu de place, n’hésitez pas aussi a retoucher table_cache selon la quantité de tables ouvertes… J’héberge quelques gros outils en dehors de mon site, et on ressent sensiblement la différence.

Un outil de bon conseil, après avoir laissé votre serveur tourner quelques jours histoire de remplir les logs: MySQL Performance Tuning Primer Script. Il n’y a pas de valeurs magiques, il vous faut voir à l’usage, et vérifier l’état de temps à autres avec show status like ‘Qcache%’; dans MySQL.

En pratique, certes, un peu à vu de nez, depuis 10 jours mes requêtes effectives ont diminués de moitié alors que mon trafic augmente (… un peu!)

Pensez a relancer votre démon MySQL lorsque vous éditez my.cnf :-) et gardez à l’esprit que trop grossir ces chiffres est inutile, voir contre-productif, faites selon ce que permet votre machine d’abord, car mieux vaut manquer de cache que de swapper!

Pour Wordpress: Tant que nous y sommes: allez vous installer WP-DBManager … ça vous permettra de gérer vos backups, d’optimiser de temps à autres vos tables, et de réparer en cas de gros pépins, sans mettre les mains dans le cambouis ;-)

Apache et le module PHP

Bon, rien de particulier en dehors du dimensionnement de base du nombre de connexions “prêtes” et “maximales” permises: en admettant que votre Apache utilise le MPM Worker, nouveau depuis quelques années (vérifiez avec la commande “httpd -l” ou “apache2 -l” les modules précompilés de votre apache):

Vous trouverez dans /etc/httpd/httpd.conf ou /etc/apache2/apache2.conf (selon votre distribution) une section ressemblant à ceci:

<IfModule mpm_worker_module>
    StartServers           10
    MaxClients            200
    MinSpareThreads        50
    MaxSpareThreads       100
    ThreadsPerChild        75
    MaxRequestsPerChild  1000
</IfModule>

Encore une fois, à l’usage, vous peaufinerez vos chiffres en n’oubliant pas un “apache2ctl -k restart” après… le jour où j’ai 200 connexions simultanés j’offre une tournée générale, mais je prévois assez large car si je ne suis pas un bloggeur influent, j’héberge pour des tiers d’autres sites et des outils assez lourds. Il n’est pas forcément utile de mettre une grande valeur au paramètre StartServers, le nombre de processus étant dynamiquement contrôlé de minute en minute par apache, il permet de lancer un certain nombre de processus dès le lancement d’Apache, utile en cas de site affluent mais mets une forte charge les premiers instants.

(J’utilise un système assez différent de Worker ou Prefork, et encore rare qui, à défaut d’être plus lent de quelques millièmes, est plus flexible pour un hébergement à plusieurs usagers et m’isole mieux les sites entres-eux)

Ceux qui voudrait presser la dernière goutte du citron côté Apache pourraient éviter les fichiers .htaccess en configurant le vHost de leur site pour y inclure les directives voulues, et faire en sorte qu’Apache n’aille plus chercher ces fichiers à la moindre requête avec un beau “AllowOverride none” bien placé. Mais faudra se passer de leur flexibilité après coup

Concernant la configuration du module PHP dans Apache: La première chose a faire dans php.ini (dans /etc/php5/apache2/ , il y en a d’autres mais concernent d’autres modes de fonctionnement de PHP, hors module Apache)

max_execution_time = 30     ; Maximum execution time, in seconds (60s chez moi)
max_input_time = 120 ; Maximum time each script may spend parsing request data
memory_limit = 128M      ; Maximum memory a script may consume (256MB chez moi)
post_max_size = 16M; <- celui là, vous allez l'augmenter a tous les coups!

Certes, si un de vos scripts passe 30 secondes a travailler, il y a peut-être un problème, et une memory_limit plus grand pourra s’avérer utile un jour ou l’autre. Quoiqu’il en soit, lorsque ces limites sont atteintes, aucun doute possible: Un beau message d’erreur vous le dit… et si vous savez que vos scripts ne sont pas buggés, redimensionnez, et suivez ça d’un “apache2ctl -k restart” :-)

Toujours dans le même fichier, vérifiez que register_globals est à off, c’était marrant quand on avait 15 ans et qu’on jouait avec nos premiers scripts… Faites en de même avec register_argc_argv et register_long_arrays, ces derniers désactivés vous feront un peu gagner en perfs, et ils disparaîtront avec PHP6 de toute manière, tout comme magic_quotes_gpc/runtime/sybase :-)

En dernier lieu, l’installation du module Apache mod_deflate permettant la compression à la volé des fichiers textes (CSS, JS, sorties HTML…) est si aisé qu’il serait idiot de s’en passer :-)

PHP: Taylorisme Elephantèsque

Le PHP, comme bien d’autres langages, est un language scripté, chaque script est chargé à la demande par le module PHP d’Apache ou par son interpréteur classique. Il est systématiquement relu par PHP, nettoyé des commentaires (inutiles pour la machine), réagencé, vérifié, puis compilé sous forme binaire, presque directement exploitable par la machine en y ajoutant les données effectives a traiter… seulement pour en arriver là, il s’est passé du temps… opérations sans cesse répétés a chaque appel de script…

Mais ce script, il n’a pas changé entre les exécutions (sommaires)… on peut donc griller des étapes, non?

Mise en cache de l’OP-Code:

En effet! Il existe plusieurs libraires ou modules permettant ce genre de cache (bah oui, encore un cache) XCache, eAccelerator (Turck MMCache), et APC… c’est ce dernier sur lequel nous nous pencherons puisqu’il est fait par les auteurs même de PHP.

Son installation est plus aisé que ses acolytes: soit il est dans les packages de votre distribution (en testing sous Debian, ne me dites pas que vous n’avez pas mis le moindre paquet testing en prod! “apt-get install php-apc”) soit, vous l’installez avec PECL “pecl install apc“.

Après quoi, vous vérifiez bien dans /etc/php5/conf.d/apc.ini que la valeur apc.shm_size soit d’au moins 128 dans un premier temps. Vous placerez le fichier apc.php (a renommer bien sûr) dans un endroit accessible et pourrez suivre son remplissage. Bien sûr, inutile d’avoir prévu 3 fois trop grand, c’est de la mémoire qui aurait pu servir ailleurs :-) (Notez que souvent Apache est relancé au moment des rotations de logs, donc Apache et PHP sont réinitialisés, APC avec!)

Les exécutions de scripts seront plus consommateur de CPU, mais nettement plus rapides, et un ami pour qui j’héberge un gros CRM m’a indiqué qu’il trouvait l’ensemble plus fluide :-)

Mise en cache des variables et objets:

Pour Wordpress: Au delà de la mise en cache des scripts PHP sous forme précompilé, on pourra aussi mémoriser des informations utiles à son fonctionnement ($variables, array et objets parfois puisé depuis une lente base de données) avec les fonctions d’Object-Cache. Soit en réutilisant APC, soit à base de Memcache.

APC possède aussi des fonctions pour mémoriser variables et autres objets, c’est la solution opté par UnBlog.fr et qui ne nécessite pas plus d’opération que d’aller mettre object-cache.php de ce zip (merci Quentin) dans /wp-content/ et d’activer le Cache avec une ligne define(”ENABLE_CACHE” ,true); dans /wp-config.php .

Memcache, de son côté est un daemon a configurer, sur une ou plusieurs machines (donc pas forcément localement ;-) ). Inconvenient: Il faut l’installer, ça fait une manip de plus (ok, “apt-get install memcached” fera l’affaire, pensez à le lancer, sachant que ses 64M de mémoire d’origine devrait suffire et qu’il ne s’agit que d’un maximum, il ne bouffe pas tout tout de suite :-) ) Avantages: costaud, il pourra être utilisé par d’autres outils et depuis d’autres languages, et vous pouvez en installer des batteries sur plusieurs serveurs comme l’ont fait Twitter ou Facebook ! Il conviendra de placer object-cache.php (merci Amaury) de le renommer en .php bien sûr, au même endroit, et avec la même activation que pour APC.

Se soulager des éléments statiques - Offloading

Là où l’on va pouvoir décharger très facilement le serveur et répartir les appels HTTP c’est en copiant toutes les données statiques (images, css, javascript) vers un autre serveur…

Sans aller jusqu’à construire un Content Delivery Network, avec ses milliers de serveurs placés de part la planète pour être au plus proche de celui qui navigue sur votre site, on va faire plus simple:

Que ce soit le même service web mais avec un autre nom d’hôte en alias sur une autre IP (car un navigateur limite le nombre d’appels simultanés vers un seul serveur), ou mieux, un service web très allégé sur un autre serveur, dédié à l’envoi de données statiques: j’ai choisi de faire entre les deux:

Un daemon web léger (dans le genre Lighttpd ou Nginx) uniquement fait pour envoyer les données statiques depuis une IP séparée, mais sur le même serveur web (c’est la crise, on réduit les coûts :-) )

Apache n’est pas un mauvais bougre pour envoyer des données statiques, mais servir des fichiers, de façon bête et méchante, n’est pas son truc, et s’il est performant (mpm-event?), il mobilise pas mal de ressources: Voilà pourquoi, on va démontrer qu’en quelques lignes on peut avoir:

  • Un petit serveur web pas intelligent mais super rapide et léger en ressources qui fera une copie lui-même des fichiers manquants!
  • Et parce que Lighttpd est un peu has-been (je déconne, je m’en sert dans mon propre projet) j’ai testé ce matin une bonne technologie Russe… plus dans le style Soyouz que Koursk, avec NginX (prononcez Engine-X), en l’utilisant comme Reverse Proxy, mais avec Stockage permettant de mémoriser les fichiers!

Il est aussi connu par votre distribution et la vue du fichier de conf d’exemple sera assez explicite pour comprendre ce qu’il s’y trame. C’est évidement qu’à titre éducatif et imparfait, après quoi, il faudra prévoir une éventuel interface de contrôle, de quoi faire un nettoyage périodique, conserver des statistiques si vous compter industrialiser ce système :-) Un Squid bien paramétré pourrait aussi très bien fonctionner, quoique plus complexe.

Amazon a remis au goût du jour son service S3 et a baptisé son nouveau réseau CloudFront, ils ont des serveurs un peu partout, et si vous avez un site un peu international et des éléments lourds a déporter ça peut être plus viable. Certains projets de librairies JavaScript comme jQuery offrent l’hébergement sur CDN de leur librairies… Quitte à dépendre d’un tiers: vous êtes toujours à jour, et c’est toujours ça de moins sur votre serveur. Google propose même un chargeur de javascript pour ça.

Je reconnais avoir utiliser une lance-roquette pour décocher une mouche, mais c’est pour la forme. De plus j’ai mes propres containtes, je n’héberge pas que mes sites, j’utilise SSL, et je les isole entres-eux (mpm-itk ou mpm-peruser)

Pour Wordpress: un service à la tarification floue existe sous le nom de SteadyOffload mais je n’ai plus besoin d’eux et vais écrire mon propre plugin Wordpress pour utiliser de manière plus flexible mon système maison :-) mais ce sera pour un prochain billet!

Vous n’avez pas un dédié et/ou n’avez pas accès à tout ça

Tout n’est pas perdu: Je vous le garde pour la fin, je ne privilégie pas ce forme de mise en cache car il risque souvent de donner une information pas à jour, ou va brider les fonctionnalités d’une partie du blog.

Le plus connu d’entre-eux est WP Super Cache

Leur principe est de conserver l’html généré de la page de façon statique, et de ne donner plus que celle-ci par la suite. Cette cache est éliminé lorsqu’un élément import change (un commentaire, un nouveau post p.e.) mais en ignorant des éléments qui aurait pu provenir d’un plugin différent, ou d’éléments communs à tout le site.

Aller plus loin:

  • Chez PapyGeek : WordPress : optimiser son blog avec un système de cache
  • Codex Wordpress : WordPress Optimization et High Traffic Tips For WordPress
  • Manuel PHP : Cache PHP alternatif
  • Les pages sur le Caching Wordpress MU de Quentin d’UnBlog.fr
  • L’article sur Memcache dans WP d’Amaury
  • Full Page Test tool de Pingdom et YSlow! (avec Firebug) de Yahoo! pour mener des essais.
  • Le Wiki du projet NginX

Après quelques jours: Ma cache APC de 256Mo se remplit à hauteur de 75% (d’autres outils que mes blogs WP pour l’essentiel), Memcache peu utilisé, Cache MySQL eficace à plus de 90% mais est nettement moins utilisé aussi, mon Load était déjà trop faible pour qu’une différence soit perceptible. Le ressenti global est un site qui se charge nettement plus vite :-) à voir avec le temps!

Nota: ne m’envoyez pas de pierres en cas d’erreur dans l’article, je n’ai pas le recule nécéssaire pour peaufiner, et si j’oublie quelquechose d’important, n’hésitez pas à m’écrire, me twitter ou commenter!… et comme ce ne sont censés être que des base d’exploration, c’est a adapter selon vos sites et vos machines, il n’y a certainement pas la moindre formule magique :-)