80 WordPress, un seul OPcache
Héberger les sites de nos clients nous amène quotidiennement à relever des défis techniques, aujourd'hui j'ai envie de v…
LireDans 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 |
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.
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 %.
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.
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.
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 :
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.
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_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.pm.status_path, également en FastCGI direct) et la mémoire de ses workers dans /proc.L'alerte « OPcache plein » s'est déclenchée dans l'heure qui a suivi sa mise en service.
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 :
opcache.max_accelerated_files, alors réglé à 200 000 ;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.
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é.
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.
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.
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.opcache_get_status(), sans outil dédié.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).
Création de site internet, e-commerce ou référencement : agence web à Paris, Besançon et Normandie.
Devis gratuitHéberger les sites de nos clients nous amène quotidiennement à relever des défis techniques, aujourd'hui j'ai envie de v…
Lire