Table des matières :
- Benchmarks PrestaShop : ce qu’on mesure avant d’optimiser PHP-FPM, OPcache et MySQL
- PHP-FPM : dimensionnement des pools, timeouts, sockets et protection contre les fuites
- OPcache : réduire le CPU “gratuit” et stabiliser les déploiements
- MySQL (InnoDB) : buffer pool, logs, plans d’exécution et pièges PrestaShop
- Benchmarks comparatifs, validation en production et rollback sans downtime
Benchmarks PrestaShop : ce qu’on mesure avant d’optimiser PHP-FPM, OPcache et MySQL
Un plan de benchmark PrestaShop crédible ne commence pas par « augmenter pm.max_children » au hasard. Il commence par isoler où part le temps : latence réseau, négociation TLS, temps applicatif PHP, I/O disque, et temps MySQL. Sur une boutique PrestaShop 8.1–9.1 (Symfony 6.4 côté backoffice / endpoints modernes), la majorité des pages non cachées (produit, panier, checkout) sont un mix de CPU PHP (construction du contexte, hooks, templates), I/O (autoload + fichiers Smarty/Twig, images si mal proxifiées) et requêtes SQL (catalogue + prix + stock + règles panier). Tant que vous n’avez pas une vision p50/p95/p99 et une ventilation (APM/slowlog), vous optimisez à l’aveugle.
Concrètement, mesurez au minimum : TTFB, RPS (throughput), p95/p99 (latence), taux d’erreur, CPU steal (VM), RSS moyen par worker PHP-FPM, InnoDB buffer pool hit ratio et temps cumulé MySQL par requête.
Pour cadrer la démarche autour du TTFB, web.dev (Google) décrit le Time To First Byte comme un indicateur de la réactivité côté serveur : c’est donc une métrique pivot pour savoir si votre goulot est “avant PHP” (réseau/TLS/proxy) ou “pendant PHP/MySQL”. Ressource utile et stable : web.dev — Time To First Byte. Si votre TTFB est déjà bas (par ex. ~200 ms sur pages non cachées, au plus près de votre audience), pousser encore PHP-FPM sans toucher à la DB ou au cache applicatif donnera souvent peu de gains. Pour cadrer la démarche TTFB côté PrestaShop, gardez sous la main ce guide interne : TTFB PrestaShop : réduire le Time To First Byte sous 200 ms.
Un benchmark exploitable doit aussi documenter le contexte, sinon vos résultats sont non-reproductibles :
- Géolocalisation de la mesure : testez depuis une zone cohérente avec votre clientèle (ex. France/Belgique/Suisse). Une boutique majoritairement française hébergée en UE verra des écarts sensibles entre un test lancé depuis Paris/Francfort et un test lancé depuis la côte ouest US (RTT + TLS).
- État des caches : “cold cache” (première requête après purge) vs “warm cache” (après un warmup). Les deux sont utiles, mais ne répondent pas aux mêmes questions.
- Variantes fonctionnelles : page produit simple vs produit avec déclinaisons, règles de prix spécifiques, cross-selling, avis, modules marketing. Ce sont souvent ces “extras” qui font exploser p95/p99.
- Mode de session : client anonyme vs connecté (et, idéalement, avec un panier). Les cookies, le contexte groupe, les règles panier et la personnalisation changent le coût applicatif.
- Front vs backoffice : le BO PrestaShop (listes commandes/clients/produits, filtres) peut être plus coûteux que le FO et déclencher des requêtes très différentes.
Voici une grille de lecture simple (à adapter à votre SLO, votre trafic et votre stack cache/CDN), utile pour éviter d’interpréter des métriques en silo :
| Indicateur | À regarder surtout en… | Pourquoi c’est actionnable |
|---|---|---|
| TTFB p95/p99 | FO non caché + tunnel | Détecte la file d’attente FPM / contention MySQL / I/O |
| RPS stable à erreurs constantes | pages GET & POST | Valide la capacité avant dégradation (et pas juste “un pic”) |
listen queue FPM |
charge soutenue | Montre la saturation avant que les 5xx n’explosent |
| % de “miss” OPcache | charge soutenue | Signale un OPcache sous-dimensionné (recompiles en boucle) |
Threads_running MySQL + temps de requêtes |
tunnel + BO | Montre contention/locks et requêtes lentes en contexte réel |
| Temps en I/O (disque) | cold cache | Détecte un problème de stockage, de logs, ou de buffer pool trop petit |
Pour les outils, évitez ab (trop limité) et privilégiez k6 ou wrk/wrk2 avec scénarios réalistes : pages catégorie et produit (GET), ajout panier (POST), étape adresse/transport/paiement (POST), recherche (GET, souvent coûteux). Important : simulez des sessions et des cookies, sinon vous mesurez une page anonyme “idéale” qui ne reflète pas le tunnel. Gardez un protocole reproductible (mêmes données, même warmup, même durée, même ramp-up). Pour la partie observabilité, l’article PrestaShop performance : monitoring, tests de charge et runbooks soldes vous donne une base de runbook et de métriques à suivre en prod.
Enfin, pour éviter le piège “on a doublé le trafic, donc on a gagné”, reliez vos chiffres à une loi simple : si votre latence augmente fortement quand vous montez la concurrence, vous observez souvent une file d’attente (FPM, MySQL, I/O). Dans ce cas, le bon levier n’est pas nécessairement “plus de workers”, mais moins de temps par requête (profiling, cache, index, réduction de hooks) ou meilleure isolation (séparer DB et web, limiter modules coûteux, etc.).
PHP-FPM : dimensionnement des pools, timeouts, sockets et protection contre les fuites
Sur PrestaShop, PHP-FPM est souvent le premier goulot d’étranglement “visible” parce qu’un pool sous-dimensionné crée des files d’attente (latence qui explose) et un pool surdimensionné tue le serveur par pression mémoire (swap, OOM killer) ou par contention CPU. Le cœur du problème : estimer combien coûte un worker et déduire pm.max_children de la RAM réellement disponible. Méthode simple (à faire après warmup et trafic réaliste) :
# mesurer la mémoire RSS des workers FPM (exemple pool www)
ps -ylC php-fpm8.3 --sort:rss | awk '{sum+=$8; n++} END {print (sum/n)/1024 " MB RSS moyen"}'
Si un worker moyen prend 140–220 MB RSS (classique sur PrestaShop chargé en modules), et que vous avez 6 GB réellement allouables à PHP (après Nginx/OS/MySQL), votre pm.max_children réaliste est souvent entre 20 et 35, pas 100. Dans la doc PHP-FPM, pm.max_children correspond au plafond du nombre de processus enfants : opérationnellement, c’est votre plafond de concurrence PHP sur les pages non cachées (et donc un plafond de capacité… tant que MySQL suit).
Pour ne pas “calculer au doigt mouillé”, complétez la mesure RSS avec deux vérifications rapides :
- CPU : si, à charge, votre CPU est proche du plafond et que la latence s’étire, ajouter des workers peut empirer (contexte switching + contention).
- Swap : dès que le serveur swappe, la latence p95/p99 se dégrade brutalement. Dans ce cas, réduire
pm.max_childrenaméliore parfois plus que l’augmenter.
Sur la plupart des boutiques, pm = dynamic reste un bon compromis (évite les latences de spawn de ondemand, tout en limitant l’empreinte au repos). Ajoutez pm.max_requests (ex. 500–2000) pour recycler les workers et mitiger les fuites mémoire de certains modules (c’est fréquent, et le core ne vous protégera pas). Activez le slowlog FPM pour capturer les scripts qui dépassent un seuil et vous donner une liste de “hot paths” :
; /etc/php/8.3/fpm/pool.d/www.conf
pm = dynamic
pm.max_children = 28
pm.start_servers = 6
pm.min_spare_servers = 6
pm.max_spare_servers = 12
pm.max_requests = 1000
request_terminate_timeout = 60s
request_slowlog_timeout = 3s
slowlog = /var/log/php8.3-fpm/www-slow.log
catch_workers_output = yes
Deux réglages méritent d’être pensés “business” (pas seulement technique) :
request_terminate_timeout: protège votre infra contre les requêtes qui partent en vrille (module qui boucle, endpoint qui attend une ressource externe…). Trop bas, vous coupez des checkouts légitimes ; trop haut, vous laissez une requête bloquer un worker longtemps.pm.max_requests: plus il est bas, plus vous recyclez (donc moins de risque de fuite), mais plus vous payez des coûts de réchauffage (autoload, caches en mémoire, etc.). Sur PrestaShop, 500–2000 est souvent un bon terrain d’observation ; ajustez ensuite avec la courbe RSS dans le temps.
Enfin, évitez les configs “par défaut” sur le transport FPM : sur un seul hôte, préférez un socket Unix à TCP pour réduire la latence et la charge CPU (et parce que TCP ouvre la porte à des erreurs de routage/ACL inutiles). Exemple Nginx :
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_read_timeout 60s;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
Le point dur (et souvent ignoré) : sans monitoring du statut FPM, vous ne voyez pas la saturation avant l’incident. Activez pm.status_path et exposez-le derrière une ACL interne ; vous pourrez suivre listen queue, active processes, max children reached. Ça vous évite de “tuner au feeling” et de découvrir en soldes que le pool plafonne.
Checklist pragmatique (utile avant une période type soldes / Black Friday) :
- Vérifier que
max children reachedreste exceptionnel (sinon vous êtes déjà au plafond). - Vérifier que
listen queuene monte pas en continu pendant les pics (sinon file d’attente). - Vérifier que les 5xx ne coïncident pas avec la saturation (sinon c’est un problème de capacité, pas “juste” des erreurs applicatives).
- Coupler slowlog FPM + slow query log MySQL : une requête lente DB peut “coûter” plusieurs workers FPM en même temps.
OPcache : réduire le CPU “gratuit” et stabiliser les déploiements
PrestaShop charge beaucoup de fichiers PHP (core, overrides, modules, vendor), et les stacks modernes (Composer + Symfony parts) accentuent l’effet. Sans OPcache correctement dimensionné, vous payez en continu : parse/compile PHP à chaque requête, hausse CPU, hausse TTFB, et comportements erratiques sous charge. La documentation PHP décrit OPcache comme un mécanisme qui conserve en mémoire partagée le bytecode précompilé afin d’éviter les recompilations : pour une boutique à trafic significatif, ce n’est pas une “option”, c’est un prérequis.
Les deux erreurs classiques : mémoire OPcache trop faible (d’où des évictions et recompilations) et maxacceleratedfiles sous-dimensionné (d’où des scripts non mis en cache). Sur PrestaShop 8.1–9.1 avec plusieurs dizaines de modules, partir sur opcache.memory_consumption = 256 est souvent un minimum ; 512 est courant dès que le codebase grossit. Même logique pour opcache.max_accelerated_files : 100000 évite les mauvaises surprises. Exemple (PHP 8.3 en production) :
; /etc/php/8.3/fpm/conf.d/10-opcache.ini
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=512
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=100000
opcache.max_wasted_percentage=10
opcache.jit=0
opcache.validate_timestamps=0
opcache.revalidate_freq=0
Le point “politique” ici : validate_timestamps=0 est la configuration qui donne généralement les meilleurs résultats, mais elle impose un processus de déploiement propre (reload FPM ou invalidation OPcache), sinon vous servez du code obsolète. Le core PrestaShop ne vous garantit pas un cycle de déploiement atomique : à vous de mettre en place un systemctl reload php8.3-fpm (ou un restart contrôlé), et un pipeline qui reconstruit les assets / vide les caches Symfony/Smarty au bon moment. Pour recouper OPcache avec le reste de la stratégie cache, voir : Cache PrestaShop : Varnish, Redis, Memcached et OPcache côté serveur.
Côté “benchmarks”, OPcache est typiquement l’optimisation qui fait baisser le CPU et stabilise la latence. Sur des boutiques où OPcache était activé mais mal dimensionné (évictions), les gains observés sont souvent plus qualitatifs que spectaculaires : p95 qui arrête de dériver sous charge, et une réduction du taux de requêtes lentes.
Pour éviter de “croire” OPcache sur parole, suivez au moins ces signaux (en interne uniquement) :
- Ratio hits/misses : un OPcache sain a une montée de hits très rapide après warmup ; si les misses continuent à grimper fortement en régime établi, vous recompilez encore trop.
- Mémoire utilisée vs gaspillage : une valeur de “wasted” qui grimpe peut indiquer fragmentation et déclencher des redémarrages/évictions plus fréquentes.
- Nombre de scripts : s’il dépasse
opcache.max_accelerated_files, vous aurez mécaniquement du “non caché”.
Un cas concret (fréquent en contexte PrestaShop) : après ajout de modules + montée de la base vendor/, l’OPcache qui tenait à 256 MB commence à évincer. Résultat : aucun incident visible “immédiat”, mais sous charge vos p95/p99 se mettent à dériver. C’est typiquement le genre de régression que votre protocole A/B (baseline → changement unique) doit détecter.
MySQL (InnoDB) : buffer pool, logs, plans d’exécution et pièges PrestaShop
La performance MySQL sur PrestaShop se joue rarement sur “une requête magique”. Elle se joue sur : (1) la capacité à garder les données chaudes en RAM (buffer pool), (2) l’absence de scans inutiles (index), (3) la maîtrise des tables temporaires et des tris, et (4) la réduction des contentions (locks) sur stock/panier. La documentation MySQL explique que le buffer pool InnoDB met en cache données et index afin de réduire les lectures disque : c’est généralement le levier numéro 1 tant que vous n’êtes pas déjà “full RAM” sur les ensembles de travail.
Sur un serveur dédié à MySQL, viser 60–75% de la RAM en innodb_buffer_pool_size est une règle de départ raisonnable ; sur un serveur “tout-en-un” (web + DB), la contrainte est plus stricte, et il faut arbitrer avec PHP-FPM.
Exemple de base (MySQL 8.0+, à adapter à votre RAM/CPU, et à valider en staging). Note : sur les versions récentes, privilégiez la capacité redo (innodb_redo_log_capacity) plutôt que les anciens couples innodb_log_file_size/innodb_log_files_in_group.
# /etc/mysql/my.cnf
[mysqld]
innodb_buffer_pool_size=8G
innodb_buffer_pool_instances=8
innodb_redo_log_capacity=2G
innodb_log_buffer_size=64M
innodb_flush_method=O_DIRECT
innodb_flush_log_at_trx_commit=1
max_connections=200
skip_name_resolve=1
slow_query_log=1
long_query_time=0.5
log_queries_not_using_indexes=0
innodb_flush_log_at_trx_commit=1 est la valeur ACID stricte (flush durable à chaque commit). Passer à 2 peut améliorer les écritures (panier/commande) mais accepte un risque de perte de transactions sur crash OS : ce n’est pas un tweak “performance” neutre, c’est un choix de résilience. Même logique pour max_connections : le monter trop haut masque une saturation applicative (pool FPM trop large, absence de cache) et finit en temp tables + thrash CPU. Si vous voyez MySQL “bien” et PHP “mal”, c’est souvent que la concurrence PHP est trop agressive ; si vous voyez PHP “bien” et MySQL “mal”, c’est souvent que la DB encaisse trop de requêtes évitables (cache, index, N+1).
Pour le catalogue et les pages listant beaucoup de produits, les gains les plus nets viennent souvent des index et des plans d’exécution propres. PrestaShop génère des requêtes complexes (JOIN sur product, product_shop, stock_available, tables de prix spécifiques, etc.), et certains modules ajoutent des conditions qui cassent les index. Travaillez avec EXPLAIN ANALYZE, le slow query log, et si possible pt-query-digest. Pour la partie indexation et lecture de plans, vous avez déjà une ressource interne détaillée : Index MySQL : optimiser WHERE, JOIN et ORDER BY avec EXPLAIN.
Deux pièges “spécial PrestaShop” à surveiller (parce qu’ils gonflent vite p95/p99) :
- Tri/pagination : des
ORDER BYsur colonnes non indexées (souvent introduits par des modules de tri avancé) peuvent déclencher filesort + tables temporaires. Sous charge, ça se transforme vite en temp tables sur disque si la RAM n’est pas suffisante. - Règles panier & prix spécifiques : leur logique peut déclencher des requêtes supplémentaires par produit / par ligne de panier, et donc amplifier un petit problème en problème systémique au checkout.
Dernier point : la “performance MySQL” n’est pas qu’une question de paramètres. Sur PrestaShop, les problèmes de concurrence (stocks, réservations, paniers) finissent en verrous et en temps d’attente applicatif. Avant d’augmenter la puissance brute, vérifiez si vous avez un problème de stratégie de verrouillage et de synchronisation. Sur ce sujet, l’implémentation Redis (verrous, atomicité, prévention de concurrence) est souvent un levier plus propre que de “sur-tuner” InnoDB : Performance e-commerce : prévenir la concurrence sur les stocks avec Redis.
Benchmarks comparatifs, validation en production et rollback sans downtime
Un benchmark utile est un benchmark comparatif : baseline (A) → changement unique (B) → mesure → itération. Mélanger PHP-FPM + OPcache + MySQL en une seule fenêtre de maintenance vous empêche d’attribuer les gains (ou les régressions). Procédez en séquence : (1) OPcache (car impact immédiat et simple à rollback), (2) PHP-FPM (capacité et stabilité), (3) MySQL (plus risqué car restart + effets de bord). Entre chaque étape, rejouez les mêmes scénarios k6/wrk, comparez p95/p99, et contrôlez les courbes CPU/mémoire. Pour éviter les “faux gains”, testez aussi le backoffice (pages liste produits, commandes) qui est souvent plus lourd que le front.
Un moyen simple de rendre vos A/B lisibles : notez, pour chaque itération, un seul changement + un résultat attendu + un critère d’arrêt. Exemple concret :
- Changement :
opcache.memory_consumption 256 → 512 - Attendu : baisse des misses OPcache en régime établi
- Arrêt : si RSS total dépasse un seuil qui déclenche swap/OOM, rollback immédiat
En production, la validation doit inclure des garde-fous : taux d’erreur HTTP 5xx, erreurs PHP, saturation FPM (max children reached), saturation MySQL (threads running, temp tables on disk). Préparez un plan de rollback simple : un fichier .ini OPcache versionné, un pool FPM versionné, un my.cnf versionné, et des commandes de reload/restart documentées. Si vous n’avez pas déjà une discipline de “post-change control” sur PrestaShop (checkout + règles panier + moyens de paiement), vous allez payer tôt ou tard : Contrôle PrestaShop post-modification : checklist tunnel de commande et règles panier.
Enfin, n’oubliez pas la partie maintenance : optimiser sans nettoyer conduit à des résultats instables (tables qui grossissent, indexes fragmentés, caches incohérents). Programmez une routine : purge des paniers obsolètes, nettoyage des tables de logs, analyse/optimisation ciblée (pas un OPTIMIZE TABLE aveugle sur tout), vérification de l’espace disque et des partitions. Cette hygiène est documentée ici : Base de données PrestaShop : routine de maintenance et nettoyage automatisé. Une boutique “propre” rend les benchmarks reproductibles et évite de surcompenser par de la config serveur.
Si vous cherchez un ordre de grandeur : sur une boutique correctement cachée côté HTTP (Varnish/CDN) mais encore sensible sur les pages dynamiques, l’optimisation combinée OPcache + pool FPM cohérent + buffer pool InnoDB dimensionné se traduit généralement par une baisse notable du p95 et une meilleure tenue sous charge (moins d’effondrement lors des pics). Mais ce n’est pas automatique : les modules qui font du N+1, les hooks trop lourds, et les requêtes non indexées plafonnent tout tuning serveur. À ce stade, basculez du “tuning” vers le profiling (XHProf/Tideways/Blackfire ou APM) et corrigez les points chauds applicatifs — c’est souvent là que PrestaShop, dans son cœur et surtout via l’écosystème de modules, montre ses limites structurelles.
