Table des matières :
- Ce que l’index de recherche PrestaShop fait réellement (et pourquoi ça compte)
- Tables SQL de l’index :
ps_search_wordetps_search_index(schéma, clés, cardinalité) - Pipeline d’indexation : extraction, normalisation, stop-words, incrémental vs batch
- Pondérations : où elles sont stockées, comment elles se combinent, comment les tester
- Recherches partielles,
LIKEet pourquoi vos tables explosent (perf + pertinence) - Diagnostic reproductible : profiler les requêtes, trouver les goulots, corriger sans casser l’index
- Quand sortir du SQL : critères objectifs pour basculer vers Elasticsearch ou Meilisearch
Dans PrestaShop (8.1 à 9.x, PHP 8.1+), l’« index de recherche » n’a rien de magique : c’est un index inversé stocké en SQL, alimenté par un batch d’indexation et consommé par des requêtes JOIN + GROUP BY qui calculent un score de pertinence à base de pondérations. Tant que votre catalogue reste “raisonnable”, ça fait le job. À partir d’un certain volume (ou d’exigences type tolérance aux fautes/synonymes), le cœur montre vite ses limites — et c’est normal, ce n’est pas un moteur de recherche.
Deux symptômes reviennent souvent en prod (et sont directement liés à la façon dont l’index est construit) :
- latence qui augmente fortement dès que vous activez la recherche partielle, ajoutez des langues, ou enrichissez les fiches produit (descriptions plus longues, plus d’attributs) ;
- pertinence jugée “incohérente” par le métier (un produit qui “devrait” sortir en premier ne sort pas, ou l’inverse), parce que le scoring est principalement une somme de poids et non un ranking IR moderne.
Le cœur de PrestaShop construit un index inversé : au lieu de parcourir tous les produits pour chaque recherche, il stocke une correspondance mot → liste de produits avec un poids (score partiel). C’est le principe général des index inversés en recherche d’information (IR), tel que présenté dans Introduction to Information Retrieval (Stanford) : https://nlp.stanford.edu/IR-book/
Concrètement, PrestaShop :
- extrait des tokens à partir de champs produit (nom, référence, descriptions, attributs, features, tags… selon configuration et modules),
- normalise les chaînes (casse, accents, ponctuation),
- exclut certains mots (stop-words, mots trop courts),
- écrit le résultat dans des tables SQL dédiées.
Le ranking final est essentiellement une somme de poids : si le mot apparaît dans le nom produit, vous lui donnez typiquement plus de “valeur” que s’il apparaît dans une description longue. Dit autrement : vos données structurées et vos libellés (noms, attributs, références) influencent souvent plus la qualité de recherche que des micro-optimisations SQL.
Ce choix d’architecture implique trois conséquences techniques que les équipes sous-estiment souvent :
1) la pertinence dépend fortement des pondérations et de la tokenisation (pas d’analyse linguistique avancée par défaut : synonymes, lemmatisation/stemming, gestion fine du pluriel…),
2) la performance dépend du volume de la table d’index et des patterns de recherche (notamment la recherche “contient”),
3) la fraîcheur dépend de votre stratégie d’indexation (auto ou batch), donc du coût CPU/SQL que vous acceptez.
Mini-scénario (très courant en FR) : une boutique vend des “chaussures en cuir”. Si vos fiches produit ont des titres du type “Derby homme marron” et que “cuir” n’est présent que dans une description longue, vous aurez mécaniquement :
- des résultats “marron” très forts (si le mot est dans le nom),
- des résultats “cuir” plus faibles (si le mot est seulement dans la description),
- et potentiellement des “zéro résultat” si l’utilisateur cherche une variante orthographique ou un pluriel inattendu (“derbies”, “cuirs”, “chaussure cuir” vs “chaussures cuir”).
À garder en tête : l’index PrestaShop sait surtout répondre à “quels produits contiennent ces termes ?” et à “comment les trier via des poids simples ?”. Il ne sait pas nativement :
- corriger proprement les fautes (typos) à grande échelle,
- gérer des synonymes métier (“sneakers” ≈ “baskets”),
- appliquer un ranking probabiliste moderne (type BM25/Language Models),
- fournir un faceting/search analytics robuste avec coût stable.
Ce que l’index de recherche PrestaShop fait réellement (et pourquoi ça compte)
Le cœur de PrestaShop construit un index inversé : au lieu de parcourir tous les produits pour chaque recherche, il stocke une correspondance mot → liste de produits avec un poids (score partiel). C’est le principe général des index inversés en recherche d’information (IR), tel que présenté dans Introduction to Information Retrieval (Stanford) : https://nlp.stanford.edu/IR-book/
Concrètement, PrestaShop :
- extrait des tokens à partir de champs produit (nom, référence, descriptions, attributs, features, tags… selon configuration et modules),
- normalise les chaînes (casse, accents, ponctuation),
- exclut certains mots (stop-words, mots trop courts),
- écrit le résultat dans des tables SQL dédiées.
Le ranking final est essentiellement une somme de poids : si le mot apparaît dans le nom produit, vous lui donnez typiquement plus de “valeur” que s’il apparaît dans une description longue. Dit autrement : vos données structurées et vos libellés (noms, attributs, références) influencent souvent plus la qualité de recherche que des micro-optimisations SQL.
Ce choix d’architecture implique trois conséquences techniques que les équipes sous-estiment souvent :
- la pertinence dépend fortement des pondérations et de la tokenisation (pas d’analyse linguistique avancée par défaut : synonymes, lemmatisation/stemming, gestion fine du pluriel…),
- la performance dépend du volume de la table d’index et des patterns de recherche (notamment la recherche “contient”),
- la fraîcheur dépend de votre stratégie d’indexation (auto ou batch), donc du coût CPU/SQL que vous acceptez.
Mini-scénario (très courant en FR) : une boutique vend des “chaussures en cuir”. Si vos fiches produit ont des titres du type “Derby homme marron” et que “cuir” n’est présent que dans une description longue, vous aurez mécaniquement :
- des résultats “marron” très forts (si le mot est dans le nom),
- des résultats “cuir” plus faibles (si le mot est seulement dans la description),
- et potentiellement des “zéro résultat” si l’utilisateur cherche une variante orthographique ou un pluriel inattendu (“derbies”, “cuirs”, “chaussure cuir” vs “chaussures cuir”).
À garder en tête : l’index PrestaShop sait surtout répondre à “quels produits contiennent ces termes ?” et à “comment les trier via des poids simples ?”. Il ne sait pas nativement :
- corriger proprement les fautes (typos) à grande échelle,
- gérer des synonymes métier (“sneakers” ≈ “baskets”),
- appliquer un ranking probabiliste moderne (type BM25/Language Models),
- fournir un faceting/search analytics robuste avec coût stable.
Tables SQL de l’index : ps_search_word et ps_search_index (schéma, clés, cardinalité)
Dans une installation standard (préfixe ps_ par défaut), l’index vit principalement dans deux tables :
ps_search_word: dictionnaire des mots normalisés, par langue et généralement par boutique (multi-shop).ps_search_index: table de relation produit ↔ mot, avec un champweight(poids).
Sur un PrestaShop multi-langue, c’est ps_search_word qui porte le contexte (id_lang, et selon le schéma, id_shop). Le produit n’a pas besoin de stocker la langue dans ps_search_index : on remonte la langue via le JOIN sur ps_search_word. Avant d’écrire un seul script d’optimisation, commencez par vérifier le schéma réel de votre instance :
SHOW CREATE TABLE ps_search_word\G
SHOW CREATE TABLE ps_search_index\G
Même si les colonnes exactes peuvent varier selon versions, vous verrez généralement quelque chose de proche de :
| Table | Colonnes typiques | Rôle |
|---|---|---|
ps_search_word |
id_word, id_lang, id_shop, word |
dictionnaire de tokens normalisés |
ps_search_index |
id_product, id_word, weight |
postings : association produit ↔ mot + score partiel |
En pratique, ce qui vous intéresse pour la perf, ce sont les index et leur sélectivité :
ps_search_worda (souvent) un index unique sur(id_shop, id_lang, word)ou(id_lang, word).ps_search_indexest (souvent) indexée sur(id_word)et/ou(id_product).
Pour objectiver le volume (au lieu de l’estimer “à l’œil”), faites au minimum ces 3 mesures :
-- Taille logique
SELECT COUNT(*) AS rows_search_word FROM ps_search_word;
SELECT COUNT(*) AS rows_search_index FROM ps_search_index;
-- Top produits “les plus tokenisés” (souvent révélateur de descriptions surchargées)
SELECT id_product, COUNT(*) AS tokens
FROM ps_search_index
GROUP BY id_product
ORDER BY tokens DESC
LIMIT 20;
-- Taille et overhead InnoDB
SHOW TABLE STATUS LIKE 'ps_search_%';
Ordres de grandeur observés sur des catalogues e-commerce : si vous avez 50 000 produits et que chaque produit génère 30–80 tokens “utiles” (après stop-words/min-length/dédoublonnage), la table ps_search_index peut rapidement dépasser 2 à 4 millions de lignes. À ce niveau, un simple changement de configuration (min-length, champs indexés, recherche partielle) se traduit directement en IO, en CPU SQL et en taille d’index InnoDB.
Point de vigilance multi-boutique / multi-langue : le “coût” de l’index n’augmente pas seulement avec le nombre de produits, mais aussi avec :
- le nombre de langues réellement indexées,
- le degré de duplication des contenus (ex. descriptions traduites vs copiées-collées),
- le nombre de champs textuels activés (tags/attributs/features).
Pipeline d’indexation : extraction, normalisation, stop-words, incrémental vs batch
L’indexation PrestaShop est un batch applicatif : le code parcourt des produits, extrait des champs textuels, et “upsert” des mots dans ps_search_word puis écrit des couples (id_product, id_word, weight) dans ps_search_index. Le point important : la tokenisation du core est volontairement simple (split + normalisation). Pas de lemmatisation, pas de stemming, pas d’analyse linguistique avancée. Le résultat dépend donc fortement de la qualité des données (naming produits, références, attributs structurés).
Stop-words et longueur minimale : l’impact est massif
Même si le cœur reste simple, deux leviers changent drastiquement la taille et l’utilité de l’index :
- la longueur minimale des mots indexés (typiquement 2, 3 ou 4 caractères) : plus vous baissez, plus vous explosez le dictionnaire (
ps_search_word) et le nombre de postings (ps_search_index) ; - les stop-words par langue : en français, si vous laissez passer “de”, “en”, “pour”, “avec”, etc., vous polluez l’index avec des termes très fréquents et peu discriminants.
Selon les versions, PrestaShop utilise des fichiers de stop-words par langue (souvent dans config/stopwords_*.txt), ce qui permet d’adapter finement une boutique FR (sans sur-indexer des mots fonctionnels).
Normalisation, accents et collations SQL
La normalisation vise généralement à rendre le matching tolérant à la casse et aux accents. En pratique, si votre catalogue contient des variantes (ex. “café” vs “cafe”), vérifiez que le comportement est cohérent avec :
- votre collation MySQL/MariaDB (
utf8mb4_*, accent-insensitive vs accent-sensitive), - la normalisation côté PHP (suppression d’accents, translittération).
Les écarts de collation peuvent produire des surprises : le moteur peut considérer deux tokens “égaux” en comparaison mais les stocker différemment si la normalisation n’est pas alignée (et dans le pire cas, vous créez des quasi-doublons inutiles dans ps_search_word).
Incrémental vs batch : choisissez une stratégie “import-friendly”
Sur l’aspect “batch vs incrémental” : l’indexation manuelle (via back-office) limite le coût en production mais introduit une latence (produits récemment modifiés non trouvables). L’indexation automatique réduit cette latence mais peut mettre votre base à genoux si vous réindexez “trop” (ex. à chaque import massif).
Bon compromis (très fréquent quand on a un ERP/PIM) :
- import catalogue (créations / updates),
- nettoyage contrôlé (désactivation produits obsolètes, cohérence des références),
- indexation en fin de lot, en lots (batch) et idéalement en heures creuses.
Deux contrôles simples après import, avant de relancer une indexation complète :
-- Vérifier que vous n’avez pas un stock de produits “désactivés” encore massivement présents
SELECT active, COUNT(*) FROM ps_product GROUP BY active;
-- Repérer les produits sans nom dans une langue (souvent source de recherche “vide”)
SELECT pl.id_lang, COUNT(*) AS missing_name
FROM ps_product_lang pl
WHERE (pl.name IS NULL OR pl.name = '')
GROUP BY pl.id_lang;
Pour comprendre le code exact de votre version, la source de vérité reste le core. Exemple (branche 8.2.x) : classes/Search.php sur GitHub (à adapter selon votre version) : https://github.com/PrestaShop/PrestaShop/blob/8.2.x/classes/Search.php
Pondérations : où elles sont stockées, comment elles se combinent, comment les tester
Les pondérations (weights) sont stockées en configuration et injectées dans le calcul de ps_search_index.weight. En back-office, vous avez des sliders/inputs du type “Poids du nom produit”, “Poids de la référence”, “Poids de la description courte/longue”, “Poids des tags”, “Poids des attributs/features”… Les noms exacts des clés varient selon les versions/modules, mais vous pouvez lister ce qui existe dans votre instance :
SELECT name, value
FROM ps_configuration
WHERE name LIKE 'PS_SEARCH_%'
ORDER BY name;
Le score final n’est pas un BM25/Lucene-like. C’est une somme de poids sur les mots matchés, avec éventuellement des contraintes (exiger tous les mots vs au moins un). À titre de repère, Elasticsearch utilise BM25 par défaut (documentation officielle) : https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-similarity.html
PrestaShop, lui, fait du scoring “maison” beaucoup plus simple ; la pertinence dépend donc davantage de votre tuning et de la structure de vos données.
Comprendre “où part le score” sur un produit
Pour debugger la pertinence, le plus efficace est souvent de rendre visible ce que contient l’index pour un produit donné : quels mots ont été indexés, avec quels poids.
SELECT sw.word, si.weight
FROM ps_search_index si
JOIN ps_search_word sw ON sw.id_word = si.id_word
WHERE si.id_product = 1234
AND sw.id_lang = 1
AND sw.id_shop = 1
ORDER BY si.weight DESC, sw.word ASC
LIMIT 200;
Vous repérez immédiatement des cas typiques :
- un produit “pollué” par des mots génériques (ex. “livraison”, “garantie”, “qualité”) présents dans une description standard copiée sur tout le catalogue ;
- une référence produit mal formée (avec ponctuation/espaces) qui se tokenise de façon inattendue ;
- des attributs trop verbeux (“Couleur : Noir profond finition…”), qui injectent des tokens peu utiles.
Valeurs de départ (exemples) et logique métier
Sans prétendre à une “recette universelle”, une logique fréquente en e-commerce :
- Nom produit : poids élevé (c’est le champ le plus discriminant),
- Référence/SKU : poids élevé si votre clientèle cherche par référence (B2B, pièces détachées),
- Attributs/features : poids moyen (utile pour “couleur”, “matière”, “taille”, “compatibilité”),
- Description courte : moyen/faible,
- Description longue : faible (sinon vous favorisez les fiches les plus bavardes, pas les plus pertinentes).
Vous pouvez vous faire une “grille” simple avant même de toucher aux réglages (exemples indicatifs, pas des valeurs core) :
| Champ | Quand monter le poids | Risque si trop élevé |
|---|---|---|
| Nom | toujours | faible |
| Référence | recherche par SKU / compatibilités | remonte des produits “techniques” hors contexte |
| Attributs/features | catalogue très structuré | bruit si vos attributs contiennent du texte marketing |
| Description longue | produits à forte sémantique (livres, contenus) | favorise le blabla, gonfle l’index |
Méthode de test pragmatique
Vous changez une seule pondération à la fois, vous réindexez, puis vous mesurez l’impact sur :
- le CTR des suggestions,
- le taux de “zéro résultat”,
- la conversion post-recherche.
Le côté “mesure” n’est pas optionnel : sans instrumentation, vous faites juste du réglage à l’instinct. Pour une méthode orientée métriques (CTR, zéro résultat, conversion), voir : Recherche interne PrestaShop : mesurer CTR, zéro résultat et conversion.
Astuce “qualité” (peu coûteuse) : avant d’ajuster les poids, faites une mini-liste de 20 requêtes métier (top recherches + cas critiques) et notez, pour chacune, le top 5 attendu. C’est votre baseline. Sans baseline, vous ne saurez pas si vous améliorez la recherche… ou si vous déplacez le problème.
Recherches partielles, LIKE et pourquoi vos tables explosent (perf + pertinence)
La fonctionnalité la plus coûteuse côté SQL, c’est la recherche partielle (“contient”, “commence par”, etc.). Techniquement, si vous autorisez le matching sur des sous-chaînes, vous sortez du cas “lookup exact sur index B-Tree” et vous basculez sur des patterns LIKE qui peuvent provoquer des scans massifs sur ps_search_word (selon collation, longueur et wildcard). Résultat : latence qui monte en flèche dès que la table dépasse quelques centaines de milliers de mots.
Nuance importante :
- un
LIKE 'term%'(préfixe) peut parfois rester relativement exploitable selon l’index et la collation, - un
LIKE '%term%'(wildcard au début) est généralement beaucoup plus coûteux, car il empêche souvent l’utilisation efficace d’un index B-Tree classique.
Le compromis est brutal : la recherche partielle améliore la tolérance aux fautes “grossière” (ex. utilisateur qui tape un début de mot) mais dégrade rapidement la perf.
En production, vous voulez souvent déplacer cette tolérance en amont (suggestions live, corrections orthographiques) plutôt que de faire porter le coût au SQL de ranking. Sur ce point, l’optimisation des suggestions et de la tolérance aux fautes mérite un traitement séparé : Recherche interne PrestaShop : optimiser suggestions live et tolérance aux fautes.
Si vous devez malgré tout supporter la recherche partielle via SQL, vous avez deux angles de réduction de coût :
1) réduire la taille du dictionnaire (ps_search_word) : augmenter la longueur minimale des mots, renforcer les stop-words, limiter les champs indexés aux champs “à forte valeur” ;
2) réduire la pression IO/CPU : dimensionner correctement MariaDB/MySQL (buffer pool, CPU, stockage), et surtout vérifier que vous n’êtes pas en train de “thrash” (buffer pool trop petit, lecture disque permanente).
Checklist rapide (utile avant de “tuner” à l’aveugle) :
- la recherche partielle est-elle activée partout (front, suggestions, autocomplete), ou seulement sur un composant ?
- vos utilisateurs cherchent-ils vraiment des sous-chaînes (ex. “nike air” → “air”), ou plutôt des préfixes ?
- le nombre de langues indexées est-il justifié (ou hérité d’une configuration historique) ?
Sur les grosses boutiques, c’est rarement un problème “app” seulement : c’est souvent un problème de base sous-dimensionnée et/ou de requêtes non profilées.
Diagnostic reproductible : profiler les requêtes, trouver les goulots, corriger sans casser l’index
Commencez par rendre le problème observable : activez le profiling SQL côté PrestaShop et capturez les requêtes de recherche (front + suggestions). Le guide interne “debug profiling” est utile pour isoler les requêtes réellement coûteuses (et éviter de blâmer le cache au hasard) : PrestaShop debug profiling : activer et analyser performances SQL. Ensuite, côté base, activez un slow query log et corrélez avec les pics de trafic : Requêtes MySQL lentes PrestaShop : activer slow query log.
Une fois les requêtes identifiées, le travail “utile” se fait avec EXPLAIN/EXPLAIN ANALYZE et la vérification des index. Deux pièges récurrents :
- collation/charset qui empêche l’utilisation efficace des index (ou rend les comparaisons coûteuses),
- recherche partielle (
LIKE '%term%') qui rend l’index B-Tree quasiment inutile.
Trois actions “safe” et souvent rentables, sans toucher au code :
1) vérifier les index réellement utilisés sur la requête de recherche (pas seulement “ce qui existe” sur la table) ;
2) mesurer le coût du JOIN entre ps_search_word et ps_search_index (cardinalité, nombre de lignes lues vs retournées) ;
3) identifier les mots ultra-fréquents (stop-words manquants) qui font exploser les postings.
Exemple : repérer les mots qui matchent “trop” de produits (souvent de mauvais tokens à exclure) :
SELECT sw.word, COUNT(*) AS nb_postings
FROM ps_search_index si
JOIN ps_search_word sw ON sw.id_word = si.id_word
WHERE sw.id_lang = 1 AND sw.id_shop = 1
GROUP BY sw.word
ORDER BY nb_postings DESC
LIMIT 50;
Évitez aussi les “optimisations” destructrices du type TRUNCATE ps_search_* en production sans plan de rollback. Les opérations sur ces tables peuvent verrouiller, gonfler le redo log et saturer l’IO. Si vous devez reconstruire l’index, faites-le en fenêtre de maintenance, avec sauvegarde préalable et idéalement sur un clone (snapshot) pour estimer : temps d’exécution, taille finale, charge CPU.
Enfin, n’oubliez pas l’environnement : une recherche SQL peut être “correcte” mais paraître lente si le serveur est déjà sous pression (cache PHP, OPcache, contention CPU, disque saturé, etc.). Pour l’infra/caches (Redis/Varnish/OPcache) et les impacts sur un site e-commerce, vous avez un rappel complet ici : Cache PrestaShop : Varnish, Redis, Memcached et OPcache côté serveur.
Quand sortir du SQL : critères objectifs pour basculer vers Elasticsearch ou Meilisearch
L’index SQL du core est correct pour un catalogue petit/moyen, mais il n’est pas conçu pour : fuzziness sérieuse, synonyms, ranking contextualisé, faceting performant, analytics avancées, ni charge élevée avec une latence stable. Dès que vous avez des contraintes de type “tolérance aux fautes + suggestions + gros catalogue + multi-lang + trafic”, vous allez passer plus de temps à contourner les limites du core qu’à délivrer de la valeur.
La bascule vers un moteur dédié est surtout un choix de SLO (latence p95/p99, disponibilité, capacité à absorber les pics) et de features.
Pour décider sans débat “au feeling”, posez des critères concrets (exemples de signaux) :
- votre table
ps_search_indexatteint plusieurs millions (voire dizaines de millions) de lignes et la recherche devient un point chaud ; - vous avez besoin de typo-tolerance fiable (“adiddas” → “adidas”), pas uniquement du préfixe ;
- vous devez gérer des synonymes métier (très courant en français : “basket”/“sneaker”, “pull”/“sweat”, “imperméable”/“k-way” selon secteurs) ;
- vous voulez des analyzers par langue (gestion des accents, élisions “l’”, “d’”, pluriels) et un ranking plus robuste.
Deux points importants côté exploitation :
- dimensionnement et compatibilité runtime (versions Java pour Elasticsearch, RAM, stockage),
- synchronisation fiable (events, files d’attente, replays, backfill).
Pour cadrer proprement l’existant et éviter les erreurs de compatibilité, partez de vos prérequis système (PHP/MariaDB/Elasticsearch) : Exigences système : compatibilités PHP, MariaDB et Elasticsearch minimales. Ensuite, regardez des implémentations concrètes :
- côté Elasticsearch : PrestaShop ElasticSearch : accélérer la recherche produit sur grands catalogues
- côté Meilisearch (souvent plus simple à opérer) : Recherche interne PrestaShop : intégrer Meilisearch pour booster la conversion
Le bon critère de décision : si vous passez votre temps à “tuner des pondérations” pour corriger des problèmes linguistiques (typos, pluriels, synonymes) ou à “optimiser du LIKE” pour tenir la charge, vous êtes déjà dans le domaine des moteurs dédiés. L’index de recherche PrestaShop est un compromis SQL/legacy. Un moteur IR (Meilisearch/Elasticsearch) est une autre classe d’outil — et c’est précisément pour ça que vous obtenez de la pertinence et de la latence sous contrôle, au prix d’une stack plus complexe.
