Cache PHP7 : performance

Cache PHP7 : performance jpp

Note 2022 : Drupal 9 ne fonctionne pas (ou très mal) avec le cache et génère des tonnes d'erreurs dans les logs, se connecter comme utilisateur devient impossible car le moteur PHP génère des tas d'erreur. 

Je n'ai jamais réussi à utiliser de manière stable "opcache" avec les versions <= 7.0 de PHP, en testant le passage de "Stretch" à "Buster" j'ai vu que la version de PHP était la 7.3 j'ai été tenté de vérifier ce que pouvait apporter ce cache.

Dans un premier temps un test un peu "brutal" qui permettra de mesurer rapidement l'apport d'Opcache. 
Un article suivant permettra de mesurer le gain à espérer dans le "monde réel". 
 

Opcache : préalable

Opcache : préalable jpp

Précisions sur l'environnement de test. 
Je dispose donc de deux Machines Virtuelles KVM identiques à la version près, une Stretch (PHP7.0) et une Buster (PHP7.3), équipées de Apache/Mariadb et des mêmes applications. 
J'ai donc réalisé quelques essais comparatifs. Je dispose d'un petit injecteur (Python simple) capable d'interroger les sites avec une liste d'Url en notant pour chaque accès le temps de réponse de la requête. Ces requêtes sont des requêtes "simple" n'impliquant qu'un accès, c'est bien plus léger puisque aucune image ni Javascript, tout provient donc "en direct" de la base de données et du moteur du CMS. 
Lors d'un premier passage on constate que MariaDB effectue pas mal de lecture ( #1600Ko ), après ce premier passage tout se passe en mémoire grâce au cache de InnoDB (320Mo) car le test ne concerne que 419 pages du site. De plus ce phénomène survit à un reboot gràce au fichier "/var/lib/mysql/ib_buffer_pool". On peut le vérifier en : 
1) Stopper MariaDB 
2) Supprimer le fichier ib_buffer_pool, non celà n'est pas dangereux ! 
3) Relancer MariaDB 
4) les tests suivants montrent que la base effectue beaucoup de lectures. 
Le premier test est réalisé avec un petit programme Python qui appelle successivement les pages du site, le seul accès est réalisé sur la page (pas de CSS, Images ...). 
Pour le test "évolué" il sera fait appel à Selenium et ses "Webdrivers" afin d'avoir des accès sur les CSS, JS et images. 
Les deux versions (Stretch et Buster) se comportent de manière quasiment identique : aucune analyse graphique ne donne l'avantage à l'un ou à l'autre et l'écart global de temps de réponse (sur 419 pages) est inférieur à 1/10 de seconde sur un total de plus de 8 secondes. Plusieurs passes (avec redémarrage) on donné des résultats similaires. 
On est donc ainsi sûrs que, si l'on réalise les mesures qu'après un "tour de chauffe", on mesurera des performances CPU en faisant abstraction des entrées/sorties.

Opcache : test brutal

Opcache : test brutal drupadmin

Le premier test consiste à utiliser le cache d'opcode de PHP (Opcache), je n'avais pas réussi à le faire fonctionner de manière stable en PHP <= 7.0, voyons comment PHP7.3 se comporte. 
Opcache comporte énormément de paramètres (plus de 30 !), c'est presque une usine à gaz à lui tout seul. Le manuel (http://php.net/manual/en/book.opcache.php) est bien fait, extrêmement volumineux, mais fournit un modèle "recommandé" :

opcache.enable=1 
opcache.memory_consumption=128 
opcache.interned_strings_buffer=8 
opcache.max_accelerated_files=4000 
opcache.revalidate_freq=60 
opcache.fast_shutdown=1 
opcache.enable_cli=1             # inutile ici

