Petit cas d’optimisation PHP

closeCet article a été publié il y a 7 ans 5 mois 17 jours, il est donc possible qu’il ne soit plus à jour. Les informations proposées sont donc peut-être expirées.

J’ai choisi de vous faire un retour d’expérience suite au récent passage en production d’un client chez nous. C’est particulièrement intéressant sur plusieurs points, notamment sur PHP qui tabassait pas mal.

Le contexte

Une VM, 3vCPU, 4Go de RAM, PHP 5.6 avec Zend Opcache d’activé, et un load de 8 presque intégralement dû à PHP-FPM. La base de données est sur un deuxième serveur, et se tourne magistralement les pouces. Dernière précision, le site est un Drupal 7, dans une configuration très classique.

strace à la rescousse

Un peu comme pour le débug du blog, j’ai eu recours à strace pour essayer de comprendre ce qui se passait. En effet, quand on regarde htop, la moitié du CPU est taggé « system ». Tiens donc. atop me confirme des entrées/sorties conséquentes. Le phénomène étant différent, j’ai procédé différemment avec strace :

Mmmmh, beaucoup de lstat, donc du disque (on voit d’ailleurs d’autres appels tous dans le même domaine), on sait que ça reste l’élément le plus lent d’une machine, et le « cloud » ne fait évidemment pas de miracles de ce côté-là. Mais pour quelle raison ?

Analyse de la configuration de PHP

J’ai voulu proposer l’activation de Zend Opcache, avant de découvrir qu’il était déjà en service. Je me suis donc tourné vers la configuration PHP, un phpinfo me permettant de noter ces deux lignes :

Ok, donc ça veut dire qu’il relit les métadonnées des fichiers pour vérifier s’il y a eu des modifications et ce toutes les 60 secondes. le phpinfo n’étant pas assez détaillé pour moi je me tourne vers une page de status un peu comme celle qui existe pour APC :

opcache_statusAvec 660 fichiers ( j’ai pris la capture après avoir résolu le problème, il y avait plus de 900 scripts qui étaient étaient chargés), et un délai avant revalidation de 60 secondes seulement, autant dire qu’il passait son temps à lire les fichiers sur le disque, consommant massivement des ressources CPU.

La solution n’est pas venu d’Opcache

Non, en effet, je me suis tourné vers une autre recherche sur Google (parce que c’est celui qui est configuré sur mon poste au boulot, et ça convient dans ce cadre en particulier), avec les termes « php reduce lstat syscalls ». Et là, j’ai découvert un article en anglais évidemment très intéressant, qui détaille qu’il existe un « bug » si on utilise open_basedir.

Si open_basedir est activé/défini, pas de realpath_cache, qui est un cache/tampon qui enregistre le chemin des fichiers pour une durée déterminée (par la variable realpath_cache_ttl) plutôt que revalider systématiquement tous les chemins de tous les fichiers. Et quand on dit tous les chemins, c’est que pour chaque fichier, il va vérifier chaque étape dudit chemin. Par exemple, si vous stockez votre index.php dans /home/sites_web/client/www/index.php, il va faire un lstat sur /, puis sur /home, puis sur /home/sites_web/, et ainsi de suite jusqu’au fichier en lui-même. et ça pour tous les fichiers, et on a vu qu’il y en avait plus de 900 enregistrés alors.

J’ai donc désactivé l’open_basedir, et le load est retombé à 2. Boum. La solution à portée d’un « ; » pour commenter une ligne dans un fichier php.ini.

La magie du Avant/Après

La magie du Avant/Après

 

La même avec le load

La même avec le load

La raison : la sécurité

Pour rappel, open_basedir permet de restreindre l’exécution de PHP dans un dossier en particulier, une sorte de chroot version PHP en somme. Le realpath cache, lui, mémorise les chemins et les droits d’accès associés pour tous les scripts qu’il doit lire, et ce afin de ne pas recalculer les droits et chemins réels à chaque fois.

Vous comprenez le souci, si on valide un chemin, qui est dans l’open_basedir, qu’on le met en cache pendant un certain temps, il ne sera pas revérifié si ce chemin est remplacé par un lien symbolique situé en dehors du « chroot », permettant par la même occasion d’exécuter du code sans contrôle, ce qui est effectivement un problème de sécurité. Le détail technique toujours en anglais est présenté dans ce rapport de bug qui explique la situation, et les raisons du choix de désactiver le cache  realpath si open_basedir est défini.

Aller plus loin dans l’optimisation opcache

Avec tout ça je me suis quand même mangé deux heures de lecture à propos du Zend Opcache et de ses bénéfices ainsi que des bonnes pratiques à adopter pour une utilisation en production. Si j’ai le courage un jour j’en ferai une traduction, mais vous pouvez vous pencher sur cet article déjà pour commencer à faire joujou avec l’Opcache. Et si vous n’en utilisez pas encore un, il est peut-être temps de s’y mettre.

 

3 Commentaires
Le plus ancien
Le plus récent
Commentaires en ligne
Afficher tous les commentaires
Cyril
Cyril
17/09/2018 17:10

Très très bon article ! Par contre en voulant tester la commande strace (avec l’ensemble des paramétres comme ci-dessus) il demande le pid, comment savoir lequel lui fournir ?

Cyril
Cyril
18/09/2018 08:46
Répondre à  Seboss666

Merci pour ta réponse. Je commence à utiliser strace depuis peu et tu t’en doutes par besoin 😀 Il m’a déjà dépatouillé et permis d’accélérer mon application par 3 juste en m’indiquant que gettext c’est lent et qu’il vaut mieux un tableau php :p. Mais le format que tu utilises est très intéressant. Merci 🙂