Recherche lente sur PrestaShop : causes, filtres, mesure
Le module pour ce problème
Recherche Elasticsearch et Navigation à Facettes
Recherche avec le moteur intégré ou avec Elasticsearch, OpenSearch et Meilisearch, tolérance aux fautes de frappe, filtres à facettes et analyse des recherches sans résultat. Il s’installe avec le moteur intégré et un moteur externe se connecte lorsque c’est nécessaire. PrestaShop 1.7.7, 8 et 9, 12 mois de mises à jour inclus, puis renouvellement facultatif.
Comment cherche la recherche native de PrestaShop
Vérifié dans le code de PrestaShop 9.1.5 (classes/Search.php) et du module ps_facetedsearch 5.0.0 ; les fichiers figurent dans les sources.
L’index
PrestaShop ne cherche pas dans le texte des produits : il cherche dans un index qui lui est propre. La table search_word contient les mots du catalogue, distincts par langue et par boutique, et search_index relie chaque produit à chacun de ses mots avec un poids. Le poids dépend du champ (nom, références, descriptions, catégorie, marque, tags, attributs, caractéristiques, options PS_SEARCH_WEIGHT_*) et un poids de 0 exclut le champ de l’index. Les mots d’une longueur inférieure au minimum (PS_SEARCH_MINWORDLEN) ne sont pas indexés et les « Mots à ne pas indexer » (PS_SEARCH_BLACKLIST) sont écartés. L’index se met à jour à l’enregistrement d’un produit si l’indexation automatique est active et se reconstruit depuis Paramètres de la boutique, Rechercher, avec « Ajouter à l’index les produits manquants » et « Reconstruire l’index », ou avec l’URL pour le cron affichée sur la même page.
La recherche
Pour chaque mot saisi, Search::find interroge l’index avec une condition LIKE sur le mot. Par défaut, le joker se trouve seulement à la fin (« mot% »). Avec « Rechercher dans le mot » (PS_SEARCH_START), le joker apparaît aussi au début, et l’interface avertit que cela peut demander beaucoup de ressources ; avec « Rechercher la fin exacte » (PS_SEARCH_END), celui de la fin disparaît. Les produits trouvés pour chaque mot sont recoupés en PHP, puis une sous-requête additionne les poids de chaque produit pour la pertinence et une requête finale charge les résultats et en compte le total. Avec le tri par prix, l’ensemble est chargé et trié en PHP avant d’être paginé.
La recherche approximative
Si la recherche exacte ne trouve rien et que « Recherche approximative » (PS_SEARCH_FUZZY) est active, PrestaShop lit dans l’index les mots de longueur compatible (au-delà de 100 000 mots, seulement ceux de même longueur) et calcule en PHP la distance de Levenshtein par rapport au mot saisi, en acceptant le plus proche dans la limite d’une différence maximale (PS_SEARCH_FUZZY_MAX_DIFFERENCE, 5 par défaut selon l’interface). Les tentatives par recherche sont limitées par PS_SEARCH_FUZZY_MAX_LOOP, et l’interface avertit que l’option consomme des ressources.
Pourquoi elle ralentit quand le catalogue grossit
Ce sont des mécanismes, pas des chiffres : les temps dépendent du catalogue, des langues, des filtres et du serveur.
- Le joker au début. Selon la documentation de MySQL, une comparaison
LIKEne peut utiliser un index comme plage que si l’argument est une chaîne constante qui ne commence pas par un joker ; MariaDB dit la même chose. L’index desearch_word(langue, boutique, mot) aide avec « mot% », pas avec « %mot% ». - La taille de l’index.
search_indexa une ligne pour chaque couple produit-mot, avec des mots distincts par langue et par boutique : il grandit avec les produits, les mots par produit et les langues. - Jointures et sommes des poids. Chaque recherche joint les tables de l’index à celles des produits et la pertinence est une sous-requête pour chaque produit trouvé.
- La boucle approximative. Après une recherche vide, une liste de mots lue dans la base de données est comparée un par un en PHP.
- Le tri par prix en PHP, qui charge tous les résultats au lieu de les paginer dans la requête.
- Les décomptes des filtres, décrits ci-dessous.
Filtres : pourquoi un décompte peut ne pas tomber juste
ps_facetedsearch calcule chaque décompte avec une requête COUNT(DISTINCT p.id_product) regroupée par valeur, en appliquant les filtres sélectionnés sauf celui du groupe qu’il est en train de compter. Il compte des produits, pas des déclinaisons. Un nombre peut diverger des résultats pour ces raisons, toutes visibles dans le code :
- Cache des blocs de filtres. Avec l’option active (elle l’est après l’installation), les blocs avec leurs décomptes sont enregistrés dans la table
layered_filter_blockpour les catégories, les marques et les fournisseurs (pas pour la recherche) et vidés par les hooks d’enregistrement des produits, des catégories, des attributs, des caractéristiques et des règles de prix. Parmi les hooks enregistrés, il n’y en a aucun sur la variation de quantité : après des mises à jour de stock faites hors de la fiche produit, le bloc peut rester l’ancien tant que vous n’utilisez pas « Vider le cache » ou le cron de vidage. - Index des prix. Le filtre de prix utilise
layered_price_indexavec une jointure interne : un produit absent de l’index n’apparaît pas lorsque le filtre est actif. L’index se met à jour à l’enregistrement du produit et depuis le panneau « Index et caches » (« Indexer tous les prix manquants », « Reconstruire l’index intégralement ») ; le panneau conseille des crons réguliers, avec une reconstruction nocturne.
Ce qui change avec un moteur externe
Elasticsearch, OpenSearch et Meilisearch conservent l’index dans un service séparé, conçu pour chercher du texte.
- Index inversé. Selon la documentation d’Elasticsearch, c’est une structure qui associe chaque terme aux documents qui le contiennent, avec un dictionnaire trié des termes et, pour chacun, une liste de documents. Le texte recherché est analysé comme le texte indexé.
- Pertinence. Elasticsearch et OpenSearch utilisent BM25 comme modèle par défaut.
- Fautes de frappe. Dans Elasticsearch, le paramètre
fuzzinessest une distance d’édition de Levenshtein calculée par le moteur ; la valeurAUTOest celle généralement recommandée (de 0 à 2 caractères, le mot doit coïncider ; de 3 à 5, une erreur est admise ; au-delà de 5, deux). OpenSearch a le même paramètre. Dans Meilisearch, la tolérance aux fautes de frappe est active par défaut : une erreur pour les termes d’au moins 5 caractères, deux à partir de 9. - Langues. Elasticsearch propose des analyseurs de langue, dont l’italien, le français, l’allemand, l’anglais, l’espagnol et le néerlandais.
- Décomptes. Les agrégations produisent des synthèses comme « combien de produits dans chaque catégorie » dans la même requête de recherche, avec le paramètre
aggs. OpenSearch avertit qu’elles consomment plus de CPU et de mémoire que les requêtes. - Coûts. C’est un service de plus à installer, mettre à jour et maintenir en marche, et l’index doit rester aligné sur le catalogue : chaque produit existe dans la base de données et dans le moteur. Pour les petits catalogues, la recherche native suffit souvent.
Comment mesurer avant de décider
- Temps de la page de recherche. Depuis un terminal :
curl -o /dev/null -s -w %{time_starttransfer}\\n https://example.com/index.php\?controller=search\&s=perceuse. Selon la documentation de curl,time_starttransferest le temps en secondes jusqu’au premier octet, y compris celui que le serveur met à calculer le résultat. Essayez de 5 à 10 recherches réelles, chacune trois fois : un code produit, un mot avec une faute de frappe, deux mots (avec le signe plus à la place de l’espace), une marque. - Journal des requêtes lentes. Dans MySQL et MariaDB, il est désactivé par défaut et la limite par défaut de
long_query_timeest de 10 secondes : abaissez-la pour le test, puis utilisezEXPLAINsur les requêtes trouvées pour voir si elles utilisent un index. - Profil de PrestaShop. La constante
_PS_DEBUG_PROFILING_dansconfig/defines.inc.php, d’après le commentaire du fichier, affiche des informations sur les performances sous chaque page. Utilisez-la sur une copie de test. - Statistiques des recherches. Le module « Recherches de la boutique » (
statssearch) enregistre le mot recherché, le nombre de résultats et la date à chaque recherche ; dans Statistiques, il liste les mots recherchés plus d’une fois. - Un filtre à la main. Notez le décompte à côté d’une valeur, cliquez dessus et comparez avec les produits listés ; recommencez après avoir changé un prix ou un stock.
- La démo. Essayez les mêmes recherches et les mêmes filtres sur la démo, qui a un grand catalogue et le module actif.
Avant de changer de moteur, il convient aussi d’essayer les réglages : désactiver « Rechercher dans le mot » si ce n’est pas nécessaire, réduire les mots approximatifs admis, mettre à zéro le poids des champs dont vous n’avez pas besoin et reconstruire l’index.
Comment le module Recherche Elasticsearch et Navigation à Facettes s’y prend
- Moteur au choix : intégré (il lit la base de données de PrestaShop, aucun service en plus), Elasticsearch, OpenSearch ou Meilisearch.
- Avec Elasticsearch et OpenSearch, les résultats et les décomptes des filtres viennent de la même requête : les agrégations voyagent dans le corps de la recherche.
- Les décomptes sont en mode automatique : actifs avec un moteur externe, désactivés avec le moteur intégré, où ils coûteraient un décompte par groupe de filtres. On peut les forcer depuis l’onglet Facettes.
- Fautes de frappe :
fuzzinessdu moteur externe ; avec le moteur intégré, un dictionnaire de trigrammes, consulté seulement après une recherche sans résultat, qui choisit le mot le plus proche avec la distance de Levenshtein sur un ensemble restreint. Il corrige un mot à la fois, ne trie pas par similarité et ne regarde pas les descriptions. - Si le moteur externe ne répond pas, un interrupteur l’exclut pendant 30 secondes et, avec le repli activé (il l’est par défaut), les recherches basculent sur le moteur intégré.
- L’index suit l’enregistrement et la suppression des produits et les variations de stock ; un cron protégé par un jeton et un assistant de réindexation le reconstruisent.
- Analyse des recherches, avec les plus fréquentes et celles sans résultat, et modèles de filtres par catégorie.
Voir la fiche du module, lire le guide d’installation et de configuration ou essayer la démo.
Sources
- PrestaShop 9.1.5 sur GitHub : Search.php, AdminSearchConfController.php, db_structure.sql, defines.inc.php
- Modules PrestaShop sur GitHub : ps_facetedsearch 5.0.0, statssearch 3.0.0
- MySQL 8.0 : range optimization, slow query log, EXPLAIN. MariaDB : LIKE, slow query log
- Elasticsearch : full-text, similarity, fuzziness, analyseurs de langue, agrégations
- OpenSearch : similarity, match et fuzziness, agrégations. Meilisearch : typo tolerance
- Elasticsearch, indices et documents. Manuel de curl, option
--write-out
Questions fréquentes
À partir de combien de produits un moteur externe est-il utile ?
Il n’existe pas de seuil valable pour tous : cela dépend des produits, des langues, du nombre de filtres et du serveur. Pour les petits catalogues, la recherche native suffit souvent. Mieux vaut mesurer d’abord, avec les tests ci-dessus.
La recherche approximative de PrestaShop ralentit-elle toutes les recherches ?
Non, elle n’intervient que lorsque la recherche exacte ne trouve rien : elle lit alors les mots candidats dans la base de données et calcule en PHP la distance de Levenshtein. Le coût pèse sur les recherches sans résultat.
Pourquoi le nombre affiché à côté d’un filtre ne correspond-il pas aux produits listés ?
Les causes visibles dans le code sont le cache des blocs de filtres, que certaines modifications de stock ne vident pas, et un index des prix ou des attributs qui n’est pas à jour. Le groupe déjà filtré est en outre compté sans son propre filtre.
Un moteur externe est-il toujours plus rapide ?
Pas forcément. Il déplace la recherche vers un index conçu pour le texte, mais c’est un service de plus à maintenir en marche et aligné, et les agrégations consomment plus de CPU et de mémoire que les requêtes.
Le module suffit-il à lui seul ?
Non. Avec le moteur intégré, la recherche reste une recherche sur la base de données de PrestaShop, avec des filtres et une correction des fautes de frappe meilleurs mais sans changer le mécanisme de fond. Un moteur externe doit être installé, mis à jour et maintenu en marche, et les synonymes et les analyseurs doivent être configurés. Mieux vaut d’abord mesurer.
