Aller au contenu principal
Site en cours de refonte — quelques pages peuvent bouger ou évoluer.
16 mars 2026

Ton site PHP rame “au hasard” : OPcache est plein, et il churn en silence

Quand OPcache sature, ton site ralentit par vagues et tu cherches du côté du code. Souvent, c’est juste OPcache qui est plein et qui évince en boucle.

10 min de lecture
15 vues
réactions
Partager :
Ton site PHP rame “au hasard” : OPcache est plein, et il churn en silence

Le message qui met sur la piste, je l’ai surtout vu remonter côté WordPress, souvent via un hébergeur ou un outil de monitoring : « Opcache RAM set too small ». Et le symptôme qui va avec est sournois : le site ne rame pas “tout le temps”. Il rame par vagues. Un coup ça vole, un coup le TTFB explose, le CPU grimpe, puis ça “revient”.

Quand tu vois ça en prod, ne pars pas direct en chasse au plugin WordPress ou au dernier commit Laravel. Commence par suspecter un truc bête et ultra fréquent : OPcache est plein. Il se met à churn (évictions en boucle) et tu payes la recompilation des scripts PHP en silence, requête après requête.

OPcache, ce qu’il fait vraiment (et pourquoi ça peut faire mal)

OPcache garde en mémoire les scripts PHP compilés (opcodes). Sans lui, PHP relit et recompile des fichiers à longueur de requêtes. Avec lui, la majorité des hits servent directement des opcodes déjà prêts. Tant que tout rentre dans le cache, c’est simple : c’est rapide, et c’est stable.

Le jour où le cache est saturé, c’est une autre histoire. OPcache commence à évincer des scripts pour faire de la place. Et si ton trafic tape un mélange de pages (WordPress + plugins, back-office, cron, endpoints API, etc.), tu déclenches facilement un cycle pénible : tu charges de nouveaux scripts, tu en dégages d’autres, puis tu recharges ceux que tu viens de dégager. Résultat : tu recompiles beaucoup plus que tu ne le crois, et tu le fais au pire moment, sur le chemin des requêtes.

Les symptômes typiques du churn (ceux qui te font perdre du temps)

Le plus vicieux, c’est le côté “aléatoire”. Tu as des périodes où les pages sortent en 100–200 ms, puis d’un coup ça monte à 800 ms, 1,5 s, 3 s. Sur certaines stacks, tu vois même apparaître des 502 parce que PHP-FPM sature en workers, pas parce que l’app “plante”, mais parce qu’elle passe son temps à compiler.

Côté infra, ça se traduit souvent par un CPU qui grimpe sans raison évidente. Pas parce que MySQL est en feu, pas parce que Redis est mort, juste parce que PHP fait du boulot qu’il ne devrait quasiment jamais refaire. Et quand tu as un APM (New Relic, Datadog, Blackfire…), tu peux voir des temps “inexpliqués” dans la catégorie PHP runtime, pas forcément dans une fonction applicative précise.

Si tu as des logs, tu peux aussi tomber sur des messages explicites selon les environnements (ou des dashboards d’hébergeurs) indiquant que la mémoire OPcache est insuffisante, ou que le buffer des interned strings est plein. Ce dernier est un grand classique sur WordPress bien chargé.

Arrêter de deviner : les métriques OPcache qui tranchent

Le diagnostic propre, c’est d’aller lire l’état d’OPcache. Le signal le plus parlant, c’est quand tu vois opcache_statistics.cache_full passer à 1, ou des valeurs qui indiquent que le cache est plein et qu’il y a des “restarts” (selon config et version). Ensuite tu regardes la hit rate. Quand elle se casse la figure alors que ton trafic est normal, c’est rarement une coïncidence.

Tu veux aussi regarder si tu es au plafond côté nombre de scripts. num_cached_scripts qui colle à max_cached_keys, ça sent très fort le opcache.max_accelerated_files trop bas. Et si la partie mémoire est limite, tu le vois via memory_usage.used_memory proche de memory_usage.total_memory.