Il est possible d'invalider la sauvegarde des commentaires "opcache.save_comments=0" et d'activer "opcache.enable_file_override" afin d'améliorer  (très peu) les performances mais si votre site utilise les "Annotations" de PHP il est impératif d'autoriser la sauvegarde des commentaires ("opcache_save_comments=1") car les annotations sont contenues dans les commentaires..... je suis tombé dans ce piège ! 
Il semble conseillé d'utiliser le chemin complet de la librairie, pour moi : "/usr/lib/php/20180731/opcache.so". Un répertoire "daté" me semble un truc à surveiller ? 
Il semble par ailleurs intéressant (au moins au début des tests) de positionner : 
"opcache.log_verbosity_level" au maximum (3 ou 4) pour les tests. 
"opcache.file_update_protection" à 2 pour du développement et à 0 pour de la production. 
"opcache.huge_code_pages" à 0 si vous n'avez pas de "huge pages", à 1 sinon cela est censé améliorer les performances. 
"opcache.lockfile_path" peut être positionné dans /tmp. 
"opcache.file_cache" permet d'installer un cache sur disque, il faut dans ce cas lui donner le nom d'un répertoire à écriture autorisée pour l'utilisateur Apache, mais je n'ai pas encore réussi à faire fonctionner cette fichue option ! 
"opcache.file_cache_only" permet d'utiliser (0) la mémoire partagée pour le cache,(1) n'utilise que le cache disque, cela peut être utile pour des machines avec peu de mémoire. 
"opcache.preload" permet de charger un script au démarrage du serveur, si ce script "include" d'autres scripts ceuc-ci seront aussi compilés au départ. 
Je vais donc essayer avec :

opcache.enable=1 
opcache.memory_consumption=128 
opcache.interned_strings_buffer=8 
opcache.max_accelerated_files=4000 
opcache.revalidate_freq=60 
opcache.fast_shutdown=1 
opcache.save_comments=0 
opcache.log_verbosity_level=3 
opcache.file_update_protection=0 
opcache.lockfile_path="/tmp"

On verra plus tard pour utiliser le cache disque. 
Premier test : cache en mémoire partagée seul. 
Ce test ne réalise que l'accès "brut" aux URL du site, il est effectué sur les 419 pages du site de test. Afin de limiter l'influence des Entrées/sorties les tests ont été effectués plusieurs fois et seuls le dernier résultat de chaque série a été pris en compte. 
Deux séries de test ont été effectuées : 
1) Délai entre accès de 500 millisecondes. 
- Sans cache : on atteint une charge CPU d'environ <10%. 
- Avec cache : on atteint une charge CPU d'environ <5%% (peu visible !). 
2) Délai entre accès de 50 millisecondes 
- Sans cache : charge CPU #40% 
- Avec cache : charge CPU #20% 
(somme des temps de réponse en secondes) : 
        Sans           Avec          DELTA       Gain %        
1) 16,216021    7,682984    8,533037    52,62 % 
2) 17,812893    9,082634    8,730259    49,01 % 
Le gain de temps de #50% est très significatif. Le graphique suivant montre bien le temps de réponse avec cache (en rouge) et sans cache (en bleu), la surface en rouge est bien plus petite que celle en bleu. 
Graphique "radar" des performances relatives

A suivre tests plus réalistes avec accès plus proche de la réalité (chargement javascripts, css, images) à l'aide de Selenium et Katalon.

Opcache : test évolué

Opcache : test évolué jpp

