80 WordPress, un seul OPcache : onze jours en production

Dans la première partie, un patch OPcache stockait une seule fois le bytecode identique de 80 sites WordPress, et divisait presque par deux la mémoire du cache sur un banc d'un million de requêtes. Depuis le 29 octobre, il tourne en production sur notre serveur mutualisé. Voici ce qu'il a donné, et ce que les graphes nous ont appris en chemin, dont une bonne partie ne concerne pas le patch.

Étude de cas · PHP 8.4 · OPcache

Cet article fait suite à l'article 80 WordPress, un seul OPcache

Vitalyn · novembre 2026 · PHP 8.4.27, patch opcache-dedup

Indicateur Résultat Détail
Mémoire OPcache −43 % 5,4 Go de bytecode tiennent en 3,1 Go
Fonctions partagées 82,5 % 1,9 million sur 2,3 millions pointent vers un bloc déjà stocké
Crashs de worker 0 aucun SIGSEGV, SIGBUS ni SIGABRT en 11 jours, toutes versions
Taux de succès du cache 99,96 % 2,7 millions de requêtes PHP 8.4 sur les 4 derniers jours

Ce qui tourne

Le serveur héberge 74 pools PHP-FPM, répartis sur plusieurs versions de PHP. Chaque site a son utilisateur Linux et son pool ; chaque version de PHP a un seul master FPM, donc un seul OPcache partagé par tous ses pools. La version 8.4 en porte 52 : c'est elle qui a reçu le dedup.

Les paquets PHP sont ceux de la distribution habituelle, recompilés avec le patch. Rien ne change tant que la directive est absente. L'activation tient en un fichier, chargé après la configuration OPcache du paquet :

; /etc/php/8.4/fpm/conf.d/99-opcache-dedup.ini
opcache.enable_dedup = 1
opcache.jit = disable

Le déploiement s'est fait en deux temps : une semaine sur le serveur de développement (dedup actif sur plusieurs versions depuis le 22 octobre, aucun incident), puis la production, le 29 octobre à 9 h 34, sur la seule version 8.4.

Mémoire : −43 %, comme au banc

Deux mesures indépendantes donnent le même chiffre.

De l'extérieur. Un relevé toutes les cinq minutes lit la partie résidente du segment OPcache du master 8.4, sans passer par PHP. Avant le 29 octobre, elle oscillait entre 4,3 et 4,5 Go. Avec le dedup, même configuration, mêmes sites : 2,5 Go. Soit −43 %.

Mémoire OPcache du master PHP 8.4 en production, avant et après le dedup
Partie résidente du segment OPcache du master 8.4, relevée toutes les 5 minutes. Les creux sont des redémarrages de PHP-FPM. Le 5 novembre, la hausse vient de l'agrandissement de la table des clés, expliqué plus bas.

De l'intérieur. opcache_get_status() compte la mémoire que le dedup a évité de copier. Ajoutée à la mémoire utilisée, elle donne ce qu'occuperait le même contenu sans le patch : aujourd'hui 3,1 Go utilisés et 2,3 Go économisés, soit 5,4 Go sans dedup. Le gain est de 43 %, et surtout, sans le patch, ce cache ne tiendrait plus dans ses 5 Go : il saturerait à chaque remplissage.

OPcache PHP 8.4 : mémoire utilisée, et ce qu'elle serait sans dedup
Mémoire OPcache utilisée (opcache_get_status(), toutes les 10 minutes) et estimation sans dedup (utilisée + économisée). Chaque chute est un vidage complet du cache : on y revient plus bas.

Le banc annonçait −46,5 % sur 80 copies de la même pile. La production mélange davantage de versions de WordPress et d'extensions : 82,5 % des fonctions compilées y trouvent un bloc identique déjà stocké, et le gain reste du même ordre.

Stabilité : aucun crash attribuable au patch

En onze jours, aucun worker n'est mort en SIGSEGV, SIGBUS ou SIGABRT, sur aucune version. Les logs des semaines précédentes, sans dedup, en contiennent trois, entre le 28 septembre et le 22 octobre.

Le serveur a pourtant connu deux épisodes de saturation mémoire, le 30 octobre et le 4 novembre, où le noyau a tué MariaDB et des workers PHP. Nous les avons analysés en détail, parce que le doute est légitime après une mise en production :

  • la mémoire OPcache est restée stable pendant les deux épisodes (entre 2,6 et 2,9 Go) ;
  • ce qui a grossi, c'est la mémoire privée des workers : 196 workers 8.4 à environ 230 Mo chacun, sur 52 pools autorisés à monter à 32 workers chacun ;
  • la tendance existait avant le dedup, alimentée par la migration des sites vers 8.4.

Le dedup réduit la mémoire partagée ; il ne touche pas à la mémoire de requête de chaque worker. C'est un problème de dimensionnement des pools, que les graphes décrits plus bas nous permettent maintenant de corriger pool par pool.

Les graphes : ce qu'ils ont révélé