Dernier point qui surprend : les interned strings. Beaucoup de projets PHP modernes s’en sortent, mais WordPress + plugins + thèmes + stacks historiques peuvent remplir ce buffer assez vite. Quand interned_strings_usage.used_memory est proche du total, tu peux avoir un comportement instable et des perfs qui se dégradent sans que le cache “scripts” soit forcément le seul coupable.

Un endpoint JSON minimal (à protéger) pour lire l’état en prod

Si tu n’as pas d’APM qui expose déjà ces infos, je préfère souvent un endpoint interne très simple, accessible uniquement via VPN / IP allowlist / basic auth, le temps de diagnostiquer. Ne laisse pas ça ouvert sur Internet, OPcache donne pas mal d’infos sur ton appli.

<?php
// opcache-status.php — à protéger (IP allowlist, auth, etc.)

if (!function_exists('opcache_get_status')) {
    http_response_code(501);
    echo "OPcache not available\n";
    exit;
}

$status = opcache_get_status(false);

$out = [
    'cache_full' => $status['opcache_statistics']['cache_full'] ?? null,
    'hit_rate' => $status['opcache_statistics']['opcache_hit_rate'] ?? null,
    'num_cached_scripts' => $status['opcache_statistics']['num_cached_scripts'] ?? null,
    'max_cached_keys' => $status['opcache_statistics']['max_cached_keys'] ?? null,
    'used_memory' => $status['memory_usage']['used_memory'] ?? null,
    'total_memory' => $status['memory_usage']['total_memory'] ?? null,
    'interned_used' => $status['interned_strings_usage']['used_memory'] ?? null,
    'interned_total' => $status['interned_strings_usage']['total_memory'] ?? null,
];

header('Content-Type: application/json; charset=utf-8');
echo json_encode($out, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);

Avec ça, tu peux faire un “avant / après” propre, et surtout arrêter de t’auto-convaincre que « c’était peut-être un pic de trafic ». Tu regardes des chiffres, point.

Les réglages qui bougent vraiment (et ceux qui font perdre une heure)

Quand OPcache churn, tu as trois leviers qui reviennent dans 90% des cas. Le premier, c’est la taille du cache opcode avec opcache.memory_consumption. Si tu es à 64 ou 128 MB sur une app PHP qui a grossi, c’est souvent juste trop petit. Sur des WordPress chargés ou des Laravel avec pas mal de vendors, 256 MB est un point de départ courant. Parfois 512 MB n’a rien d’extravagant si tu as la RAM pour. Le but n’est pas de “gonfler pour gonfler”, c’est d’arrêter les évictions en boucle.

Le second, c’est opcache.max_accelerated_files. Ce setting est sous-estimé. Si tu as beaucoup de fichiers PHP (vendors, plugins, modules), tu peux remplir le cache en “nombre de scripts” avant même de le remplir en mémoire. Et quand tu touches le plafond, c’est du churn quasi assuré. Je préfère le régler franchement (en fonction de la volumétrie réelle) plutôt que de bricoler au petit bonheur.

Le troisième, c’est opcache.interned_strings_buffer. Là encore, WordPress est un bon exemple parce que tu peux te retrouver avec énormément de chaînes “internées”. Si ce buffer est trop petit, tu prends des comportements de saturation qui ressemblent à de la perf aléatoire. Passer de 8 à 16, voire 32 MB est souvent le correctif simple quand les métriques montrent que tu es au plafond.

Il y a aussi des réglages qui ne “réparent” pas un cache plein. Par exemple, jouer avec opcache.revalidate_freq ou opcache.validate_timestamps peut changer la façon dont les scripts sont rafraîchis lors des déploiements, mais ça ne va pas magiquement te donner plus de place. Si ton OPcache est juste trop petit, tu peux optimiser le refresh tant que tu veux, tu resteras dans la douleur.

Exemple de base (à adapter), juste pour se mettre d’accord

; php.ini / conf.d/opcache.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=100000

Les valeurs exactes dépendent de ton app, de ta RAM et de ton mode de déploiement. Mais l’idée est là : tu dimensionnes en fonction de la réalité (nombre de fichiers + mémoire réellement consommée), pas en fonction d’un default de distro.

