Table des matières :
- Mesurer le problème : requêtes SQL et hooks, même combat (PrestaShop 8.2/9.x, PHP 8.2+)
- Instrumentation pragmatique : profiler, logs SQL, et trace des hooks
- Réduire le volume de requêtes : N+1, surcharge objet, et mauvaises pratiques modules
- Optimiser les requêtes restantes : index, EXPLAIN, et nettoyage de patterns SQL coûteux
- Domestiquer les hooks : cartographie, suppression, et remplacement par des points d’extension plus sobres
- Cache applicatif : réduire SQL et hooks sans créer une bombe à invalidation (Redis recommandé)
- Refactor modules et garde-fous : éviter le retour des requêtes et hooks toxiques
Mesurer le problème : requêtes SQL et hooks, même combat (PrestaShop 8.2/9.x, PHP 8.2+)
Réduire les requêtes SQL et les hooks dans PrestaShop n’a de sens que si vous savez où et combien vous payez. Les boutiques « lentes » que je vois en 2026 ont rarement un seul coupable : c’est souvent un empilement de micro-coûts (N+1 SQL + hooks bavards + absence de cache applicatif). Comme l’a formulé Donald E. Knuth : « Premature optimization is the root of all evil. » (1974). Traduction opérationnelle : commencez par instrumenter, ensuite seulement vous coupez.
Avant de toucher au code, fixez un baseline simple et comparable (staging idéalement, ou prod à trafic faible) :
- TTFB (Time To First Byte) : très corrélé au « temps serveur » (PHP + DB + cache). Google considère généralement un TTFB bon à ≤ 0,8 s. Référence : article web.dev sur le TTFB
- Nombre de requêtes SQL + temps cumulé SQL : ce n’est pas la même chose (200 requêtes ultra rapides peuvent être moins graves que 20 requêtes qui full-scan).
- Top pages (celles qui font le CA et celles crawlées) : home, catégories fortes, fiches produits best-sellers, panier, checkout, et 2–3 pages BO (commandes, catalogue).
- P95/P99 : ne regardez pas uniquement la moyenne ; les “pics” font souvent les abandons panier.
Petite règle de réalité terrain (utile pour prioriser, surtout si DB/WEB ne sont pas sur la même machine) : si votre latence réseau entre PHP et MySQL est de 8–12 ms (classique si DB dans une autre zone/datacenter), alors 300 requêtes peuvent ajouter mécaniquement 2,4–3,6 s rien qu’en aller-retours, avant même de compter le temps CPU/IO. D’où l’intérêt de réduire le volume de requêtes avant de micro-optimiser chaque SELECT.
Côté SQL, activez des métriques exploitables côté MySQL/MariaDB : slow query log, durée, rows_examined, et fréquence. Sur un hébergement où vous avez la main (VM/Docker), le combo le plus utile reste : slow log + pt-query-digest (Percona Toolkit) + un profilage applicatif ponctuel (Xdebug/Blackfire). Si vous êtes sur mutualisé, vous devrez souvent vous rabattre sur : logs applicatifs (PrestaShop), requêtes « suspectes » identifiées via APM, et audit d’index. Référence interne utile : Audit MySQL/MariaDB : optimiser les requêtes lentes e‑commerce et Logs Amazon Lightsail : activer general log et slow query MySQL.
Côté hooks, il faut une cartographie par page (home, catégorie, produit, panier, checkout, BO). Le cœur PrestaShop exécute des hooks en cascade, et un seul module « innocent » peut déclencher : requêtes SQL, IO disque (templates), appels HTTP (tracking), et parsing de données (JSON, XML). Si vous avez déjà une méthode d’audit reproductible, gardez-la comme référence : Audit performance PrestaShop : méthode reproductible et preuves terrain. Le but ici : relier un hook → un module → un coût (SQL/CPU).
Pour rendre ce lien actionnable, notez dès le début (dans un tableur ou un ticket) :
| Page | Hook dominant | Module(s) impliqué(s) | Symptôme | Indice de priorité |
|---|---|---|---|---|
| Catégorie | displayProductPriceBlock |
pricing/avis/reco | explosion SQL | haute |
| Produit | actionFrontControllerSetMedia |
tracking/AB test | CPU + assets | moyenne |
| Checkout | displayPaymentTop |
paiement | latence + erreurs | critique |
Ce “journal” vous évite de vous perdre dans une optimisation sans fin.
Instrumentation pragmatique : profiler, logs SQL, et trace des hooks
En environnement de staging (jamais en prod), activez le mode debug PrestaShop et utilisez le profiler Symfony quand il est disponible (BO, et une partie du front selon versions/modules). Pour PrestaShop 9.x (Symfony 6.4 côté BO, PHP 8.2+), le profiler vous donne rapidement : temps contrôleur, appels services, et surtout volumes de requêtes quand Doctrine est impliqué (BO). Sur le front, vous êtes encore très souvent sur l’ancienne couche (Db::getInstance()), donc le profiler Symfony ne suffit pas.
Concrètement : vous cherchez les plans d’exécution qui montrent ALL (full scan), des rows anormalement élevés, et des index non utilisés. C’est le point de passage obligé dès que vous suspectez une requête catalogue (catégorie/produit) ou une requête de facettes.
Quelques réglages pragmatiques (si vous avez la main) qui rendent le slow log exploitable :
long_query_timesuffisamment bas en staging (ex. 0.2–0.5s) pour capturer les requêtes “limites”.log_queries_not_using_indexesavec prudence (beaucoup de bruit), utile ponctuellement.- Sur MySQL 8, ne négligez pas
performance_schema: même si vous ne faites pas de tuning poussé, ça aide à comprendre qui exécute quoi quand un module dégénère.
Pour tracer les hooks, deux approches fonctionnent sans tout casser :
- APM (New Relic, Datadog, Blackfire) : vous voyez la stack PHP, donc souvent
Hook::exec()et les méthodes du module. - Instrumentation applicative : wrapper léger autour de
Hook::exec()ou ajout de logs ciblés (en module, pas en override) pour mesurer temps et compte SQL.
Exemple minimaliste (staging) : mesurer le temps d’un hook et journaliser.
// PrestaShop 8.2/9.x - PHP 8.2+ (staging)
$start = microtime(true);
$out = Hook::exec('displayHeader');
$ms = (microtime(true) - $start) * 1000;
PrestaShopLogger::addLog(sprintf('Hook displayHeader: %.1f ms', $ms), 1);
Ça ne vous donne pas le détail module par module, mais ça permet d’identifier les hooks dominants (souvent displayHeader, displayTop, displayFooter, actionFrontControllerSetMedia, displayProductAdditionalInfo, etc.) et de prioriser.
Checklist rapide pour éviter de profiler “dans le vide” :
- Mesurer 3 fois la même page (cache chaud/froid) et garder le pire cas.
- Tester en navigation réelle (catégorie → produit → ajout panier → checkout) : c’est là que certains modules déclenchent des “rafales” SQL.
- Séparer Front / BO : les causes (Doctrine vs legacy, listes BO, filtres) ne sont pas les mêmes.
Réduire le volume de requêtes : N+1, surcharge objet, et mauvaises pratiques modules
Le pattern le plus destructeur en PrestaShop reste le N+1 : une liste (produits, déclinaisons, features, images) puis, pour chaque item, une requête additionnelle. Sur une catégorie à 48 produits, un module peut vous ajouter 48×(feature + stock + image) si son code reconstruit le monde au lieu d’exploiter les données déjà disponibles dans le contexte.
Un exemple courant côté modules : dans un hook de listing, charger un objet Product complet pour chaque produit juste pour lire 1 champ. C’est une anti‑stratégie, parce que new Product($id, ...) déclenche plusieurs requêtes, et potentiellement des chargements multilingues/multiboutiques. À la place : préférez une requête ciblée (un SELECT sur ps_product_lang ou un champ custom), ou récupérez le champ depuis le tableau déjà construit par le contrôleur. Si vous devez enrichir le listing, faites-le en batch : une requête WHERE id_product IN (...).
Mini-scenario très classique (et très coûteux) : un module “badges marketing” veut afficher “Livraison 24/48h” et “Meilleure vente” sur un listing.
- Version naïve : pour chaque produit, il recharge
Product, puis vérifie stock, puis lit une table custom → N+1. - Version “batch” : il reçoit la liste d’IDs déjà affichés, fait un SELECT sur sa table custom, et s’appuie sur les infos stock déjà calculées par le core (ou les charge en une fois).
Autre problème classique : « requêtes de configuration » répétées. Des modules qui font Configuration::get('KEY') dans chaque hook, parfois plusieurs fois, sur chaque requête HTTP. C’est encore plus pénible si des valeurs volatiles sont stockées dans ps_configuration (ex. état panier), car vous ajoutez du write/lock et du bruit cache. Pour ce point précis, gardez la référence : PrestaShop configuration : éviter l’état panier dans la table ps_configuration.
Une optimisation simple (souvent oubliée) consiste à mettre un cache in-memory par requête côté module : la première lecture Configuration::get() charge, les suivantes lisent une propriété (ou un static). Ça ne remplace pas Redis, mais ça élimine des dizaines d’appels identiques dans le même cycle HTTP.
Cas terrain (catégorie, PrestaShop 8.2, MySQL 8.0, 35 modules actifs) :
- Avant : ~420 requêtes SQL / page catégorie, TTFB ~1,4 s.
- Après suppression de 2 N+1 et mise en cache des données de 3 hooks : ~140 requêtes, TTFB ~0,55 s.
Ce n’est pas « magique » : c’est l’effet mécanique de réduire les multiplicateurs (N × hooks × appels config).
- une hausse “disproportionnée” du nombre de requêtes sur une page qui ne devrait pas bouger (ex. page produit stable),
- des requêtes qui se répètent à l’identique (même SQL, mêmes paramètres) dans le même chargement.
Pour élargir la méthode de diagnostic TTFB/bots, voir : PrestaShop lent : diagnostic TTFB, bots et optimisation serveur.
Optimiser les requêtes restantes : index, EXPLAIN, et nettoyage de patterns SQL coûteux
Une fois le volume réduit, vous vous retrouvez avec moins de requêtes, mais certaines restent chères. Là, vous ne négociez plus : EXPLAIN, index, et réécriture. Référence interne à garder sous la main : Index MySQL : composer, mesurer avec EXPLAIN et optimiser les requêtes et, si vous êtes en migration, MySQL migration 5.7 vers 8 : procédure, staging et tests de charge.
Typologies de requêtes PrestaShop qui méritent quasi systématiquement un audit :
- Catalogue :
ps_product,ps_product_lang,ps_category_product,ps_stock_available,ps_specific_price. - Recherche / facettes :
ps_facetedsearch_*(et parfois des sous-requêtes infâmes si le module est mal configuré). - BO volumineux : listes commandes/clients avec filtres et jointures.
Sur ps_specific_price, par exemple, l’absence d’index composite adapté à vos filtres (id_product, id_customer, id_group, id_shop, from, to) se traduit par des scans et des temps erratiques. Attention : ajouter des index « au hasard » sur une boutique active est un risque (temps de build d’index, locks, consommation disque). Faites-le en staging, mesurez, puis planifiez une fenêtre de déploiement.
Pour interpréter EXPLAIN sans se mentir, gardez ce mini mémo :
| Signal EXPLAIN | Ce que ça signifie souvent | Action typique |
|---|---|---|
type=ALL |
scan complet | ajouter/ajuster index, réduire dataset via filtre |
rows très élevé |
beaucoup de lignes testées | revoir ordre des colonnes d’index, filtrer plus tôt |
Using temporary / Using filesort |
tri/agrégation coûteuse | index de tri, limiter ORDER BY, paginer correctement |
| index non choisi | cardinalité faible / mauvais ordre | index composite mieux aligné sur WHERE |
Astuce MySQL 8 utile en e-commerce (sans “bricolage” risqué) : les index invisibles. Ils permettent de tester en staging (ou sur une réplique) “que se passe-t-il si je retire cet index” sans le supprimer réellement. C’est particulièrement pratique quand on hérite d’une base avec des dizaines d’index redondants : vous pouvez valider qu’un index est inutile avant de l’enlever proprement.
Côté moteur, restez lucide : le cœur PrestaShop a encore des zones legacy qui n’optimisent pas parfaitement les requêtes et qui laissent les modules faire n’importe quoi. Votre job n’est pas de « tuner MySQL jusqu’au bout », mais de réduire ce qui tape la base inutilement, puis de solidifier ce qui reste. Pour compléter la partie serveur/cache (OPcache, PHP-FPM, Redis), la lecture utile est : Lenteur back-office PrestaShop : OPcache, PHP-FPM et Redis pour accélérer.
Domestiquer les hooks : cartographie, suppression, et remplacement par des points d’extension plus sobres
Un hook n’est pas « mauvais » en soi ; c’est un bus d’événements synchrone, exécuté dans le même cycle HTTP. Le problème, c’est la dérive : trop de modules branchés, sur des hooks très chauds, avec du code qui n’est pas idempotent et qui refait des requêtes déjà faites. Sur un front-office, les hooks typiques qui deviennent des goulets : displayHeader (assets + config + tracking), actionFrontControllerSetMedia (register JS/CSS), displayNav*, displayFooter, displayProductAdditionalInfo, displayProductPriceBlock, displayShoppingCartFooter.
La réduction la plus rentable est souvent « bête » : déshook ce qui est inutile page par page. Beaucoup de boutiques laissent des modules de paiement / livraison / CRM accrochés à des hooks globaux alors qu’ils ne servent qu’au checkout ou au BO.
Concrètement, dans une reprise, je vise généralement (au minimum) :
- sur catégorie : limiter tout ce qui enrichit chaque produit (avis, reco, badges) aux vrais besoins business,
- sur produit : garder ce qui sert à convertir (prix, dispo, livraison), et repousser le non-critique (widgets lourds) si possible,
- sur panier/checkout : réduire au strict nécessaire (paiement, anti-fraude, transport), éviter toute logique “marketing” qui requête en base.
Commencez par lister les associations hook ↔ module et désactivez au strict nécessaire. En migration/reprise, c’est exactement le genre de contrôle à faire : Migration PrestaShop : checklist d’audit thème, modules, hooks et overrides.
Deuxième levier : remplacer certains usages de hooks « display* » par des mécanismes plus sobres.
- Assets : au lieu d’injecter du JS/CSS dans
displayHeadervia du HTML concaténé, utilisezregisterJavascript/registerStylesheet(meilleure gestion, moins de duplication, plus compatible cache). - Données : évitez de recalculer des structures lourdes dans un hook si vous pouvez les calculer dans un contrôleur, puis les passer au template.
Troisième levier : limiter l’exécution du hook dans le module (garde-fou). Exemple : votre module est hooké globalement mais ne doit tourner que sur product.
// PrestaShop 8.2/9.x - PHP 8.2+
public function hookDisplayFooter($params)
{
$controller = $this->context->controller;
if (!method_exists($controller, 'getPageName') || $controller->getPageName() !== 'product') {
return '';
}
// ... logique utile uniquement sur fiche produit
}
Ce pattern ne réduit pas le nombre d’appels de Hook::exec(), mais réduit drastiquement la charge (SQL/CPU) déclenchée par le module.
Cache applicatif : réduire SQL et hooks sans créer une bombe à invalidation (Redis recommandé)
La meilleure optimisation SQL, c’est souvent de ne pas requêter. Mais un cache mal invalidé, c’est pire que lent : c’est faux. Sur PrestaShop 8.2/9.x, vous pouvez combiner : cache natif (Cache::store/Cache::retrieve), cache Symfony (si votre code est dans un contexte Symfony/Service), et un backend Redis pour la persistance. La stratégie globale est traitée ici : Cache PrestaShop : stratégie 2026 OPcache, Redis, Varnish et CDN ainsi que Cache PrestaShop : erreurs fréquentes et bonnes pratiques multiboutique.
Si vous développez sur PrestaShop 9.x, le composant Cache de Symfony est une base solide (PSR-6, adaptateurs, stratégies de pool). Référence : Symfony — composants Cache. L’idée pratique : isoler votre couche cache (clé, TTL, invalidation) pour qu’elle survive à une montée de version, au lieu de disséminer des Cache::store() partout.
Exemple de cache autour d’un enrichissement de listing (pseudo-code, à adapter) :
$key = 'mymodule|cat|' . (int)$idCategory . '|shop|' . (int)$this->context->shop->id . '|lang|' . (int)$this->context->language->id;
if (Cache::isStored($key)) {
return Cache::retrieve($key);
}
$data = Db::getInstance()->executeS('SELECT id_product, my_flag FROM '._DB_PREFIX_.'my_table WHERE id_category='.(int)$idCategory);
Cache::store($key, $data);
return $data;
Là où ça se joue vraiment : l’invalidation. Si my_table dépend d’un événement (MAJ produit, stock, prix spécifique), vous devez invalider sur les hooks d’écriture correspondants (actionProductSave, actionUpdateQuantity, actionObjectSpecificPriceAddAfter, etc.) ou, à défaut, utiliser un TTL court sur les pages sensibles.
Une façon simple d’éviter la “bombe à invalidation” : ne pas cacher des agrégats trop larges si vous ne savez pas les invalider proprement. Exemple : cacher un tableau “tout le pricing de la catégorie” est risqué si les prix spécifiques changent souvent ; cacher “un flag marketing stable” (ex. produit éligible à un badge) est beaucoup plus sûr.
Table de décision rapide (à adapter à votre contexte) :
| Donnée | Cache acceptable ? | Invalidation recommandée |
|---|---|---|
| Badges “Nouveau/Promo” | oui (TTL court si promo) | hooks produit / prix spécifique |
| Stock affiché | prudence | hooks quantité + TTL faible |
| Bloc CMS (footer) | oui (TTL long) | MAJ CMS / déploiement |
| Total panier / frais livraison | non (ou très expert) | trop d’entrées variables |
Sur un checkout, évitez de cacher ce qui touche au prix final sans une stratégie robuste (sinon vous créez des incohérences paniers). Pour rester concret sur la perf du checkout, voyez : Optimisation PrestaShop : audit performance checkout et plan d’action.
Refactor modules et garde-fous : éviter le retour des requêtes et hooks toxiques
Réduire aujourd’hui, c’est bien ; empêcher la régression demain, c’est le vrai gain. Le problème central en PrestaShop, c’est que les modules sont un terrain libre : n’importe quel prestataire peut livrer un module qui hooke partout, fait des requêtes non indexées, et surcharge des objets. Vous avez donc besoin de garde-fous techniques : revue de code, tests, et profilage en CI sur des parcours critiques.
Concrètement, si vous produisez ou maintenez des modules :
- Interdisez le
new Product()en boucle et imposez des méthodes batch. - Centralisez l’accès configuration (un
ConfigRepositoryinterne avec cache in-memory par requête). - Ajoutez une règle « pas de SQL dans
displayHeadersauf exception justifiée ». - Fixez un “budget performance” par page : par exemple page catégorie ≤ X requêtes et ≤ Y ms SQL (les valeurs dépendent de votre catalogue, mais l’idée est d’avoir une barrière anti-régression).
Pour industrialiser sans overrides, référez-vous à : Module PrestaShop sans override : méthode de cadrage, tests et livraison du code.
Dernier point, souvent négligé : la supervision. Si vous n’observez pas (TTFB, taux d’erreur 503, latence DB, saturation PHP-FPM), vous reviendrez au même état après quelques installs de modules. Les KPI à suivre et comment les relier aux incidents sont détaillés ici : Monitoring PrestaShop : KPI e-commerce, paiements et prévention des erreurs 503 et, en cas de dégradation forte, Erreur HTTP 503 : diagnostiquer PHP et logs Apache/LiteSpeed.
L’optimisation PrestaShop « réduire les requêtes SQL et les hooks » n’est donc pas une checklist magique : c’est une boucle mesure → réduction (N+1 / hooks inutiles) → optimisation (index/EXPLAIN) → cache (avec invalidation) → prévention (CI + monitoring). Tant que vous restez sur un socle modulaire, la discipline compte plus que le micro-tuning.
