Table des matières :
- Quand la recherche SQL de PrestaShop devient le goulot d’étranglement
- Concevoir un index Elasticsearch adapté au modèle catalogue PrestaShop
- Mapping et analyse linguistique : pertinence, synonymes, multi-langue
- Synchronisation temps réel : hooks, batch, reindex sans downtime
- Facettes et requêtes : DSL, agrégations, pagination et tri
- Mise en production : dimensionnement, refresh, caches et observabilité
- SEO : facettes, URLs et budget de crawl sans se tirer une balle dans le pied
- Elasticsearch, OpenSearch, Meilisearch : arbitrer en 2026
La mise en place d’Elasticsearch sur PrestaShop n’a d’intérêt que quand vous avez déjà épuisé les optimisations “classiques” (cache, SQL, TTFB, etc.) et que le goulot devient la recherche/facettage. Sur un petit catalogue, le cœur suffit. Sur un catalogue volumineux (ou très filtré), les requêtes SQL de recherche + facettes finissent par dominer le temps CPU, saturer InnoDB, et dégrader le TTFB.
L’objectif réaliste n’est pas seulement “faire plus vite”, mais surtout rendre la latence plus stable (p95/p99), absorber les pics (trafic + bots), et améliorer la pertinence sans multiplier les hacks SQL. Une migration réussie se juge donc sur des métriques avant/après (TTFB des pages catégories filtrées, temps CPU MySQL, taux d’erreurs PHP-FPM, et latence côté moteur sur les requêtes de recherche/facettes).
Quand la recherche SQL de PrestaShop devient le goulot d’étranglement
Le cœur PrestaShop (8.x/9.x) s’appuie historiquement sur une indexation SQL (tables ps_search_*) et, côté facettes, sur des jointures/group by lourds pilotés par ps_facetedsearch. Ça fonctionne, mais le modèle relationnel est structurellement défavorable pour deux besoins : la pertinence textuelle (typos, stemming, synonymes, multi-champs) et le comptage de facettes sur des combinaisons de filtres (effet “explosion” des jointures).
Sur des catalogues à 50k–500k produits, le symptôme est rarement “la recherche est lente” en isolation. Le symptôme réel est : pages catégories filtrées qui passent à >1–2 s côté serveur, spikes CPU MySQL lors des pics de trafic, et timeouts PHP-FPM dès que les bots (ou un comparateur) balaient des combinaisons de facettes.
Checklist de diagnostic (rapide, mais très pratique) avant de parler Elasticsearch :
- MySQL : pics CPU corrélés aux filtres + présence de
Creating tmp table,Using filesortdansEXPLAIN. - InnoDB : augmentation des lectures (buffer pool misses) sur les plages de trafic “catalogue”.
- PHP-FPM : files d’attente qui montent dès qu’un bot explore des URLs de filtres.
- Symptôme UX : la home est OK, mais catégories filtrées + tri deviennent irrégulières (parfois 300 ms, parfois 3 s).
- Données : le nombre de combinaisons de filtres “possibles” explose (attributs + marques + prix + disponibilité), même si les produits restent “raisonnables”.
Si vous êtes dans ce cas, relisez d’abord l’approche SQL native côté facettes (et ses limites) dans l’article : Recherche à facettes PrestaShop : configurer ps_facetedsearch et filtres combinés. Dans beaucoup de shops, un réglage correct des index, des caches et du module suffit encore — et c’est toujours moins coûteux à opérer qu’un moteur dédié.
Elasticsearch change la nature du problème : on passe d’un calcul “à la demande” en SQL à une réponse depuis un index inversé + agrégations. Elastic résume correctement son objet : “Elasticsearch is a distributed, RESTful search and analytics engine capable of addressing a growing number of use cases.” (Elastic, documentation Elasticsearch). Dans un contexte catalogue, ça se traduit par des latences de recherche beaucoup plus stables (p95/p99), et une meilleure pertinence sans bricoler du LIKE/FullText SQL.
Mini-situation typique (terrain) : un marchand FR avec 180k produits, 25 attributs filtrables et 12 marques majeures. En SQL, une page catégorie “chaussures” + 4 filtres + tri prix peut déclencher des jointures coûteuses et des sous-requêtes pour compter les facettes. Avec Elasticsearch, le même écran se résume (côté moteur) à une requête filtrée + quelques agrégations, et la pression se déplace vers la qualité de l’index et la synchronisation, pas vers le moteur relationnel.
Concevoir un index Elasticsearch adapté au modèle catalogue PrestaShop
Le piège courant est de “mettre les produits dans Elasticsearch” sans réfléchir à l’accès réel : langue, boutique, groupe client, devise, règles de prix, stock, disponibilité, visibilité catalogue, etc. Une stratégie robuste en PrestaShop 8.1–9.2 (PHP 8.2/8.3) est d’indexer un document par produit × langue × boutique, pour simplifier l’analyse linguistique et éviter les champs multi-langues inutilisables en scoring. Si vous avez du multi-boutique massif, vous pouvez aussi indexer un document par produit avec des sous-structures par boutique, mais la complexité de filtrage et d’agrégation augmente.
Les champs “catalogue” se classent en 3 familles :
- Textuels (nom, description courte, marque, catégories, tags) pour le scoring.
- Exact match (SKU/référence, EAN/UPC,
id_category_default,id_manufacturer,id_feature_value_*, attributs) pour les filtres/facettes. - Numériques (prix, remise, stock, poids) pour range queries, tri et agrégations.
Quelques champs “non évidents” qui évitent des incohérences fonctionnelles :
active,visibility(catalogue/recherche),available_for_orderdate_add,date_upd(tri nouveauté / réassort)category_ids(toutes les catégories, pas uniquement la défaut)attribute_value_ids(déclinaisons) si vous filtrez sur la couleur/taillecustomer_group_idssi votre catalogue/prix dépend des groupes (B2B/B2C)shop_id,lang_id(indispensables en multi)
Sur les déclinaisons, deux modèles reviennent :
- Index “produit” : vous indexez le produit, et vous agrégerez des attributs disponibles “globalement”. Simple, mais plus difficile si les stocks/prix varient fortement par déclinaison.
- Index “déclinaison” (ou hybride) : vous indexez un document par combinaison, et vous remontez un “produit parent” côté UI. Plus fidèle au stock/prix, mais plus coûteux (volume d’index) et plus délicat sur le scoring.
La partie la plus sous-estimée est le prix. PrestaShop calcule le prix “affichable” via taxes, devises, règles de prix spécifiques, groupes clients, arrondis, etc. Indexer “un seul prix” est souvent faux. Deux approches réalistes : (1) indexer un prix “base” (HT) + prix promo si applicable et recalculer côté PHP au rendu, ou (2) indexer des prix par contexte (shop/currency/group) si vous avez peu de combinaisons. Dans les deux cas, documentez explicitement ce qui est “source de vérité”, sinon vous allez produire des tris “prix croissant” incohérents.
Astuce pratique : si vous optez pour (1), vous pouvez malgré tout améliorer la cohérence des tris en indexant un prix de tri (par exemple “prix TTC pour le groupe par défaut et la devise principale”), puis en assumant que les variations (B2B, devises secondaires) seront traitées en affichage, pas en tri. Ce n’est pas parfait, mais c’est souvent acceptable si le shop n’expose pas 10 devises à la fois.
Mapping et analyse linguistique : pertinence, synonymes, multi-langue
La pertinence d’Elasticsearch n’est pas magique : elle dépend du mapping et des analyzers. Le socle est Lucene ; rappel utile : “Apache Lucene is a high-performance, full-featured text search engine library written entirely in Java.” (Apache Lucene). Elasticsearch ajoute la distribution, les APIs, les agrégations et l’écosystème, mais la qualité du scoring reste conditionnée par vos choix d’analyse.
Pour un catalogue FR (et potentiellement multi-langue), vous voulez typiquement :
- un champ
nameentextavec analyzerfrench(stemming + stopwords), - un champ
name.rawenkeywordpour tri/agrégations (si nécessaire), - un champ
name.ngram(edge n-gram) dédié à l’auto-complétion si vous ne partez pas surcompletionsuggester, - des champs
reference,ean13enkeyword(pas en text).
Point d’attention “FR” concret : si vos utilisateurs tapent sans accents (“cafe”) mais que vos fiches sont accentuées (“café”), ajoutez asciifolding dans votre analyse (ou sur un champ dédié), sinon vous allez perdre du recall sur des requêtes très courantes en e-commerce.
Exemple minimal de mapping (à adapter, Elasticsearch 8.x). Note : le normalizer doit être défini dans settings.analysis si vous l’utilisez sur un champ keyword.
PUT prestashop_products_fr_v1
{
"settings": {
"analysis": {
"normalizer": {
"lowercase_norm": {
"type": "custom",
"filter": ["lowercase", "asciifolding"]
}
},
"filter": {
"autocomplete_filter": { "type": "edge_ngram", "min_gram": 2, "max_gram": 20 }
},
"analyzer": {
"autocomplete": {
"tokenizer": "standard",
"filter": ["lowercase", "asciifolding", "autocomplete_filter"]
}
}
}
},
"mappings": {
"properties": {
"shop_id": { "type": "integer" },
"lang_id": { "type": "integer" },
"product_id": { "type": "integer" },
"active": { "type": "boolean" },
"visibility": { "type": "keyword" },
"name": {
"type": "text",
"analyzer": "french",
"fields": {
"raw": { "type": "keyword", "normalizer": "lowercase_norm" },
"ac": { "type": "text", "analyzer": "autocomplete", "search_analyzer": "standard" }
}
},
"reference": { "type": "keyword" },
"ean13": { "type": "keyword" },
"manufacturer_id": { "type": "integer" },
"category_ids": { "type": "integer" },
"price_tax_excl": { "type": "scaled_float", "scaling_factor": 100 },
"quantity": { "type": "integer" },
"in_stock": { "type": "boolean" }
}
}
}
Les synonymes méritent une attention particulière. Un fichier de synonymes “à l’ancienne” déployé sur les nœuds est source d’incidents (décalage de config, rolling restart, erreurs d’analyse). Sur Elasticsearch récent, privilégiez les mécanismes de synonym sets quand c’est possible (selon votre distribution/licence), ou versionnez strictement le fichier et automatisez le déploiement.
Deux règles simples qui évitent beaucoup de régressions :
- Synonymes au moment de la recherche (search-time) plutôt qu’à l’indexation, quand c’est possible : vous itérez sans reindex complet.
- Synonymes “équivalents” vs “directionnels” : l’e-commerce est rempli de termes asymétriques (ex. “iphone” → “smartphone”, mais pas toujours l’inverse). Mal gérer ça dégrade la précision plus vite qu’on ne le pense.
Sans gouvernance, les synonymes dégradent le recall/precision plus souvent qu’ils n’aident.
Synchronisation temps réel : hooks, batch, reindex sans downtime
Indexer est simple. Synchroniser proprement est le vrai coût. PrestaShop déclenche des hooks et des événements (selon versions/modules) mais ne fournit pas un bus “reliable” out-of-the-box pour garantir la livraison. En pratique, évitez l’indexation synchrone dans le cycle HTTP (surtout au BO) : si Elasticsearch répond lentement ou est indisponible, vous ajoutez du coupling et vous créez des erreurs 500.
Une approche robuste consiste à : (1) écrire les changements dans une table “outbox” MySQL (idempotente, avec entity_type, entity_id, shop_id, lang_id, updated_at), (2) consommer cette outbox via cron/worker, (3) pousser en _bulk vers Elasticsearch. Cette approche réduit la pression sur le BO et vous permet de rejouer. Pour la partie MySQL/perf, vous voulez éviter d’ajouter de la contention : l’article Audit MySQL/MariaDB : optimiser les requêtes lentes e‑commerce est utile pour cadrer l’impact.
Exemple de table “outbox” (conceptuel) : l’idée n’est pas le SQL parfait, mais l’idempotence et la rejouabilité.
- Une clé unique du type
(entity_type, entity_id, shop_id, lang_id)permet de “coalescer” plusieurs mises à jour en une seule entrée. - Un champ
attempts+last_erroraide à diagnostiquer les boucles de retry. - Une stratégie de backoff (ex. 1 min, 5 min, 30 min) évite de marteler le cluster en incident.
Côté “événements”, vous finissez généralement par couvrir : création/modif produit, modif déclinaisons, mises à jour de stock, changement de prix spécifique, changement de catégories, activation/désactivation. Les hooks exacts varient selon versions, mais l’idée reste : détecter les mutations et pousser un “job d’indexation”.
Détails opérationnels qui font la différence sur un gros catalogue :
- Bulk par paquets (ex. 200–1000 documents selon taille) pour lisser la charge et limiter l’impact mémoire.
- Gestion des erreurs
_bulk: un HTTP 200 peut contenir des erreurs item par item ; loggez et rejouez finement. - Suppression : prévoir les deletes (produit désactivé, supprimé, non visible) pour éviter les “fantômes” dans les résultats.
- Reindex complet (nightly ou à la demande) pour corriger les dérives (données manquantes, règles de prix recalculées, import massif).
Pour reindexer sans downtime, utilisez des aliases : prestashop_products_fr pointe vers prestashop_products_fr_v1, puis vous créez v2, vous re-buildez, et vous basculez l’alias atomiquement. Si vous êtes en managé, la stratégie snapshot/restore + parallélisation est détaillée ici : Elasticsearch managé : migration zéro interruption via snapshot, restore et parallélisation.
Facettes et requêtes : DSL, agrégations, pagination et tri
L’objectif “accélérer les facettes catalogue” se traite via les aggregations. Contrairement à SQL où chaque filtre combiné peut réécrire (et alourdir) la requête, Elasticsearch peut calculer des buckets de facettes sur l’ensemble filtré, avec des performances souvent plus prévisibles. Pour une catégorie + attributs + marque + range prix, vous combinez un bool.filter et vous exposez des terms aggregations sur manufacturer_id, category_ids, attribute_value_ids, etc.
Exemple de requête (simplifiée) :
POST prestashop_products_fr/_search
{
"track_total_hits": false,
"from": 0,
"size": 24,
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "chaussure trail",
"fields": ["name^4", "description_short^1", "reference^6"],
"type": "best_fields",
"operator": "and"
}
}
],
"filter": [
{ "term": { "shop_id": 1 } },
{ "term": { "lang_id": 1 } },
{ "term": { "in_stock": true } },
{ "range": { "price_tax_excl": { "gte": 5000, "lte": 20000 } } }
]
}
},
"aggs": {
"by_manufacturer": { "terms": { "field": "manufacturer_id", "size": 50 } },
"by_category": { "terms": { "field": "category_ids", "size": 50 } }
}
}
Attention à un point : les utilisateurs attendent souvent des compteurs de facettes “contextuels” (les comptes recalculés en fonction des filtres déjà sélectionnés, mais sans “auto-filtrer” la facette courante). Ça se gère via post_filter ou via des agrégations filter dédiées par facette (pattern classique). Si vous ne le faites pas, vous afficherez des compteurs incohérents et l’UX facettes régresse malgré de bonnes performances.
- Cardinalité élevée : une facette sur un champ trop cardinal (ex. tags libres) peut coûter cher en mémoire. Pour des listes très longues, limitez à ce qui est réellement filtrable, ou utilisez des patterns comme
compositeaggregation pour paginer les buckets. - Taille des buckets :
size: 50est un choix UX, pas une règle technique. Si vous avez 300 marques mais que l’UX n’en affiche que 15, ne demandez pas 300 buckets “par défaut”.
La pagination et le tri sont aussi à cadrer. Sur un catalogue filtré, from + size est OK tant que vous restez sur des profondeurs raisonnables. Pour du “deep paging” (ex. > 10 000), préférez search_after (sinon, les coûts augmentent fortement). Et si vous avez besoin d’un total exact (ex. “1 234 résultats”), vous devrez activer track_total_hits (au moins sur certaines pages) — ce qui a un coût.
Le tri est un autre sujet sensible. Trier par prix, par nouveauté, par popularité, par disponibilité… implique des champs adaptés (numériques, dates, scores). Pour la “popularité”, évitez d’écrire le nombre de ventes dans Elasticsearch à chaque commande en synchrone ; batcher via outbox/worker, ou indexer un score recalculé (ex. quotidien) suffit souvent. Sinon, vous finissez par payer une infra “temps réel” inutile.
Mise en production : dimensionnement, refresh, caches et observabilité
En production, Elasticsearch n’est pas “un service PHP”. C’est un système distribué avec ses règles : heap, GC, segments, shards, refresh, merge. Une citation utile (souvent ignorée) : “By default, Elasticsearch periodically refreshes indices every second.” (Elastic, refresh API / near real-time : near real-time). Conséquence : si vous indexez en masse, ajustez refresh_interval (voire -1 pendant le bulk) pour éviter de tuer le cluster.
Quelques choix de production qui évitent des “surprises” :
- Shards : trop de shards = overhead et instabilité ; trop peu = manque de parallélisme et reindex pénible. Le bon chiffre dépend du volume et du trafic, mais l’important est de le décider avec un plan de croissance (v2, v3…).
- Heap JVM : garder une marge pour l’OS (cache disque) est crucial sur des index volumineux. Une ressource technique très utile sur ce sujet : Elastic — A Heap of Trouble (2016).
- SSD + I/O stable : les merges de segments et les pics d’indexation se sentent immédiatement sur la latence.
- Règles de refresh : en e-commerce, vous voulez souvent “assez temps réel” (stock/prix), mais pas au prix d’une instabilité. Le compromis se fait par type de données (stock plus fréquent, descriptions plus rare).
Côté dimensionnement, un catalogue e-commerce “standard” tient souvent sur 1–3 nœuds data selon taille et trafic, mais les vraies variables sont : volume de champs textuels, nombre de facettes, cardinalité (ex. attribute_value_ids), taille des résultats, et surtout p95/p99. Un bon réflexe est de mesurer la latence côté applicatif (APM) et côté moteur (search slow logs).
Checklist observabilité (simple, efficace) :
- latence p95/p99 des endpoints
_search - heap utilisé (%) + fréquence/temps de GC
search_throttled/ files d’attente (thread pools) et “rejections”- erreurs
_bulk(par type : mapping, document trop gros, timeouts) - taille des index et croissance quotidienne
La supervision “temps réel” est un investissement minime comparé au coût des incidents : Netdata : intégrer Prometheus, SNMP et StatsD pour tout surveiller.
Enfin, n’isolez pas Elasticsearch du reste de la chaîne perf. Une recherche rapide mais un TTFB élevé reste une page lente. Si votre front est déjà sous pression, vous devez traiter cache PHP, OPcache, Redis, CDN, etc. (sinon le gain ES se dilue). Deux ressources internes qui cadrent bien le sujet : Cache PrestaShop : stratégie 2026 OPcache, Redis, Varnish et CDN et PrestaShop performance : optimiser cache, images WebP et base MySQL.
SEO : facettes, URLs et budget de crawl sans se tirer une balle dans le pied
Accélérer les facettes ne doit pas ouvrir la porte à un déluge d’URLs filtrées. Avec un moteur rapide, vous pouvez générer (et servir) des milliers de combinaisons de filtres parfaitement valides… que Googlebot va tenter de crawler si vous laissez faire. Le résultat : budget de crawl consommé sur des pages à faible valeur, duplication de contenu, et parfois indexation de pages “vides” selon stock/tri.
Le travail SEO technique est orthogonal au choix Elasticsearch : vous devez définir quelles combinaisons de facettes méritent une URL indexable, lesquelles doivent être noindex, et comment gérer canonical/paramètres. Sur PrestaShop, c’est un sujet récurrent et très concret : Budget de crawl PrestaShop : maîtriser URLs de filtres et facettes couvre les patterns (paramètres, réécriture, robots, canonical) qui évitent de transformer vos facettes en usine à thin content.
Quelques règles SEO “opérationnelles” qui se marient bien avec une implémentation Elasticsearch :
- Allowlist des pages facettées indexables (ex. “chaussures + marque”, “chaussures + couleur”), et noindex sur le reste.
- Canonical cohérent : une page filtrée doit canoniser vers elle-même si elle est censée être indexée, sinon canoniser vers la catégorie mère (selon stratégie).
- Gestion des tris : les paramètres
order=/sort=ne doivent quasiment jamais être indexables. - Pages à 0 résultat : en général, noindex + UX propre (et éventuellement redirection douce vers la catégorie sans certains filtres).
Autre point : en migrant vers Elasticsearch, certains implémentent un rendu “JS-only” des résultats (SPA/instant search) et cassent le HTML crawlable ou les balises essentielles (H1/H2, contenu catégorie, maillage). Si vous changez la couche de rendu, auditez systématiquement la structure finale et les variations par filtre. Méthode pragmatique : comparer DOM server-side / client-side, et s’assurer que les sections SEO critiques ne disparaissent pas. Référence interne utile : Audit SEO HTML : diagnostiquer balises H1 H2 et contenu manquant.
Elasticsearch, OpenSearch, Meilisearch : arbitrer en 2026
En 2026, le choix n’est pas “Elasticsearch ou rien”. Beaucoup d’équipes choisissent OpenSearch (fork open source) pour des raisons de licence/écosystème managé, notamment chez certains hébergeurs. Si vous visez du managé en Europe (SLA/HA), gardez en tête les offres type OpenSearch managé et leurs contraintes (versions, plugins, limites). Exemple de cadrage : OVHcloud Managed OpenSearch : déploiement, SLA et haute disponibilité 3-AZ.
Meilisearch est une alternative intéressante si votre priorité est l’implémentation rapide et la simplicité opérationnelle (réglage de pertinence, latence faible, modèle plus “application”). En revanche, dès que vous avez des facettes avancées, des besoins complexes de scoring, ou un multi-langue exigeant, Elasticsearch/OpenSearch restent plus flexibles (au prix d’une complexité d’exploitation nettement supérieure). Pour un point de départ côté prod Docker/pertinence : Meilisearch : mise en production Docker et réglage de la pertinence.
Un moyen simple d’arbitrer (sans religion) est de poser ces questions :
- Avez-vous besoin de scoring multi-champs avancé (poids fin par champ, analyse linguistique, synonymes, règles) ?
- Votre UX facettes a-t-elle besoin de compteurs contextuels et de filtres combinables à haute fréquence ?
- Pouvez-vous opérer (ou faire opérer) un cluster : monitoring, reindex, incidents, montées de version ?
- Votre contrainte “geo”/conformité impose-t-elle un hébergement UE / un fournisseur précis ?
Le critère décisif pour Elasticsearch PrestaShop n’est donc pas “la vitesse brute”, mais votre capacité à industrialiser : schéma d’index, gouvernance des synonymes, pipeline d’indexation idempotent, reindex via alias, monitoring, et règles SEO sur les facettes. Si vous n’êtes pas prêt à opérer ça (ou à le faire opérer en managé), vous allez déplacer la complexité du SQL vers le moteur de recherche… sans gagner en fiabilité.