WordPress, Laravel, Symfony : pourquoi ça touche tout le monde

WordPress a le combo parfait pour remplir OPcache vite : beaucoup de fichiers, beaucoup de plugins, et des chemins d’exécution qui varient selon les pages, le back-office, les hooks, les cron, les appels XML-RPC/REST, etc. Ça fait tourner beaucoup de scripts différents, donc ça met de la pression sur OPcache.

Laravel et Symfony sont moins “surprenants” au quotidien, mais dès que tu as un gros vendor/ + des features qui chargent des classes sur des chemins variés (jobs, console, queues, API, admin), tu peux très bien tomber dans le même piège. Et en dev/staging, tu ne le vois pas toujours parce que le trafic n’a pas la même diversité. En prod, ça se révèle d’un coup, avec de vrais utilisateurs, et tu te retrouves à suspecter tout sauf le cache.

Ce que je demande à un hébergeur (ou à l’équipe infra) quand ça arrive

Si tu n’as pas la main sur la conf PHP, tu peux aller vite en formulant une demande claire. Je demande qu’on me donne l’état OPcache (ou qu’on me confirme qu’ils l’ont regardé), et je demande un ajustement ciblé : augmenter opcache.memory_consumption, augmenter opcache.interned_strings_buffer si le buffer est plein, et vérifier opcache.max_accelerated_files par rapport au nombre réel de scripts en cache.

Et je précise un point important : il faut idéalement éviter le “on a redémarré et ça va mieux”. Évidemment que ça va mieux juste après un restart, tu viens de vider le cache et tu repars dans une phase où tu n’as pas encore touché les plafonds. Ce que tu veux, c’est un fix qui tient quand le cache est “à chaud”, plusieurs heures après, avec du trafic.

Vérifier que c’est réellement réglé (sans se raconter d’histoires)

Après changement, je veux voir cache_full rester à 0. Je veux voir une hit rate haute et surtout stable. Et je veux que num_cached_scripts arrête de taper un plafond artificiel. Sur la partie mémoire, c’est normal de consommer beaucoup si tu as dimensionné correctement, mais je ne veux pas être collé à 99,9% en permanence avec des symptômes de latence en dents de scie.

Je regarde aussi le TTFB sur une fenêtre assez large. Pas dix minutes après le déploiement, mais plutôt sur une demi-journée, voire une journée si le trafic est cyclique. Le churn se voit souvent quand tu as des variations d’usage (pic du matin, back-office à certaines heures, jobs qui se déclenchent), donc il faut mesurer au bon moment.

Dernier avertissement : quand tu touches OPcache, tu vas souvent provoquer un “évènement” de cache cold (reload PHP-FPM, restart, rotation). Fais-le proprement, et pas en plein pic. Et si tu as plusieurs serveurs, vérifie que la conf est homogène. Un seul nœud mal dimensionné suffit à te donner une perf “au hasard”, parce que ton load balancer va te faire jouer à la roulette.

Mon avis (assez sec) : OPcache, c’est de la perf “invisible”… jusqu’au jour où ça casse

OPcache marche tellement bien qu’on l’oublie. Et c’est exactement ça le piège : on ne le monitor pas, on ne le dimensionne pas, on le laisse avec des defaults. Puis un jour, l’app grossit, les plugins s’accumulent, le vendor enfle, et tu te retrouves avec une prod qui rame “sans raison”.

Si tu gères un site PHP en prod et que tu n’as pas, au minimum, un moyen de lire l’état OPcache, tu pilotes à l’aveugle. La bonne nouvelle, c’est que le fix est souvent bêtement simple. La mauvaise, c’est qu’il faut accepter que ce n’était pas “un petit glitch réseau”. C’était ton cache qui hurlait en silence.

Sources

Cet article vous a plu ?

Commentaires

Laisser un commentaire

Entre 10 et 2000 caractères

Les commentaires sont modérés avant publication.

Aucun commentaire pour le moment.

Soyez le premier à donner votre avis !