Table des matières :
- Pré-requis et architecture Docker : ce que vous devez décider avant de lancer un conteneur
- Docker Compose “production” : volumes persistants, healthchecks, réseau privé, reverse proxy
- Durcissement : clés, politiques d’accès, sauvegardes (dumps/snapshots) et mises à jour sans sueurs froides
- Modèle de pertinence Meilisearch : ranking rules, attributs de recherche et tolérance aux fautes
- Réglage de la pertinence en pratique : filtres, facettes, tri commercial et règles de distinct
- Recette d’intégration e‑commerce / PrestaShop : schéma de document, multi-boutique, jobs d’indexation et validation
- Supervision et perf : latence p95, backlog de tâches, charge et impact sur le TTFB front
Meilisearch n’est pas “juste un binaire à lancer” : en production, vous gérez un moteur qui persiste un index, exécute des tâches asynchrones (indexation, réglages, dumps), expose une API HTTP et devient très vite un composant critique de l’expérience de recherche (et donc du taux de conversion). Ce qui suit part d’un déploiement Docker réaliste (reverse proxy, volumes, sauvegardes, rotation) et enchaîne sur le réglage de la pertinence (ranking rules, filtres, facettes, synonymes) avec des exemples orientés catalogue e‑commerce / PrestaShop.
“Meilisearch is a powerful, fast, open-source, easy to use and deploy search engine.” — Meilisearch, README (GitHub)
Pré-requis et architecture Docker : ce que vous devez décider avant de lancer un conteneur
Premier point à trancher : où vit l’index et quel est votre RPO/RTO acceptable. Meilisearch persiste son stockage sur disque (dossier de base de données), et votre “source de vérité” produit (PrestaShop/MySQL, PIM, ERP…) n’est qu’une entrée : reconstruire un index peut être long si vous avez 100k+ produits, plusieurs langues, et un pipeline d’enrichissement (synonymes, champs calculés, popularité). Donc le déploiement Docker doit traiter Meilisearch comme un service stateful : volume dédié, sauvegardes, stratégie de restauration.
Concrètement, posez la question en amont : si je perds le volume Meilisearch, combien de temps avant de retrouver une recherche fonctionnelle et “à peu près pertinente” ? Entre l’export produit, l’enrichissement (features, stock, prix), l’indexation asynchrone et la chauffe des caches, le délai réel est souvent sous-estimé.
Deuxième décision : topologie réseau et exposition. Par défaut, vous ne voulez pas exposer Meilisearch sur Internet en direct. L’approche la plus propre : conteneur Meilisearch sur un réseau Docker privé, et exposition via un reverse proxy (Nginx/Traefik/HAProxy) avec TLS, limitation IP, et éventuellement auth mTLS si vous êtes dans un réseau interne. Si vous n’avez pas l’habitude des stacks multi-conteneurs en prod, reprenez les bases de Compose côté durcissement et opérabilité : Docker Compose : orchestrer des services multi-conteneurs en production.
Troisième point : dimensionnement. Meilisearch est généralement plus simple à opérer qu’Elasticsearch/OpenSearch, mais il reste sensible aux mêmes réalités : I/O disque, latence réseau, RAM disponible, et charge concurrente (autosuggest + recherche + facettes). Sur un VPS, évitez les disques lents et sous-dimensionnés, sinon vous verrez des pics de latence côté API et des tâches d’indexation qui s’empilent. Pour cadrer les prérequis infra Docker (CPU/RAM/IOPS), utilisez une grille de lecture type : VPS pour Docker : critères techniques et ressources recommandées.
Pour vous forcer à expliciter les choix, une mini-checklist “avant de run” (très concrète en e‑commerce) :
| Sujet | Question à trancher | Recommandation pragmatique |
|---|---|---|
| Données (volume) | SSD ? sauvegardé ? chiffré au repos ? | Volume dédié sur SSD, snapshots côté infra si possible + dumps applicatifs |
| RPO | Perte acceptable de données d’index | Souvent 24h max (dump quotidien) ; plus serré si prix/stock bougent beaucoup |
| RTO | Temps acceptable de restauration | Prévoyez un runbook + test de restauration (sinon, RTO = inconnu) |
| Exposition réseau | API publique ? interne ? | Interne uniquement, reverse proxy + allowlist (ou VPN) |
| Réglementaire (FR/UE) | Où stockez-vous dumps/snapshots ? | Stockage en UE si vos contraintes RGPD l’imposent ; minimisez les données dans les logs |
Docker Compose “production” : volumes persistants, healthchecks, réseau privé, reverse proxy
Dans les exemples ci-dessous, le contexte est Docker Engine 26.x + plugin Docker Compose v2.29+ sur Linux (Debian 12/Ubuntu 24.04), et Meilisearch v1.11.x (adaptez le tag à la version que vous ciblez). L’objectif : un service redémarrable, observable, sans exposition directe, avec une clé maître injectée via secret.
Voici un compose.yaml minimal mais exploitable en prod (reverse proxy volontairement séparé) :
services:
meilisearch:
image: getmeili/meilisearch:v1.11
container_name: meilisearch
restart: unless-stopped
networks:
- search_net
volumes:
- meili_data:/meili_data
environment:
MEILI_ENV: "production"
MEILI_DB_PATH: "/meili_data"
MEILI_NO_ANALYTICS: "true"
MEILI_HTTP_ADDR: "0.0.0.0:7700"
# La clé est injectée via secret dans la commande ci-dessous.
secrets:
- meili_master_key
command: >-
sh -lc 'export MEILI_MASTER_KEY="$(cat /run/secrets/meili_master_key)";
exec meilisearch'
healthcheck:
test: ["CMD", "sh", "-lc", "wget -qO- http://127.0.0.1:7700/health | grep -q available" ]
interval: 10s
timeout: 3s
retries: 10
networks:
search_net:
driver: bridge
volumes:
meili_data:
secrets:
meili_master_key:
file: ./secrets/meili_master_key.txt
Trois détails méritent d’être explicités. (1) Le volume meili_data est non négociable : sans lui, chaque redéploiement implique une réindexation complète. (2) Le healthcheck ne teste pas “le port ouvert” mais un endpoint fonctionnel (/health), ce qui vous permet de brancher un orchestrateur ou une supervision basique. (3) Le réseau privé évite l’exposition par ports: ; le reverse proxy est alors le seul point d’entrée.
Deux améliorations “prod” souvent utiles (sans complexifier l’architecture) :
- Rotation des logs côté Docker pour éviter un disque saturé (surtout sur VPS avec partition système petite) :
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
- Séparation du réseau : si votre backend (indexeur) et votre reverse proxy ne sont pas sur le même hôte, privilégiez un réseau privé/VPN inter-serveurs plutôt qu’une exposition publique avec IP allowlist. La surface d’attaque baisse, et la latence est plus stable.
Côté reverse proxy, gardez les choses simples : TLS côté Nginx, et proxy HTTP vers le conteneur. Exemple Nginx (serveur dédié/VPS) :
server {
listen 443 ssl http2;
server_name search.example.com;
# TLS : à gérer via certbot/ACME ou votre PKI.
# Limitez l'accès si l'API n'est consommée que par votre infra.
# allow 203.0.113.10; deny all;
location / {
proxy_pass http://meilisearch:7700;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Deux réglages Nginx à considérer selon votre usage :
- Rate limiting sur les endpoints de recherche si vous avez du “live search” et une pression bot (ça se fait proprement au reverse proxy, sans toucher à Meilisearch).
- Taille de requête (
client_max_body_size) si vous envoyez de gros batchs/documentsvia le proxy (sinon vous aurez des 413 côté Nginx).
Si votre stack e‑commerce souffre déjà de latence, le moteur de recherche n’est qu’un maillon : mesurer TTFB, pression bot et saturation I/O reste prioritaire pour éviter de “masquer” un problème infra avec un service en plus. Référence utile : PrestaShop lent : diagnostic TTFB, bots et optimisation serveur.
Durcissement : clés, politiques d’accès, sauvegardes (dumps/snapshots) et mises à jour sans sueurs froides
La règle de base : pas de master key en clair dans Git, pas de Meilisearch accessible publiquement, et pas de confusion entre clés admin et clés front. Meilisearch expose une API de gestion de clés (API keys) ; en pratique vous allez créer : (a) une clé admin pour l’indexeur (back-office / jobs), (b) une clé search restreinte pour le front (autorisations search, indices limités), et éventuellement (c) une clé analytics si vous collectez des métriques d’usage (en interne, RGPD). Côté applicatif, injectez ces secrets via votre gestionnaire (SOPS, Vault, secrets Docker/K8s), pas via des .env distribués.
Exemple concret : création d’une clé de recherche limitée (index products uniquement, actions minimales, expiration recommandée pour réduire le risque en cas de fuite front) :
curl -X POST "https://search.example.com/keys" \
-H "Authorization: Bearer $MEILI_ADMIN_KEY" \
-H "Content-Type: application/json" \
--data '{
"name": "frontend-search",
"description": "Clé front : recherche uniquement sur products",
"actions": ["search"],
"indexes": ["products"],
"expiresAt": "2027-01-01T00:00:00Z"
}'
Côté reverse proxy, ne comptez pas sur “l’obscurité” (URL secrète) : mettez une allowlist IP si seul votre backend consomme l’API d’indexation, et réservez la clé front aux endpoints nécessaires. Si votre site sert des utilisateurs en France/UE, pensez aussi à la minimisation : les logs de requêtes de recherche peuvent contenir des données personnelles (noms, emails, références de commande recopiées). Gardez des durées de rétention courtes et évitez d’associer requête + IP en clair si ce n’est pas indispensable.
Pour la sauvegarde, deux approches complémentaires existent en général dans Meilisearch : dumps (export logique) et snapshots (état instantané). En prod, un dump quotidien + snapshot plus fréquent (selon votre débit d’indexation) donne un bon équilibre. Exemple de dump via API (à lancer depuis une machine autorisée) :
curl -X POST "https://search.example.com/dumps" \
-H "Authorization: Bearer $MEILI_ADMIN_KEY"
# Puis suivre l'état des tâches
curl "https://search.example.com/tasks?types=dumpCreation" \
-H "Authorization: Bearer $MEILI_ADMIN_KEY"
- Politique de rétention : un dump par jour sans rotation = stockage qui grossit (et, à terme, incident). Décidez “N jours” et automatisez la purge.
- Stockage hors du serveur : si votre VPS tombe (ou si le disque corrompt), un dump stocké localement ne vous aide pas. Un stockage objet (compatible S3) ou un serveur de backup séparé est souvent le meilleur ratio coût/sérénité.
Point opérationnel souvent oublié : testez la restauration. Un dump non restaurable, c’est un fichier inutile. Faites un exercice trimestriel : restauration sur un conteneur isolé, vérification GET /indexes, GET /stats, et exécution d’un jeu de requêtes de non-régression (top 50 requêtes). Cette discipline est la même que pour votre base MySQL/MariaDB : si vous n’avez pas de routine backup/restore, vous volez à l’aveugle (et c’est encore plus violent lors d’une mise à jour majeure). Pour les paramètres exacts et les endpoints disponibles selon votre version, gardez en favori la documentation officielle
Pour les mises à jour de Meilisearch, évitez le “pull latest + restart” : l’index peut changer de format et vous forcer à migrer. Stratégie pragmatique : (1) snapshot/dump, (2) déploiement en canary (nouveau conteneur, nouveau volume, restauration dump), (3) tests de pertinence et de perf, (4) bascule DNS/route, (5) rollback possible. Si vous êtes déjà outillés en GitOps/Kubernetes, appliquez les mêmes principes de contrôle de version et de rollback (voir : Migration PrestaShop vers Kubernetes : checklist GitOps et pièges courants).
Modèle de pertinence Meilisearch : ranking rules, attributs de recherche et tolérance aux fautes
Avant de régler des curseurs, posez une définition commune de la “pertinence”. Dans le monde IR, une formulation classique est :
“Information retrieval (IR) is finding material (usually documents) of an unstructured nature (usually text) that satisfies an information need from within large collections (usually stored on computers).” — Manning, Raghavan, Schütze, « Introduction to Information Retrieval », 2008
En e‑commerce, “satisfies an information need” signifie presque toujours : montrer rapidement le bon produit, gérer la typos, accepter des requêtes incomplètes, tout en gardant une capacité de tri commercial (stock, marge, nouveautés). Meilisearch combine un modèle de ranking basé sur des règles (ranking rules) et des réglages d’index (searchable attributes, stop-words, synonyms, typo tolerance). Le piège : toucher aux ranking rules sans avoir verrouillé le schéma de documents et l’ordre des champs recherchables.
Gardez une règle simple : d’abord le “matching” (où on cherche), ensuite le “ranking” (comment on départage). Si vos champs sont mal choisis (ou dans le mauvais ordre), vous compensez ensuite avec des boosts et des tri “magiques”… qui finissent par casser sur les requêtes longues, les marques, ou les références.
Le réglage le plus sous-estimé est searchableAttributes, car il définit où Meilisearch cherche et dans quel ordre. Pour un catalogue, vous voulez généralement prioriser name, brand, reference/sku, puis catégories, puis description courte, et reléguer la description longue (bruit + SEO textuel) en fin de liste voire hors recherche. Exemple (API) :
curl -X PATCH "https://search.example.com/indexes/products/settings" \
-H "Authorization: Bearer $MEILI_ADMIN_KEY" \
-H "Content-Type: application/json" \
--data '{
"searchableAttributes": [
"name",
"brand",
"sku",
"categories",
"description_short",
"description"
]
}'
Mini-scenario très fréquent sur un catalogue PrestaShop : un marchand vend des “Chaussures Trail X Pro”. Si la description longue contient 2 000 mots SEO (“trail”, “running”, “marathon”, “ultra”…), vous risquez de remonter des produits “SEO-compatibles” plutôt que le bon modèle. En mettant description en dernier (ou en l’excluant), vous réduisez ce bruit et vous améliorez le “time to product”.
Enfin, la tolérance aux fautes (typoTolerance) doit être alignée sur votre langue et vos tokens. Sur un site FR, l’auto-correction agressive peut confondre des références techniques (ex. “M8”, “RJ45”, “i7-14700K”) avec des mots. L’approche robuste : désactiver la typo sur certains champs (SKU), augmenter la taille minimale de mots pour corriger, et utiliser des synonymes pour les variations métier (“t-shirt”/“tee shirt”, “usb-c”/“type c”). Exemple simple de synonymes (à adapter à votre vocabulaire catalogue) :
curl -X PATCH "https://search.example.com/indexes/products/settings" \
-H "Authorization: Bearer $MEILI_ADMIN_KEY" \
-H "Content-Type: application/json" \
--data '{
"synonyms": {
"tee shirt": ["t-shirt", "tshirt"],
"usb c": ["usb-c", "type c", "usbc"]
}
}'
Dans les environnements PrestaShop multi-langue, segmenter par index (un index par langue) simplifie les stop-words et synonymes. Alternative acceptable : un seul index avec un champ lang filtrable, mais vous aurez plus vite des compromis (synonymes FR qui perturbent EN, etc.).
Réglage de la pertinence en pratique : filtres, facettes, tri commercial et règles de distinct
Sur un moteur de recherche e‑commerce, la pertinence ne se limite pas au texte : les facettes et filtres font partie du produit. Dans Meilisearch, vous devez déclarer explicitement les champs filtrables et triables (filterableAttributes, sortableAttributes). Sans ça, vous aurez des erreurs côté front au moment de filtrer par catégorie, prix, stock, ou attributs produits.
Exemple de réglages orientés boutique (produits) :
curl -X PATCH "https://search.example.com/indexes/products/settings" \
-H "Authorization: Bearer $MEILI_ADMIN_KEY" \
-H "Content-Type: application/json" \
--data '{
"filterableAttributes": [
"shop_id",
"lang",
"category_ids",
"brand",
"price",
"in_stock",
"features.color",
"features.size"
],
"sortableAttributes": [
"price",
"sales_30d",
"popularity",
"updated_at"
],
"pagination": { "maxTotalHits": 2000 }
}'
- Ne rendez pas “filtrable” tout le document par confort. Chaque attribut filtrable/facetable a un coût : taille d’index, temps de calcul facettes, et complexité de maintenance.
- Pensez “UX d’abord” : si votre front ne propose jamais de filtrer par
updated_at, ne le rendez pas filtrable/triable “au cas où”.
Le tri commercial se fait ensuite au niveau requête (sort) ou via ranking rules additionnelles si votre stratégie est stable. Cas typique : pousser les produits en stock et populaires. Deux approches : (a) imposer un filtre in_stock = true par défaut (attention aux ruptures), ou (b) conserver tous les produits mais booster in_stock et sales_30d via un tri secondaire. Exemple de requête :
curl -X POST "https://search.example.com/indexes/products/search" \
-H "Authorization: Bearer $MEILI_SEARCH_KEY" \
-H "Content-Type: application/json" \
--data '{
"q": "chaussure trail",
"filter": "shop_id = 1 AND lang = \"fr\"",
"sort": ["in_stock:desc", "sales_30d:desc", "price:asc"],
"facets": ["brand", "features.size", "features.color"]
}'
Pour éviter d’alourdir les réponses (et donc la latence perçue), pensez à limiter ce que vous renvoyez au front. Sur un autosuggest, vous n’avez pas besoin de 40 champs :
- utilisez
attributesToRetrieve(ex.id,name,price,image,url), - et
attributesToHighlightsurnameseulement, par exemple.
Dernier levier très utile sur des catalogues à variantes : distinctAttribute. Si vous indexez une ligne par déclinaison (taille/couleur), l’utilisateur n’a pas envie de voir 18 fois le même produit en haut des résultats. Définir un champ product_group_id (id produit parent) et activer le distinct permet de dédupliquer les hits tout en gardant le stock et les attributs en facettes. C’est souvent un meilleur compromis que de “flatten” toutes les déclinaisons dans un seul document (qui rend les facettes plus complexes).
Un test rapide pour valider que votre distinct aide vraiment : prenez 10 requêtes “taille/couleur” (ex. “sneakers 42”, “robe noir”), comparez le top 10 avant/après, et regardez si vous gagnez en diversité (moins de doublons) sans perdre le bon produit.
Recette d’intégration e‑commerce / PrestaShop : schéma de document, multi-boutique, jobs d’indexation et validation
Côté PrestaShop (8.1 à 9.1 en 2026), le cœur n’offre pas un pipeline natif “clean” pour alimenter un moteur externe sans module/worker. En pratique, vous finissez avec : (1) un export initial (batch), (2) une mise à jour incrémentale (hooks + queue), (3) un recalcul périodique des champs dérivés (popularité, ventes J‑30, disponibilité). Si vous devez faire du CRUD sur le catalogue ou lire des entités efficacement, le Webservice peut suffire sur petits volumes, mais sur gros catalogues vous irez plus vite avec une intégration directe DB + code métier (et des contrôles d’intégrité). Point de départ utile côté API : API Webservice PrestaShop : guide CRUD et ressources développeur.
Un schéma de document “qui marche” évite 80% des bugs de pertinence. Exemple minimal (produit) :
id(string/int) : identifiant unique du document (déclinaison ou produit parent).product_group_id: pourdistinctAttribute.shop_id,lang: indispensable en multi-boutique / multi-langue.name,description_short,categories[],brand,sku.price,in_stock,updated_at.features(objet) :{ "color": "noir", "size": "42" }.url,image: affichage.
Ajoutez ensuite (si vous en avez l’usage) des champs “métier” qui permettent un tri stable sans bricolage côté front :
sales_30d(int) : ventes glissantes,margin_rate(float) : si vous faites du merchandising (attention à l’accès),is_new(bool) : nouveauté,position_in_category(int) : si votre SEO/merchandising dépend de positions.
En ingestion, évitez les mises à jour “au fil de l’eau” synchrones dans le thread HTTP d’un back-office. Utilisez une queue (Symfony Messenger, RabbitMQ, SQS…) et alimentez Meilisearch en asynchrone ; Meilisearch lui-même répond avec des tasks (UID) : vous pouvez donc tracer “document envoyé” vs “document indexé”. Si votre stack gère déjà des files, appliquez les patterns de retry/poison message (cf. : Intégration ERP e‑commerce : architecture API, webhooks et files RabbitMQ).
Une stratégie d’indexation incrémentale “safe” (et réaliste) ressemble souvent à :
- Hook produit (création/édition) → job “re-index productgroupid”
- Hook stock/prix → job “patch champs stock/prix”
- Cron nocturne → recalcul
sales_30d,popularity, purge des produits désactivés - Job de contrôle → compare un échantillon (ex. 200 produits) entre DB et index (prix/stock) pour détecter les dérives
Pour valider la pertinence, ne vous contentez pas d’un “ça a l’air ok”. Constituez un dataset de tests : top requêtes internes (search logs), requêtes sans résultats, requêtes avec clic, et requêtes “métier” (références, marques, tailles). Si vous avez déjà instrumenté la recherche interne (requêtes, CTR, reformulations) vous avez la matière première ; sinon, faites-le, même basiquement, car le tuning sans données est de la superstition (voir : Recherche interne PrestaShop : analytics RGPD et optimisation du moteur).
Supervision et perf : latence p95, backlog de tâches, charge et impact sur le TTFB front
En production, vous ne pilotez pas Meilisearch au “ressenti”. Les métriques minimales : latence p50/p95 des endpoints de recherche, débit (req/s), taille d’index, et backlog de tâches (indexation en retard). À défaut d’exporter des métriques Prometheus, vous pouvez déjà automatiser un check via GET /tasks et GET /stats et pousser ça dans votre supervision existante (Netdata/Prometheus/Grafana). Si vous déployez Netdata sur vos serveurs, c’est un bon point d’entrée pour corréler CPU/I/O et latence applicative : Netdata : activer et configurer les collectors pour monitoring temps réel.
tasksqui restent “enqueued” longtemps : votre indexation n’absorbe plus le flux (CPU/IO insuffisants, batch trop gros, ou pics de mise à jour).- Hausse des “failed tasks” : payload invalide, champs qui changent de type (ex.
priceparfois string), ou settings poussés trop souvent. - p95 qui grimpe uniquement quand les facettes sont demandées : trop de facettes, trop de champs filtrables, ou requêtes trop larges (ex.
maxTotalHitstrop haut).
Côté perf, le piège classique est d’implémenter un “live search” agressif côté front (requête à chaque keypress) sans debounce, sans cache, et sans seuil minimal de caractères. Résultat : surcharge de Meilisearch, saturation réseau, et hausse du TTFB global si votre front passe par votre backend. Imposer minChars=2/3, un debounce 150–250ms, et un cache LRU sur les suggestions (côté edge ou front) suffit souvent à diviser le trafic par 3 à 10 sur des sessions longues.
Enfin, reliez le tuning Meilisearch aux vrais objectifs : moins de “no results”, meilleure conversion sur sessions avec recherche, et stabilité sous charge. Un protocole simple : (1) tests de charge reproductibles (k6/vegeta) sur un corpus de requêtes, (2) mesure p95 et taux d’erreur, (3) ajustement maxTotalHits, facettes, et champs renvoyés (réduire le payload via attributesToRetrieve), (4) re-test. Si vos pages produit/panier sont déjà lourdes, l’amélioration de la recherche doit s’inscrire dans un plan perf global (cache, DB, front) : Optimisation PrestaShop : accélérer pages produit et panier.
Si vous devez aller plus loin (vector search/semantique, multi-index par canal, pipeline d’enrichissement), commencez par verrouiller ce socle : déploiement Docker stateful + clés/ACL + dumps testés + dataset de tests de pertinence. Tout le reste est un détail — et en prod, les détails coûtent cher.