J'ai voulu réaliser quelques tests avec Selenium, que j'ai déjà utilisé dans le passé, mais l'IDE accessible par Firefox ou Chrome ne génère plus de Python ... quel dommage. 
J'ai trouvé une autre possibilité : passer par le plugin "Katalon" qui, lui génère du bon Python facile à utiliser et "bricoler". 
Et voici les résultats. 
A partir du script Python généré par Katalon, lisible et simple à comprendre j'ai pu en y intégrant un peu de code bâti autour de la carte du site (sitemap.xml pour ne pas la citer) j'ai réussi à faire fonctionner deux scripts (un utilisant Firefox, l'autre Chrome) parcourant la carte du site en sens inverse. 
Ce test a été réalisé en lançant trois fois à 15 secondes d'intervalle les deux scripts en "background), l'affichage étant réalisé devant mes petits yeux ébahis j'ai pu vérifier que la vitesse de chaque script était légèrement supérieure à une page par seconde : #320 secondes pour #420 pages les six scripts demandaient donc à peu près 6 pages par seconde. 
Le système ayant fonctionné avant ces tests on peut supposer que tous les buffers systèmes étaient bien remplis. Ce qui est confirmé par l'absence d'I/O disques durant ces tests. 

 
La somme des "Deltas" légèrement positive semble indiquer un léger "mieux" lors de l'utilisation de Opcache, ceci est peut-être confirmé par les résultats des deux premiers tests défavorables à OPcache car il faut "compiler" le code et le stocker dans des structures mémoire ce qui doit coûter un peu de temps. 
Au niveau du CPU c'est plus net, l'utilisation de Opcache fait baisser l'usage CPU. 
Le graphe suivant présente le %CPU utilisé (en bleu et en rouge) et l'avantage de Cache par rapport à Non_cache, or cet avantage semble le plus souvent positif et montre une "économie" de CPU. 
 

Graphe usage CPU cache/nocache
Graphe usage CPU

On remarque bien qu'au début (de 0 à 50 secondes) l'avantage du cache est faible puis se stabilise aux environs de 15% avec des pointes au delà de 20%. 
Au niveau de la mémoire le cache "consomme" de la mémoire (ici le cache est limité à 128Mo), je vais refaire un essai avec un cache plus important : 256Mo par exemple. 
 

Graphe occupation mémoire
Graphe usage mémoire

On voit bien (en rouge) l'augmentation de l'empreinte mémoire et la baisse de la taille des buffers. 
En combinant le tableau de durée des tests et celui de l'usage CPU on voit qu'avec Opcache cela va "un peu plus vite" tout en consommant nettement moins de CPU. 
Ces tests ont été réalisés avec 128Mo de mémoire consacrés à Opcache j'ai voulu voir si en augmentant cet empreinte mémoire il y avait quelques choses à gagner. J'ai donc passé la taille du cache de 128M à 256M (et forcé la taille mémoire en conséquence !) et refait un test. 
Il s'avère qu'au niveau de la mémoire 128M, dans mon cas, étaient suffisants. Avec 256Mo de cache la mémoire occupée est sensiblement égale à la fin du test, voir graphe ci-dessous :

Occupation mémoire opcache 128M et 512M
Occuparion mémoire cache 128M et 256M

Il est probable que 128Mo suffisent pour le logiciel utilisé, cette valeur est donc à tester pour chaque site pour bénéficier de performances optimales ou à utiliser, au moins, le cache "fichiers".

Remarque : 
Pendant les tests avec 6 threads (3 Firefox et 3 chrome) la machine exécutant les tests (Core i7-6700 CPU @ 3.40GHz avec 16Go) était très chargée en CPU #70/80%. J'ai essayé avec 8 threads et le ventilateur CPU a commencé à vrombir car on dépassait les 90% de CPU. Les navigateurs commandés par les Webdrivers sont donc très avides de CPU ! 
Tester ainsi de gros sites demande certainement une bonne batterie de clients costauds ...

Opcache : cache fichier

Opcache : cache fichier jpp

J'ai rencontré des problèmes "idiots" pour la mise en place du cache qui m'ont retardé dans mes tests. 
Profitant de quelques jours de congés dans le centre j'ai effectué quelques recherches et fini par me rendre compte que seul l'endroit où je voulais installer le répertoire de cache (/var/tmp/opcache) posait problème, même avec tous les bons attributs. J'ai alors essayé d'utiliser le répertoire "/home/opcache" dûment autorisé par un "chown www-data:www-data /home/opcache" et ... miracle cela fonctionne dès le restart d'Apache. Je n'ai pas encore compris l'astuce ! 
Le répertoire "/home/opcache" se peuple rapidement d'un répertoire au nom abscons : "78e784e1b844f19ba1d9d3014253c3f0" , il sera différent ailleurs, contenant un arbre de répertoires correspondant à l'arbre des fichiers PHP des applications installées, le nom change par ailleurs à chaque nouvelle version du logiciel. Les "anciennes" versions peuvent être éliminées sans danger. 
Pour mémoire les paramètres "opcache" utilisés :

Paramètres Opcache
opcache.enable=1 
opcache.memory_consumption=128 
opcache.interned_strings_buffer=8 
opcache.max_accelerated_files=10000 
opcache.max_wasted_percentage=5 
opcache.use_cwd=1 
opcache.validate_timestamps=1 
opcache.revalidate_freq=2 
opcache.save_comments=1 
opcache.enable_file_override=1 
opcache.consistency_checks=20 
opcache.error_log="/var/log/apache2/opcache.log" 
opcache.log_verbosity_level=4 
opcache.file_cache = "/home/opcache" 
opcache.validate_permission=0

L'option "save_comments" est absolument nécessaire si vos applications utilisent des "annotations" car celles-ci se cachent, lâchement, au milieu des commentaires ...et sans les annotations les programmes peuvent ne plus fonctionner ou peuvent ne plus fonctionner nromalement.
L'option "use_cwd" est nécessaire afin d'éviter la collision si deux scripts PHP présents dans deux répertoires différents portent le même nom.

Et ça marche. 
Quelques mois plus tard :

  • La charge CPU est réduite
  • Les temps de réponse sont excellents.