Pour suivre la mise en production, il fallait voir OPcache et les pools en continu, version par version, sans exposer quoi que ce soit sur le web.

  • OPcache. Un petit script PHP, déposé hors des sites, renvoie opcache_get_status() au format Prometheus. Il n'est pas joignable par HTTP : un relevé l'appelle chaque minute directement en FastCGI, sur le socket d'un pool de chaque version, comme le ferait le serveur web. Il refuse toute requête qui arrive par un serveur web.
  • Pools. Le même relevé lit la page de statut de chaque pool (pm.status_path, également en FastCGI direct) et la mémoire de ses workers dans /proc.
  • Tableaux de bord. Le tout part dans notre monitoring, avec un tableau OPcache par version, un tableau PHP-FPM par pool et quatre alertes : cache plein, requêtes en attente d'un worker, pool à sa limite, mémoire PHP élevée.

L'alerte « OPcache plein » s'est déclenchée dans l'heure qui a suivi sa mise en service.

1. Le cache était plein, mais pas de mémoire

Le tableau OPcache montrait la version 8.4 « pleine » avec 2,7 Go libres sur 5. En cause, deux limites que le dedup avait fait passer devant la mémoire :

  • la table des clés : 262 237 entrées sur 262 237. Elle se dimensionne avec opcache.max_accelerated_files, alors réglé à 200 000 ;
  • le tampon des interned strings : 64 Mo sur 64.

Une fois l'une de ces limites atteinte, OPcache cesse de mettre en cache les nouveaux scripts : ils sont recompilés à chaque requête. Nous avons porté max_accelerated_files à 1 000 000 et interned_strings_buffer à 256 Mo le 5 novembre. Quatre jours plus tard, la table des clés est à 32 %, mais les interned strings sont déjà à 91 % : elles seront la prochaine limite.

La leçon vaut pour toute mise en place du dedup. En libérant de la mémoire, il permet de mettre davantage de scripts en cache, et les autres limites d'OPcache arrivent plus vite. Il faut les dimensionner ensemble : clés, interned strings et mémoire.

2. Des extensions vident le cache de tous les sites

Le graphe de la mémoire montre des chutes à intervalles irréguliers : sept vidages complets du cache 8.4 en quatre jours, dont deux vers 3 heures du matin. Ce ne sont ni des redémarrages ni des saturations : le compteur manual_restarts les attribue à des appels à opcache_reset().

Cette fonction est présente dans le code de plusieurs extensions WordPress courantes, de cache ou de gestion à distance, qui peuvent l'appeler après une mise à jour ou un vidage de leur propre cache. Sur un serveur où un master sert 52 sites, l'action d'un seul site vide le cache des 51 autres, qui recompilent tout dans les minutes qui suivent.

Ce comportement n'a rien à voir avec le dedup : c'est OPcache natif. La parade aussi : opcache.restrict_api, qui réserve l'API d'OPcache à un répertoire d'administration. Nous la recommandions déjà pour fermer l'énumération des scripts des autres sites (partie 1) ; la production montre qu'elle protège aussi la disponibilité.

3. La table de dedup a de la marge

La table qui référence les blocs partagés contient 404 109 entrées pour 786 432 utilisables (51 %), soit 1,8 bloc unique par script en cache. Sa taille dépend de max_accelerated_files et plafonne à 2²⁰ emplacements de 32 octets, soit 32 Mo. Sur le serveur de développement, plus petit, elle avait atteint 93 %. Une table pleine ne casse rien : les nouveaux blocs ne sont simplement plus partagés. Mais ce constat plaide pour une taille réglable, une question que nous poserons aux mainteneurs.

4. Dimensionner les pools sur des mesures

Le tableau PHP-FPM donne, pour chaque pool, le pic de workers actifs, le nombre de fois où la limite a été atteinte et la mémoire réelle (PSS) de ses workers. Sur les 74 pools du serveur, tous autorisés à monter à 32 workers, 15 n'en ont jamais utilisé plus de deux en deux jours de relevés, et cinq seulement ont touché la limite. Le réglage uniforme laissait chaque petit site réserver de quoi faire tomber le serveur. C'est le chantier en cours pour en finir avec les saturations mémoire.

Ce que la production change à la proposition

  • Le gain se confirme hors du banc : −43 % sur un parc réel et hétérogène, mesuré de deux façons.
  • Aucun crash en onze jours, et les deux incidents du serveur sont expliqués sans le patch.
  • La documentation doit insister sur les autres limites d'OPcache : max_accelerated_files et interned_strings_buffer deviennent le goulot avant la mémoire.
  • opcache.restrict_api est indispensable dès que plusieurs sites partagent un master, dedup ou pas.
  • La taille de la table de dedup devrait pouvoir se régler, ou se dimensionner sur la mémoire plutôt que sur le nombre de scripts.
  • Les compteurs exposés suffisent à l'exploitation : mémoire économisée, remplissage de la table et partage se suivent avec opcache_get_status(), sans outil dédié.

Et maintenant

Le dedup reste actif sur 8.4 et sera étendu à 8.2, 8.3 et 8.5 une fois les pools redimensionnés. Côté PHP, le patch est prêt pour la branche de développement ; nous le proposons aux mainteneurs, avec ces chiffres de production en plus de ceux du banc.


Mesures du 21 octobre au 9 novembre 2026 sur notre serveur de production (PHP 8.4.27, 52 pools).

Vous avez un projet web ?

Création de site internet, e-commerce ou référencement : agence web à Paris, Besançon et Normandie.

Devis gratuit

Articles similaires