Votre recherche sur le site est « correcte » jusqu’au jour où elle ne l’est plus : lancement de produit, lien viral
ou un client enthousiaste qui tape « wireless noise cancelling… » et le CPU de la base de données plafonne. Ensuite,
ce sont des pages qui expirent, une pertinence qui vire au hasard, et quelqu’un propose de tout mettre en cache « en attendant de régler ça ».
C’est comme ça que naissent les mugs commémoratifs d’incident.
Voici la ligne pratique entre « MariaDB suffit » et « il vous faut un cluster de recherche ». Ce n’est pas idéologique, c’est
opérationnel : latence de queue, réindexation, contrôle du classement, domaines de défaillance et comment diagnostiquer ce qui
est réellement lent.
La vraie question : qu’optimisez-vous ?
« MariaDB vs Elasticsearch » est un cadrage trompeur. En production, vous ne choisissez pas une technologie ; vous choisissez
quels modes de panne vous acceptez d’assumer.
MariaDB est excellent quand la recherche est un problème de lookup filtré. Elasticsearch est excellent quand la recherche est
un problème de langage et de pertinence. Dès que vous avez besoin des deux à grande échelle — filtres rapides, tolérance aux fautes,
synonymes, autocomplétion, « vouliez-vous dire », réglage du classement, scoring multi-champs — vous passez de « requêter des enregistrements »
à « exécuter un système de recherche ». Les systèmes de recherche veulent leur propre infrastructure, parce que leur travail est différent.
Règle orientée production : utilisez MariaDB pour la recherche quand la requête est prévisible, bornée et indexable avec une forte sélectivité.
Utilisez Elasticsearch quand vous devez servir l’intention, pas seulement des correspondances exactes, et contrôler la pertinence sans réécrire
votre SQL chaque semaine.
Le piège est de penser qu’Elasticsearch c’est « pour la montée en charge » et MariaDB « pour le petit ». C’est l’inverse.
Les deux peuvent monter. Elasticsearch sert les comportements de recherche que les moteurs relationnels ne peuvent pas fournir
de façon économique ou cohérente.
Faits intéressants et contexte historique (court, utile)
- Lucene précède Elasticsearch. La bibliothèque d’indexation/recherche (Lucene) a démarré en 1999 ; Elasticsearch (construit sur Lucene) est arrivé vers 2010 et l’a emballé pour un usage distribué.
- BM25 est devenu le socle de pertinence par défaut. Lucene est passé du TF/IDF classique à BM25, modèle de scoring plus moderne et ajustable. Ça compte parce que la « pertinence » est mathématique, pas subjective.
- FULLTEXT de MySQL/MariaDB était conçu pour des documents, mais avec des contraintes. C’est utile, mais l’analyse linguistique, le contrôle du scoring et la composition de requêtes sont limités comparés aux moteurs basés sur Lucene.
- FULLTEXT pour InnoDB est apparu plus tard qu’on ne le croit. Historiquement, FULLTEXT était associé à MyISAM ; le support InnoDB est arrivé plus tard, et les attentes opérationnelles ont été en retard sur la réalité.
- Elasticsearch a démocratisé l’indexation « quasi temps réel ». Le concept d’intervalle de refresh (segments devenant recherchables après refresh) est un compromis délibéré entre débit et fraîcheur.
- La recherche distribuée est un problème de coordination. La difficulté n’est pas l’indexation ; ce sont les shards, les réplicas, le routage, les merges et la très humaine envie de changer les mappings après livraison.
- SQL LIKE est devenu le péché originel de la recherche sur site. Les gens le déploient encore parce que ça marche le premier jour. Le jour trente vient avec une facture CPU.
- Les appliances de recherche existaient avant le « search géré » cloud. Les entreprises utilisaient des boîtes de recherche propriétaires bien avant les offres hébergées d’aujourd’hui ; la douleur opérationnelle n’est pas nouvelle, juste reconditionnée.
Ce que MariaDB peut apporter à la recherche sur site (et où il casse)
MariaDB est excellent quand il s’agit vraiment d’un lookup indexé
MariaDB brille quand votre « recherche » consiste principalement à :
- Correspondances exactes (SKU, ID, email, nom d’utilisateur).
- Correspondances préfixes pouvant utiliser un index (par ex. table de mots-clés normalisés).
- Filtrage/tri sur colonnes structurées (catégorie, prix, statut, permissions).
- Jeux de données relativement petits où les scans complets restent peu coûteux et prévisibles.
La base vous donne une forte consistance, des jointures simples, des transactions et une seule surface opérationnelle.
Si votre produit doit « trouver les produits avec color=blue et price < 50 et en stock », MariaDB est l’adulte de la pièce.
FULLTEXT : utile, mais ne prétendez pas que c’est un moteur de recherche
FULLTEXT de MariaDB peut supporter une recherche basique sur site. C’est souvent le bon choix quand :
- Vous avez une seule langue, des besoins morphologiques limités et majoritairement des correspondances littérales.
- Vous acceptez une pertinence grossière et vous ne réglez pas le classement chaque semaine.
- Le corpus n’est pas énorme et la concurrence de requêtes est modérée.
- Vous n’avez pas besoin d’analyseurs sophistiqués (synonymes, règles de stemming par champ, n-grams pour l’autocomplete).
Mais FULLTEXT est l’endroit où l’optimisme obtient l’approbation budgétaire puis meurt en production.
Les failles apparaissent à quatre endroits :
- Contrôle du classement : vous disposez de moins de leviers, et ces leviers sont moins composables. Si vous avez besoin que « les correspondances sur le titre l’emportent sauf si le corps correspond fortement et que la fraîcheur compte », vous finirez par construire une logique de scoring personnalisée hors du SQL.
- Analyse linguistique : tokenisation, mots vides, stemming, synonymes — ce n’est pas le focus des moteurs relationnels. Vous pouvez patcher, mais vous patcherez pour toujours.
- Isolation opérationnelle : une charge lourde de recherche concurrence les écritures OLTP. Quand la recherche monte en charge, votre trafic de checkout ne devrait pas le ressentir.
- Flexibilité des requêtes : fuzziness, tolérance aux fautes de frappe, requêtes de phrase, proximité et boosting multi-champs sont possibles sous certaines formes mais douloureux à faire évoluer sans réécriture.
L’odeur en production : votre BD devient la boîte de recherche
Si vous exécutez la recherche dans MariaDB, vous liez le comportement de frappe de l’utilisateur à votre datastore primaire. Cela signifie :
- Chaque frappe pour un endpoint d’autocomplétion peut devenir une requête BD.
- La recherche peut déclencher des recherches fulltext qui se cachent mal.
- Les requêtes lentes volent le buffer pool et le CPU aux transactions.
C’est là que les équipes finissent par « résoudre » la recherche en ajoutant des réplicas en lecture. Ça marche jusqu’à ce que ça ne marche plus, et ça déplace
la défaillance vers le lag de réplication et les résultats incohérents. Ce n’est pas une stratégie ; c’est une tactique de retardement.
Blague n°1 : Si vous utilisez LIKE '%term%' en production, la base de données finira par vous appeler pour l’incident.
Ce qu’Elasticsearch vous achète réellement
C’est un moteur de recherche conçu pour ça avec des compromis assumés
Elasticsearch n’est pas « un remplaçant de base de données ». C’est un système d’indexation et de recherche optimisé pour :
- Requêtes full-text rapides sur de grands corpus.
- DSL de requête flexible : logique booléenne, phrase/proximité, fuzziness, boosting, poids par champ.
- Pipelines d’analyse de texte : tokenizers, filtres, synonymes, stemming, n-grams, analyseurs par champ.
- Mise à l’échelle horizontale avec shards et réplicas.
- Mises à jour quasi temps réel (cycles de refresh) plutôt que consistance transactionnelle stricte.
Opérationnellement, vous achetez de l’isolation et des réglages différents
La raison en production de déployer Elasticsearch est l’isolation : la charge de recherche appartient à un cluster séparé avec sa propre mise à l’échelle,
sa propre enveloppe de performance et son propre rayon d’explosion en cas de panne.
Elasticsearch vous donne des réglages que MariaDB n’a pas :
- Parallélisme au niveau des shards : fan-out des requêtes sur les shards, puis fusion des résultats. Vous payez un overhead de coordination ; vous gagnez du débit.
- Lifecycle management des index : rollover, rétention, merges de segments et tiering planifiables, pas devinés.
- La réindexation en tant qu’opération de première classe : les changements de schéma sont douloureux, mais l’écosystème attend la réindexation et fournit des workflows.
- Vitesse d’itération de la pertinence : changer analyseurs, boosting, synonymes et logique de requête peut se faire sans repenser le schéma relationnel.
Ce que vous achetez aussi : de nouveaux modes de panne
Elasticsearch vous permettra de créer un cluster qui semble sain et qui fonctionne très mal. Coupables typiques :
- Explosion de shards : trop de petits shards, trop d’overhead.
- Pression de merge de segments : le débit d’indexation s’effondre sous les merges.
- Pression sur le heap et pauses GC : la latence de queue devient un graphique en dents de scie.
- Erreurs de mapping : champs indexés incorrectement, entraînant des requêtes coûteuses ou une pertinence cassée.
- Intervalle de refresh trop bas : « pourquoi l’indexation est si lente ? » parce que vous faites la fête du commit chaque seconde.
Et oui, c’est un autre cluster. Un autre ensemble de sauvegardes/snapshots, de mises à jour et de surprises en on-call. Mais si la recherche
est une fonctionnalité produit, pas une case cochée, le cluster n’est pas optionnel. C’est le coût de faire des affaires.
Quand le cluster de recherche est indispensable (déclencheurs de décision)
« Indispensable » est un mot fort. Voici donc des déclencheurs opérationnels forts. Si vous en avez deux ou plus, arrêtez de débattre et
commencez à planifier le cluster de recherche.
1) Autocomplétion et typeahead à traffic réel
L’autocomplétion change le profil de requête : au lieu d’une recherche par session, vous avez plusieurs requêtes par interaction utilisateur.
Les requêtes sont courtes, faiblement sélectives et difficiles à mettre en cache. MariaDB peut le faire, mais bruyamment.
2) La pertinence est un indicateur produit, pas un agrément
Si les parties prenantes parlent de « qualité des résultats », « marchandisage », « booster cette catégorie », « rétrograder hors stock »,
« synonymes » ou « tolérance aux fautes », vous êtes en territoire moteur de recherche. Les contrôles de pertinence de FULLTEXT ne sont pas
là où vous voulez passer votre carrière.
3) Multilingue, morphologie ou langage de domaine
Dès que vous avez besoin d’analyseurs par langue, de tokenisation personnalisée (numéros de pièce, noms chimiques, citations juridiques),
ou de synonymes qui changent sans déploiement, Elasticsearch devient le choix pratique.
4) Vous avez besoin de signaux de classement hybrides
Les résultats de recherche dépendent de plus en plus de signaux comme popularité, fraîcheur, taux de clics, personnalisation, disponibilité
et règles business. Elasticsearch peut les mélanger avec function scores et filtres structurés sans transformer le SQL en performance art.
5) Votre base OLTP est une ressource sacrée
Si MariaDB gère aussi l’argent, les commandes, l’auth, l’inventaire ou toute chose qui réveille les dirigeants, vous voulez la recherche en dehors.
Les pics de recherche ne doivent pas pouvoir affamer le débit transactionnel.
6) Vous exigez une fraîcheur « assez bonne », pas une consistance stricte
Elasticsearch est conçu pour le quasi temps réel. Si « les éléments deviennent recherchables en ~1–30 secondes » est acceptable, vous pouvez découpler
l’indexation des écritures. Si vous avez vraiment besoin d’une lecture après écriture immédiate pour les résultats de recherche, vous concevez
explicitement pour ça (chemin de lecture hybride, indices UI, ou indexation synchrone) ou vous restez dans la base et acceptez les contraintes.
7) Vous voulez une évolution de schéma de recherche sans interruption
Vous changerez mappings/analyseurs. Si votre organisation ne peut pas tolérer une indisponibilité de recherche pendant la migration des index,
vous voulez un moteur avec des workflows de réindexation et des coupures d’alias intégrés.
Blague n°2 : Un cluster de recherche est comme une seconde machine à espresso — personne ne veut la maintenir, mais tout le monde remarque quand elle disparaît.
L’option sensée par défaut : architecture hybride
Patron : MariaDB comme source de vérité, Elasticsearch comme index optimisé en lecture
Dans la plupart des entreprises réelles, la meilleure réponse est les deux :
- MariaDB reste la source de vérité pour les entités, permissions, transactions et intégrité référentielle.
- Elasticsearch stocke un document dénormalisé, optimisé pour la requête, par entité (produit, article, ticket, annonce).
- Les requêtes de recherche touchent Elasticsearch en premier ; les résultats renvoient des IDs ; l’application réhydrate éventuellement les détails depuis MariaDB.
Cette architecture a un nom en pratique : accepter la consistance éventuelle, mais concevoir l’écart.
Vous bâtissez un pipeline d’indexation, vous surveillez le retard, et vous décidez du comportement de l’UI quand l’index est en retard.
Pipeline d’indexation : votre fiabilité se trouve ici, pas dans la requête
Le pipeline peut être :
- CDC (change data capture) depuis les binlogs MariaDB → stream → indexeur → Elasticsearch.
- Pattern outbox : l’application écrit l’entité + un événement outbox dans la même transaction BD ; un worker consomme et indexe.
- Réindexation périodique par batch pour les systèmes plus simples, avec des mises à jour incrémentales quand c’est possible.
Le pattern outbox est ennuyeux. C’est pourquoi il fonctionne. Il transforme « avons-nous indexé ceci ? » en un job réessayable avec un registre durable.
Choix de conception clés qui préviennent la douleur future
- Versions d’index immuables : créez
products_v42, puis basculez un aliasproducts_current. Ne « modifiez jamais en place » quand les analyseurs changent. - Stocker les champs sources nécessaires au rendu : évitez les « tempêtes de réhydratation » où chaque résultat de recherche devient N requêtes BD.
- Séparer les types de requêtes : index d’autocomplete vs index de recherche complète vs index de navigation par catégorie. Analyseurs différents, stratégies de shards différentes.
- SLO explicite de fraîcheur : « 99% des mises à jour recherchables sous 30s. » Puis mesurez-le.
Une citation, parce que l’opérationnel est une philosophie avec pager duty
« L’espoir n’est pas une stratégie. »
— Gen. Gordon R. Sullivan
Tâches en production : commandes, sorties, décisions (12+)
Ce sont les contrôles que vous lancez quand quelqu’un dit « la recherche est lente » et que tout le monde regarde la base de données.
Chaque tâche inclut une commande exécutable, un extrait réaliste de sortie, ce que cela signifie et la décision que vous prenez.
Tâche 1 : Identifier les requêtes MariaDB les plus chronophages (Performance Schema)
cr0x@server:~$ mysql -e "SELECT DIGEST_TEXT, COUNT_STAR, ROUND(SUM_TIMER_WAIT/1e12,2) AS total_s, ROUND(AVG_TIMER_WAIT/1e12,4) AS avg_s FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 3\G"
*************************** 1. row ***************************
DIGEST_TEXT: SELECT * FROM products WHERE name LIKE ? ORDER BY updated_at DESC LIMIT ?
COUNT_STAR: 248901
total_s: 9123.55
avg_s: 0.0367
*************************** 2. row ***************************
DIGEST_TEXT: SELECT id FROM products WHERE MATCH(title,body) AGAINST (? IN BOOLEAN MODE) LIMIT ?
COUNT_STAR: 55432
total_s: 2101.22
avg_s: 0.0379
Ce que cela signifie : Vous dépensez des heures CPU sur des requêtes LIKE et FULLTEXT. La latence moyenne paraît acceptable, mais le volume en fait le dominant.
Décision : Si ce sont des endpoints visibles par les utilisateurs, isolez-les. Au minimum : stoppez les recherches LIKE avec wildcard ; envisagez Elasticsearch si pertinence/autocomplete sont requis.
Tâche 2 : Confirmer si LIKE utilise un index (EXPLAIN)
cr0x@server:~$ mysql -e "EXPLAIN SELECT * FROM products WHERE name LIKE '%headphones%' LIMIT 20\G"
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: products
type: ALL
possible_keys: idx_products_name
key: NULL
key_len: NULL
rows: 1843921
Extra: Using where
Ce que cela signifie : Scan de table complet. Votre index sur name est inutilisable avec un wildcard en tête.
Décision : Remplacez par FULLTEXT lorsque c’est acceptable, ou migrez vers Elasticsearch pour les correspondances par sous-chaîne/tolérance aux fautes.
Tâche 3 : Vérifier le buffer pool et la pression de lecture MariaDB
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';"
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Innodb_buffer_pool_reads| 18499231 |
+-------------------------+----------+
+----------------------------------+-------------+
| Variable_name | Value |
+----------------------------------+-------------+
| Innodb_buffer_pool_read_requests | 83199231121 |
+----------------------------------+-------------+
Ce que cela signifie : Le ratio est passable, mais les lectures disque absolues sont élevées. Les requêtes de recherche peuvent dégrader la localité du cache.
Décision : Si l’OLTP est impacté, isolez la recherche. Augmenter le buffer pool aide jusqu’à un certain point ; mieux vaut déplacer les patterns d’IO de recherche hors de la base primaire.
Tâche 4 : Détecter le lag de réplication si vous avez délégué la recherche aux réplicas
cr0x@server:~$ mysql -e "SHOW SLAVE STATUS\G" | egrep "Seconds_Behind_Master|Slave_IO_Running|Slave_SQL_Running"
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 47
Ce que cela signifie : Le réplica a ~47 secondes de retard. Vos résultats de recherche peuvent être obsolètes, et les lectures peuvent s’accumuler.
Décision : Soit acceptez la staleness explicitement (indice UX, modèle éventuel), soit cessez d’utiliser des réplicas comme béquille de recherche. C’est un signal fort pour un index de recherche.
Tâche 5 : Mesurer le comportement de FULLTEXT MariaDB (et sa sélectivité)
cr0x@server:~$ mysql -e "EXPLAIN SELECT id FROM products WHERE MATCH(title,body) AGAINST ('wireless headphones' IN NATURAL LANGUAGE MODE) LIMIT 20\G"
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: products
type: fulltext
possible_keys: ft_title_body
key: ft_title_body
key_len: 0
rows: 120000
Extra: Using where
Ce que cela signifie : FULLTEXT est utilisé, mais il s’attend à examiner ~120k lignes pour cette requête. Pas terrible une fois ; terrible à l’échelle.
Décision : Si vous avez besoin de QPS élevé, d’autocomplete ou de multiples champs avec boosting, passez à Elasticsearch et concevez la taille des shards en conséquence.
Tâche 6 : Vérifier rapidement la santé du cluster Elasticsearch
cr0x@server:~$ curl -s http://localhost:9200/_cluster/health?pretty
{
"cluster_name" : "search-prod",
"status" : "yellow",
"timed_out" : false,
"number_of_nodes" : 6,
"number_of_data_nodes" : 4,
"active_primary_shards" : 48,
"active_shards" : 48,
"unassigned_shards" : 48
}
Ce que cela signifie : Jaune avec réplicas non assignés. Vous pouvez servir des lectures, mais vous n’avez pas de redondance pour beaucoup de shards.
Décision : Traitez comme un incident de fiabilité. Corrigez l’allocation (watermarks disque, perte de nœud, nombre de réplicas) avant d’optimiser les performances ; sinon vous optimisez un cluster boiteux.
Tâche 7 : Identifier une explosion de shards (nombre et tailles)
cr0x@server:~$ curl -s http://localhost:9200/_cat/shards?v | head
index shard prirep state docs store ip node
products_v42 0 p STARTED 812341 3.2gb 10.0.1.21 es-data-1
products_v42 0 r UNASSIGNED
products_v42 1 p STARTED 799002 3.1gb 10.0.1.22 es-data-2
products_v42 1 r UNASSIGNED
Ce que cela signifie : Vous avez des réplicas non assignés, et les tailles de shard sont plutôt petites. Si des centaines/milliers de shards ressemblent à ça, l’overhead dominera.
Décision : Consolidez les shards (moins de primaires), corrigez l’allocation des réplicas et arrêtez de créer un index par locataire à moins d’aimer la douleur.
Tâche 8 : Vérifier la distribution des latences de recherche (pas les moyennes)
cr0x@server:~$ curl -s http://localhost:9200/_nodes/stats/indices/search?pretty | egrep -A3 "query_total|query_time_in_millis"
"query_total" : 9012331,
"query_time_in_millis" : 81233122,
"query_current" : 37
Ce que cela signifie : Les stats agrégées montrent la charge, pas la latence en queue. Mais un query_current élevé sous charge suggère une saturation.
Décision : Corrélez avec le p95/p99 applicatif et les métriques de rejet de threadpool ensuite. Ne proclamez pas victoire sur la base d’une moyenne.
Tâche 9 : Vérifier les rejets du threadpool (symptôme classique « c’est lent »)
cr0x@server:~$ curl -s http://localhost:9200/_nodes/stats/thread_pool/search?pretty | egrep -A6 "\"search\"|rejected"
"search" : {
"threads" : 13,
"queue" : 1000,
"active" : 13,
"rejected" : 21844,
"largest" : 13,
"completed" : 99123312
}
Ce que cela signifie : Les requêtes de recherche sont rejetées. Les utilisateurs voient des timeouts et des échecs intermittents.
Décision : Scale out, réduisez le coût des requêtes (filtres, fielddata, pagination profonde), ajoutez du caching ou appliquez du throttling. Les rejets signifient que vous avez dépassé la capacité maintenant.
Tâche 10 : Détecter les requêtes Elasticsearch lentes via le slowlog
cr0x@server:~$ sudo tail -n 5 /var/log/elasticsearch/search-prod_index_search_slowlog.log
[2025-12-30T10:21:41,902][WARN ][index.search.slowlog.query] [es-data-2] [products_v42][1] took[2.8s], took_millis[2803], total_hits[200000], search_type[QUERY_THEN_FETCH], source[{"query":{"match":{"body":{"query":"wireless headphones","operator":"and"}}},"sort":["_score",{"updated_at":"desc"}],"from":0,"size":10000}]
Ce que cela signifie : Pagination profonde (size:10000) plus tri est coûteux et souvent inutile.
Décision : Implémentez search_after ou limitez size, retournez moins de résultats, ou utilisez un endpoint de navigation séparé avec tris précomputés.
Tâche 11 : Vérifier le nombre de segments et la pression de merge
cr0x@server:~$ curl -s http://localhost:9200/_cat/segments/products_v42?v | head
index shard prirep segment generation docs.count size committed searchable version compound
products_v42 0 p _0 0 5123 9.8mb true true 9.11.1 true
products_v42 0 p _1 1 4981 9.5mb true true 9.11.1 true
Ce que cela signifie : Beaucoup de petits segments peuvent impliquer des refreshs fréquents et des merges lourds ensuite.
Décision : Pour une indexation intense, augmentez l’intervalle de refresh, regroupez les mises à jour ou utilisez l’indexation en bulk. Laissez les merges respirer.
Tâche 12 : Valider l’intervalle de refresh de l’index (fraîcheur vs débit)
cr0x@server:~$ curl -s http://localhost:9200/products_v42/_settings?pretty | egrep -A3 "refresh_interval"
"refresh_interval" : "1s",
"number_of_shards" : "12",
"number_of_replicas" : "1"
Ce que cela signifie : 1s de refresh est agressif. Excellent pour la fraîcheur, mauvais pour le débit d’indexation et le churn de segments.
Décision : Si vous n’avez pas besoin d’une fraîcheur à la seconde, migrez vers 5–30s et mesurez les améliorations d’ingestion et de latence des requêtes.
Tâche 13 : Confirmer les problèmes de mapping qui causent des requêtes coûteuses
cr0x@server:~$ curl -s http://localhost:9200/products_v42/_mapping?pretty | egrep -n "\"name\"|\"type\"|\"keyword\"" | head
34: "name" : {
35: "type" : "text"
36: },
78: "category" : {
79: "type" : "text"
80: }
Ce que cela signifie : category est seulement text ; les filtres/agrégations dessus seront lents ou nécessiteront fielddata (mauvaises nouvelles).
Décision : Ajoutez des sous-champs keyword pour les filtres/agrégations exacts. Planifiez une réindexation avec bascule d’alias.
Tâche 14 : Mesurer le retard d’indexation (santé du pipeline)
cr0x@server:~$ mysql -e "SELECT MAX(updated_at) AS db_latest, (SELECT MAX(indexed_at) FROM search_index_audit) AS indexed_latest\G"
*************************** 1. row ***************************
db_latest: 2025-12-30 10:27:12
indexed_latest: 2025-12-30 10:26:02
Ce que cela signifie : L’index a ~70 secondes de retard par rapport aux écritures DB (si indexed_at suit les mises à jour d’index réussies).
Décision : Si votre SLO de fraîcheur est 30s, c’est un incident. Inspectez le backlog de l’indexeur, les échecs bulk ou le throttling d’ingestion ES.
Tâche 15 : Vérifier le système de fichiers et la latence disque sur les nœuds data
cr0x@server:~$ iostat -x 1 3
Device r/s w/s rkB/s wkB/s await svctm %util
nvme0n1 210.3 98.2 8123.4 4312.8 18.42 0.62 97.8
Ce que cela signifie : Le disque est presque saturé, avec un await élevé. Les recherches et merges sont gourmands en IO.
Décision : Augmentez le débit de stockage (plus de nœuds, disques plus rapides), réduisez le nombre de shards et évitez l’indexation lourde pendant les pics de requêtes.
Mode opératoire de diagnostic rapide
Quand « la recherche est lente », vous voulez un triage en trois passes : symptômes côté utilisateur, saturation du backend, puis conception des requêtes/index.
Ne commencez pas par deviner. Commencez par restreindre.
Première étape : confirmer où le temps est passé
- Métriques applicatives : latence p50/p95/p99 pour l’endpoint de recherche, et taux d’erreur/timeouts.
- Répartition des dépendances : temps BD vs temps ES vs rendu applicatif. Si vous n’avez pas de tracing, ajoutez-le. Vos suppositions coûtent cher.
- Concurrence : combien de requêtes en vol s’accumulent ? La mise en queue est le tueur silencieux.
Si le p50 est correct et que le p99 est mauvais, vous avez une saturation, des pauses GC, de la contention de lock ou un petit pourcentage de requêtes pathologiques.
Si tout est uniformément lent, vous avez des contraintes systémiques : disque, CPU ou réseau.
Deuxième étape : vérifier les signaux de saturation évidents
- MariaDB : slow query log, CPU, misses du buffer pool InnoDB, waits de verrous, lag de réplication (si vous utilisez des réplicas pour la recherche).
- Elasticsearch : rejets de threadpool, GC, utilisation du heap, utilisation/latence disque, shards non assignés, nœuds chauds.
- Infrastructure : IO des nœuds (
iostat), steal CPU (virtualisé), pertes/latence réseau entre l’app et les nœuds de recherche.
Troisième étape : isoler les formes de requêtes coûteuses
- Pagination profonde (from/size) et tri sur champs non analysés.
- Requêtes wildcard et wildcards en tête dans SQL.
- Agrégations haute cardinalité (ES) ou prédicats peu sélectifs (SQL).
- Mauvais mappings forçant fielddata ou scripting.
- Fraîcheur trop agressive (intervalle de refresh ES trop bas) pendant une indexation lourde.
Quatrième étape : décider si c’est un problème de capacité ou de conception
Si vous voyez des rejets de threadpool, saturation disque ou lag de réplica, vous avez un problème de capacité maintenant.
Si les requêtes sont fondamentalement coûteuses (wildcards, pagination profonde, faible sélectivité), la capacité ne fera que masquer le problème.
Trois mini-histoires d’entreprise (réalistes, anonymisées)
Mini-histoire 1 : l’incident causé par une mauvaise hypothèse
Une place de marché de taille moyenne a lancé les « suggestions de recherche » parce que le produit voulait que l’UI paraisse réactive. L’équipe ingénierie a pris
le raccourci évident : une requête MariaDB sur la table products, WHERE name LIKE 'term%', avec un index sur name.
Ça a fonctionné en staging. Ça a fonctionné en production aussi — à 9h.
La mauvaise hypothèse était que « la recherche préfixe utilise un index, donc c’est bon marché ». Ce n’est vrai qu’à moitié. En production,
les utilisateurs tapaient des préfixes courts : « a », « b », « s ». Ces préfixes correspondaient à d’énormes plages. La requête utilisait l’index, oui,
mais elle parcourait quand même de grandes portions, effectuait des tris et retournait les top-N. La latence a grimpé. Le CPU a augmenté. Les connexions se sont empilées.
Le checkout a commencé à expirer parce qu’il partageait la même base.
La réponse d’incident a été classique : vertical scaling de la base, ajout d’un réplica en lecture, ajout de cache. Le cache a aidé pour les préfixes populaires,
jusqu’à ce que des promotions changent ce que les gens tapaient et que le cache rate de nouveau. Le réplica a pris du retard, les suggestions sont devenues incohérentes,
et le support a reçu des tickets sur des « produits manquants ».
La correction n’a pas été héroïque. Ils ont séparé le cas d’usage recherche. L’autocomplete est passé sur Elasticsearch avec un analyseur n-gram.
Ils ont limité le taux de suggestions par utilisateur, ajouté un debounce côté frontend et fixé un plafond strict sur le nombre de résultats.
MariaDB est redevenue bonne pour les transactions. La leçon n’était pas « Elasticsearch est plus rapide » mais « la forme des entrées utilisateur compte plus que votre définition d’index ».
Mini-histoire 2 : l’optimisation qui s’est retournée contre eux
Un portail de docs interne utilisait Elasticsearch et avait une latence acceptable — jusqu’à ce qu’un ingénieur « optimise la pertinence ». Le plan :
augmenter les champs en requête (title, headings, body, tags), ajouter de la fuzziness, des synonymes et booster les documents récents.
De bonnes intentions. Le DSL de requête a gonflé en une petite nouvelle.
Ils l’ont déployé juste avant une vague d’onboarding. Le trafic de recherche a explosé. Soudain le p99 est passé à des secondes, pas des millisecondes.
Les nœuds étaient « sains », mais les rejets du threadpool ont grimpé. Des pauses GC sont apparues. Le cluster n’est pas tombé ; il est juste devenu poliment inutile.
Le retour de bâton venait de quelques coûts composés : fuzziness sur plusieurs champs, un minimum-should-match bas, et un grand size parce que l’UI voulait afficher
« beaucoup de résultats » sans pagination. Chaque requête s’est étendue en de nombreuses combinaisons de termes et a tiré de larges fenêtres de résultats. CPU et heap ont augmenté.
Les caches de requête étaient inefficaces car chaque requête était unique.
La reprise a consisté à traiter les fonctionnalités de pertinence comme un budget. Ils ont limité la fuzziness aux champs courts, resserré le minimum-should-match,
réduit size, supprimé les tris coûteux et introduit une approche en deux phases : d’abord une récupération bon marché, puis un reranking optionnel pour le top-N.
La pertinence s’est améliorée et le cluster a survécu à l’onboarding.
La morale : « meilleure pertinence » peut être une attaque par déni de service que vous vous infligez vous-même. Rendez-la mesurable et bornée.
Mini-histoire 3 : la pratique ennuyeuse mais correcte qui a sauvé la journée
Un site retail utilisait une recherche hybride : MariaDB pour la vérité, Elasticsearch pour la recherche. Rien de sophistiqué. Ce qu’ils avaient :
une table outbox dans MariaDB, traitée par une petite flotte de workers. Chaque mise à jour d’index était un job. Chaque job avait des réessais.
Chaque échec finissait dans une dead-letter avec suffisamment de contexte pour rejouer.
Un après-midi, un déploiement a introduit un changement de mapping qui rejetait des documents pour un sous-ensemble de produits — seulement ceux avec un attribut bizarre.
Le worker a commencé à réessayer ces jobs en boucle. Sans garde-fous, cela serait devenu un backlog infini, puis un incident « des éléments manquent dans la recherche »,
puis une longue nuit.
À la place, les contrôles ennuyeux ont fonctionné. Le worker avait un coupe-circuit : après N échecs pour un job, il le mettait en quarantaine.
Les alertes de retard d’indexation se sont déclenchées car le SLO de fraîcheur a été dépassé. L’on-call pouvait immédiatement voir quels documents avaient échoué et pourquoi,
car l’entrée outbox stockait l’ID de l’entité et le type d’opération.
Ils ont rollbacké le changement de mapping, rejoué les jobs mis en quarantaine et sont revenus à l’état stable sans deviner.
Pas d’héroïsme, pas d’archéologie SQL manuelle. Juste un registre et une boucle de réessai.
La leçon : votre pipeline d’indexation est un système distribué. Traitez-le comme tel, même si c’est « juste la recherche ».
Erreurs fréquentes : symptôme → cause racine → correction
1) Symptôme : la recherche expire par intermittence pendant les pics de trafic
Cause racine : La recherche partage les ressources MariaDB avec l’OLTP ; les pools de connexions saturent ; les recherches lentes se mettent en file derrière les écritures.
Correction : Isolez la charge de recherche (Elasticsearch ou cluster DB séparé), appliquez des timeouts sur les requêtes et implémentez des limites de débit pour l’autocomplete.
2) Symptôme : « on a ajouté un réplica et c’est pire »
Cause racine : Le lag de réplica provoque des résultats incohérents ; les retries applicatifs amplifient la charge ; les attentes read-after-write se brisent.
Correction : Cessez d’utiliser les réplicas comme moteurs de recherche. Si vous devez le faire, acceptez explicitement la staleness et affichez-la en UX ; sinon construisez un index.
3) Symptôme : Elasticsearch est « sain » mais les requêtes sont lentes
Cause racine : Saturation du threadpool, shards chauds ou pauses GC ; la santé du cluster ne capture pas la performance.
Correction : Vérifiez les rejets de thread_pool, la pression du heap, l’IO disque et la distribution des shards ; scalez ou réduisez le coût des requêtes.
4) Symptôme : filtres/agrégations terriblement lents dans Elasticsearch
Cause racine : Filtrage sur des champs text, mapping keyword manquant ; fielddata activé pour « réparer », explosion du heap.
Correction : Ajoutez des sous-champs keyword, réindexez et supprimez les hacks basés sur fielddata.
5) Symptôme : l’indexation ralentit exactement pendant les pics
Cause racine : Intervalle de refresh trop bas, merges lourds et charge de requêtes simultanée ; l’IO se conteste.
Correction : Augmentez l’intervalle de refresh, utilisez le bulk, planifiez la réindexation hors-peak et assurez-vous que les nœuds data disposent de marge IO.
6) Symptôme : « des nouveaux éléments manquent dans les résultats »
Cause racine : Retard du pipeline d’indexation, jobs échoués ou rejets silencieux par le mapping.
Correction : Suivez explicitement le retard de fraîcheur, implémentez des dead-letter/quarantaine, alertez sur les documents rejetés et fournissez des outils de rejouage.
7) Symptôme : les changements de pertinence cassent après un déploiement
Cause racine : Les changements d’analyseur/mapping nécessitent une réindexation ; les changements partiels créent un comportement mixte entre segments et champs.
Correction : Utilisez des indexes versionnés + coupure d’alias. Ne « changez pas les pneus en roulant » en modifiant les analyseurs en place.
8) Symptôme : le CPU MariaDB explose à cause de la recherche, même avec FULLTEXT
Cause racine : Requêtes peu sélectives, forte concurrence, ou charge mixte ; FULLTEXT coûte toujours du CPU et de la mémoire.
Correction : Déléguez la recherche, ou contraignez-la (longueur minimale, filtres), ajoutez du cache pour les requêtes fréquentes et limitez la concurrence.
Listes de contrôle / plan pas à pas
Pas à pas : décider si Elasticsearch est indispensable
- Listez les comportements de recherche requis : autocomplete, tolérance aux fautes, synonymes, multilingue, requêtes de phrase, boosting, signaux de classement personnalisés.
- Mesurez le profil de requête : QPS, p95/p99, concurrence et si les utilisateurs génèrent plusieurs requêtes par interaction.
- Vérifiez l’impact sur la BD : les requêtes de recherche apparaissent-elles parmi les plus consommateurs de temps ? Les endpoints OLTP sont-ils impactés pendant les pics de recherche ?
- Définissez l’exigence de fraîcheur : 1–30 secondes est-elle acceptable ? Si oui, découplez l’indexation. Si non, prévoyez un chemin de lecture hybride ou reconsidérez le périmètre.
- Décidez de l’isolation : si l’OLTP est critique pour le business, isolez la recherche par défaut.
- Estimez le budget opérationnel : on-call, cadence de mises à jour, stratégie de snapshots et capacity planning pour shards/heap/disque.
Pas à pas : implémenter une architecture de recherche hybride sans la fragiliser
- Définissez le modèle de document : dénormalisez les champs nécessaires à la recherche et au rendu de la page de résultats.
- Choisissez un mécanisme d’indexation : outbox ou CDC. Préférez outbox pour un contrôle des échecs.
- Construisez l’idempotence : indexer la même mise à jour d’entité deux fois doit être sûr.
- Ajoutez des tables d’audit/métriques : suivez le dernier timestamp indexé et les compteurs d’échec.
- Utilisez des index versionnés :
items_v1,items_v2avec un aliasitems_current. - Plan de réindexation : build en background, dual-write (optionnel), puis coupure d’alias, puis retrait de l’ancien index.
- Garde-fous : limitez la taille des résultats, throttlez l’autocomplete, bloquez la pagination profonde et fixez des timeouts de requête.
- UX de secours : si ES est down, décidez d’un fallback (recherche BD limitée) ou d’un message d’erreur convivial. Choisissez-en un et testez-le.
Checklist opérationnelle : empêcher qu’Elasticsearch ne devienne une maison hantée
- Politique de sizing des shards (éviter les shards minuscules ; éviter l’excès).
- Dashboards pour : rejets de threadpool, usage du heap, GC, latence disque, taux d’indexation, temps de refresh/merge.
- Stratégie de snapshots et calendrier de tests de restore.
- SLO explicites : p95/p99 des requêtes et retard de fraîcheur d’indexation.
- Runbooks pour : shards non assignés, nœuds chauds, documents rejetés, bascule de réindex.
FAQ
1) MariaDB FULLTEXT peut-il remplacer Elasticsearch pour un petit site ?
Oui, si la « recherche » est mono-langue, faible QPS, avec des attentes de pertinence modestes et sans autocomplétion.
Dès que le produit demande synonymes, fuzziness ou boosting multi-champs, planifiez la migration.
2) Elasticsearch c’est seulement pour le « big data » ?
Non. Elasticsearch sert la complexité des comportements de recherche et l’isolation des charges. De petits jeux de données peuvent le justifier
si l’UX dépend de la pertinence et du typeahead.
3) Quelle est la plus grande différence opérationnelle entre la recherche MariaDB et Elasticsearch ?
La recherche MariaDB concurrence les écritures et les transactions ; la recherche Elasticsearch concurrence l’indexation et les merges.
Dans les deux cas, vous gérez la contention — mais dans Elasticsearch vous pouvez l’isoler de l’OLTP.
4) Pourquoi ne pas ajouter plus de réplicas MariaDB pour la recherche ?
Les réplicas échangent CPU contre lag de réplication et complexité opérationnelle. C’est acceptable pour la montée en lecture de requêtes prévisibles.
Les requêtes de recherche sont souvent imprévisibles et peu sélectives, et les réplicas ne résoudront pas les limites de pertinence.
5) Comment gérer les permissions (ACL) dans Elasticsearch ?
Soit filtrez par champs de permission au moment de la requête (le document contient tenant/org/visibility), soit utilisez des index par locataire si leur nombre est faible.
Évitez les index par utilisateur sauf si vous aimez les longues fins de semaine. Pour des ACL complexes, envisagez de retourner des IDs candidats puis d’appliquer les permissions côté application.
6) Comment réindexer sans downtime ?
Utilisez des index versionnés plus un alias. Construisez items_v2, backfillez, puis déplacez atomiquement l’alias items_current de v1 à v2.
Gardez v1 pour rollback tant que la confiance n’est pas établie.
7) Quelle est l’erreur de scaling Elasticsearch la plus courante ?
Trop de shards. Cela augmente le coût de coordination, l’overhead mémoire et le temps de recovery. Moins de shards correctement dimensionnés battent de nombreux petits shards
dans la plupart des environnements de production.
8) Les résultats doivent-ils être réhydratés depuis MariaDB ou servis entièrement depuis Elasticsearch ?
Si vous réhydratez chaque ligne de résultat depuis MariaDB, vous risquez de transformer une recherche en N+1 requêtes sous charge.
Stockez suffisamment de champs dans Elasticsearch pour rendre la page de résultats. Réhydratez uniquement lorsque l’utilisateur clique sur la page de détail.
9) Puis-je utiliser Elasticsearch comme source de vérité ?
Vous pouvez, mais vous ne devriez généralement pas. Elasticsearch n’est pas optimisé pour l’intégrité relationnelle et les workflows transactionnels.
Utilisez-le comme index dérivé, et conservez votre vérité dans MariaDB (ou un autre store transactionnel).
10) Quelle fraîcheur peut-on attendre raisonnablement d’Elasticsearch ?
Typiquement de l’ordre de secondes à dizaines de secondes, selon l’intervalle de refresh, la charge d’indexation et la conception du pipeline.
Si vous avez besoin d’« immédiatement recherchable », vous accepterez soit un coût d’indexation plus élevé, soit un chemin de lecture hybride.
Conclusion : prochaines étapes que vous pouvez exécuter
Si votre recherche sur site est essentiellement un filtrage structuré avec des correspondances mot-clé occasionnelles, MariaDB peut tenir —
surtout si vous gardez les requêtes sélectives et évitez les fantasmes de wildcard. Mais si la recherche est une fonctionnalité produit avec autocompletion,
tolérance aux fautes, réglage de pertinence et ranking multi-champs, le cluster de recherche n’est pas un luxe. C’est la bonne frontière à poser.
Prochaines étapes pratiques :
- Lancez les diagnostics : identifiez si MariaDB paie la facture recherche aujourd’hui (slow queries, CPU, pression du buffer).
- Décidez de l’isolation : si OLTP et recherche partagent les ressources, séparez-les avant que le prochain pic ne le fasse pour vous.
- Concevez le pipeline : choisissez outbox ou CDC, définissez un SLO de fraîcheur et construisez réessais + quarantaine.
- Implémentez des index versionnés : les coupures d’alias sont la manière d’éviter que la réindexation devienne une panne.
- Posez des garde-fous : limitez les fenêtres de résultats, appliquez des throttles pour l’autocomplete et interdisez la pagination profonde dans vos contrats API.
Construisez la recherche comme si les humains l’utilisaient à grande échelle : avec impatience, créativité et tous en même temps. Ce n’est pas du pessimisme.
C’est de l’opérationnel.