Comprendre Elasticsearch #1 : les bases à connaître avant de l’optimiser
Elasticsearch est souvent présenté comme un moteur de recherche simple à prendre en main : on envoie des documents, Elasticsearch crée son mapping, puis il suffit d’interroger les données.
Sur un petit projet, cette approche peut effectivement fonctionner.
Mais lorsque les volumes augmentent — par exemple sur un catalogue comportant plusieurs millions de références — les choix qui semblaient anodins peuvent rapidement devenir déterminants pour les performances.
Pourquoi une requête est-elle rapide dans un cas et beaucoup plus lente dans un autre ?
Pourquoi certaines recherches avec des wildcards sont-elles particulièrement coûteuses ?
Pourquoi le nombre de shards a-t-il autant d’importance ?
Et surtout : que se passe-t-il réellement lorsqu’un document est indexé puis recherché dans Elasticsearch ?
Avant de chercher à optimiser Elasticsearch, il faut comprendre ce qui se passe sous le capot.
Dans ce premier article, nous allons donc revenir sur les fondamentaux : shards, segments, index inversé, Doc Values, analyse des textes et déroulement d’une recherche. L’objectif n’est pas de rentrer dans chaque détail de Lucene, mais de disposer du modèle mental nécessaire pour comprendre les problématiques de performance.
À retenir : Elasticsearch n’est pas simplement une base dans laquelle on stocke des documents JSON. C’est avant tout un moteur de recherche distribué, construit sur Apache Lucene, dont les mécanismes d’indexation conditionnent directement les performances des requêtes.
Elasticsearch, c’est quoi exactement ?
Elasticsearch est une solution distribuée conçue pour la recherche et l’analyse de données. Elle permet notamment de travailler avec des données structurées, non structurées et, selon les usages, vectorielles.
Son fonctionnement repose sur des documents JSON, organisés dans des index.
On peut donc utiliser Elasticsearch comme une forme de base documentaire NoSQL, mais il est important de garder en tête son objectif premier : rechercher et analyser efficacement des données.
Cette distinction est essentielle lorsqu’on commence à réfléchir à son architecture.
Une base de données classique et un moteur de recherche ne répondent pas nécessairement aux mêmes problématiques. Elasticsearch va notamment construire des structures d’index spécialisées pour retrouver rapidement les documents correspondant à une requête.
Et pour comprendre ces mécanismes, il faut descendre d’un niveau.
L’architecture d’Elasticsearch en quelques notions
Pour simplifier, on peut représenter une base Elasticsearch selon cette hiérarchie : Cluster → nœuds → index → shards → segments
Chaque élément joue un rôle précis.
- Un nœud héberge une partie des données et participe au traitement des requêtes.
- Un index regroupe des documents partageant une même logique de données.
- Un shard est une partition d’un index.
Chaque shard repose sur un moteur Lucene autonome.
Chaque shard est lui-même composé de plusieurs segments.
C’est principalement dans les shards et les segments que se joue une grande partie du fonctionnement du moteur de recherche.
Les shards : répartir les données et la charge
Un shard peut être vu comme une partition indépendante d’un index.
Derrière cette notion se trouve un moteur Lucene autonome, avec notamment son propre dictionnaire de termes, ses segments, ses fichiers, son cache et ses structures internes.
Lorsqu’un document est indexé, Elasticsearch doit déterminer dans quel shard il doit être placé. Pour cela, Elasticsearch utilise notamment l’identifiant du document afin de déterminer le shard cible.
Prenons un exemple volontairement simple.
Imaginons un index composé de 5 shards. Si le résultat du hash de l’identifiant d’un document est 18, le calcul peut être représenté ainsi :
18 % 5 = 3
Le document est alors affecté au shard correspondant.
Cette répartition permet notamment de distribuer les données et la charge de recherche entre plusieurs machines.
Pourquoi cette notion est importante ?
Parce qu’une requête sur un index peut devoir être exécutée sur plusieurs shards. Les recherches sont alors distribuées et traitées en parallèle avant que les résultats soient regroupés.Le nombre et la taille des shards ne sont donc pas des détails purement administratifs : ils participent directement au fonctionnement du moteur.
C’est justement l’un des sujets sur lesquels nous reviendrons dans le deuxième article consacré à l’optimisation d’Elasticsearch.
Les segments : là où Lucene indexe réellement les données
Chaque shard est composé de segments. Pour comprendre leur rôle, il faut s’intéresser au principe d’index inversé.
Prenons une phrase très simple : « Le chat mange la souris »
Lorsqu’un champ de type texte est indexé, Lucene va analyser cette chaîne à l’aide d’un tokenizer et de différents filtres.
Selon la configuration retenue, les mots peuvent notamment être normalisés en minuscules et certains mots peu pertinents, comme « le » ou « la », peuvent être supprimés grâce à un filtre de stop words.
On peut alors obtenir quelque chose comme :
- chat
- mange
- souris
Lucene construit ensuite une structure permettant de retrouver les documents contenant ces termes :
- chat → document 1
- mange → document 1
- souris → document 1
C’est le principe de l’index inversé : plutôt que de parcourir tous les documents pour savoir lesquels contiennent un terme, le moteur dispose directement d’une structure reliant les termes aux documents qui les contiennent.
Pourquoi les segments sont-ils immuables ?
Un point fondamental à comprendre est qu’un segment est immuable. Une fois créé, il n’est pas modifié.
Lorsqu’un nouveau document arrive, il est d’abord écrit dans une structure en mémoire. Lorsqu’Elasticsearch effectue un refresh, les données deviennent ensuite consultables et un nouveau segment peut être créé.
Reprenons notre exemple. Si nous ajoutons : « Quand le chat n’est pas là, les souris dansent »
Un nouveau segment peut contenir les termes indexés de ce document :
- chat → document 2
- dansent → document 2
- est → document 2
- pas → document 2
- quand → document 2
- souris → document 2
Nous avons alors deux segments distincts.
Segment 1
- chat → document 1
- mange → document 1
- souris → document 1
Segment 2
- chat → document 2
- dansent → document 2
- est → document 2
- pas → document 2
- quand → document 2
- souris → document 2
Mais avoir de nombreux petits segments n’est évidemment pas une situation idéale sur le long terme.
C’est là qu’intervient le merge.
Le merge : quand Lucene fusionne les segments
Lucene possède une politique de merge qui détermine lorsqu’il est pertinent de fusionner plusieurs segments.
Dans notre exemple, les deux segments peuvent être fusionnés pour former un nouveau segment :
Segment 3
- chat → document 1, document 2
- dansent → document 2
- est → document 2
- mange → document 1
- pas → document 2
- quand → document 2
- souris → document 1, document 2
Les anciens segments sont alors supprimés et remplacés par le nouveau.
Ce mécanisme est particulièrement important pour comprendre les performances d’Elasticsearch : l’indexation ne consiste pas simplement à écrire des documents dans une structure unique qui serait ensuite modifiée en permanence.
Le moteur travaille avec des structures immuables qui évoluent au fil des indexations et des merges.
Que se passe-t-il lorsqu’un document est supprimé ?
Une question vient naturellement : Si les segments sont immuables, comment Elasticsearch peut-il supprimer un document ?
Le document n’est pas immédiatement retiré physiquement du segment.
Lucene utilise notamment une structure appelée live docs bitmap pour indiquer quels documents sont encore considérés comme actifs.
Lorsqu’un document est supprimé, il est donc marqué comme tel dans cette structure. Il peut continuer à exister physiquement dans le segment jusqu’à ce qu’un merge intervienne.
Lors d’une recherche, Lucene peut retrouver le document dans l’index puis vérifier s’il est toujours considéré comme actif.
Le document sera réellement supprimé des segments lors d’un merge. Le principe est également applicable aux mises à jour.
Pourquoi c’est intéressant pour l’optimisation ?
Parce que les écritures, les suppressions, les mises à jour et les merges participent tous à la façon dont évolue l’index. Comprendre cette mécanique est indispensable avant de chercher à optimiser les performances.
Index inversé et Doc Values : deux façons de représenter les données
L’index inversé est particulièrement efficace pour retrouver des documents à partir de termes. Mais il n’est pas adapté à tous les usages.
Prenons un autre besoin : trier les résultats par prix ou effectuer une agrégation sur une catégorie.
Il ne suffit plus de savoir quels documents contiennent un terme. Il faut pouvoir accéder efficacement aux valeurs associées aux documents. C’est notamment le rôle des Doc Values.
Les Doc Values utilisent une représentation différente de l’index inversé et sont optimisés pour les opérations telles que :
- les tris ;
- les agrégations ;
- certaines comparaisons de valeurs.
Pour les chaînes de caractères, les Doc Values s’appuient notamment sur des types tels que keyword et wildcard, contrairement au type text, qui est conçu pour l’analyse et la recherche plein texte.
Les ordinals : travailler avec des valeurs plus efficacement
Pour faciliter certaines opérations, Elasticsearch/Lucene peut représenter les termes sous forme d’entiers appelés ordinals.
L’idée est simple : plutôt que de manipuler constamment des chaînes de caractères, certaines opérations peuvent travailler sur des identifiants numériques associés aux valeurs.
Chaque segment possède ses propres Doc Values et ses propres ordinals locaux.
Lorsqu’une agrégation est exécutée, des Global Ordinals peuvent être construits afin d’être réutilisés par les requêtes suivantes.
Cette construction représente donc un coût initial, notamment après une modification de la structure des segments.
La première agrégation peut ainsi être plus longue que les suivantes, le temps que les Global Ordinals soient disponibles. Encore une fois, ce détail peut sembler très théorique.
Pourtant, lorsqu’on travaille avec de gros volumes et de nombreuses agrégations, comprendre ce type de mécanisme devient essentiel pour diagnostiquer un problème de performance.
Que se passe-t-il lorsqu’on lance une recherche Elasticsearch ?
Maintenant que nous avons compris comment les données sont indexées, intéressons-nous au chemin inverse : que se passe-t-il lorsqu’un utilisateur lance une recherche ?
Prenons une requête envoyée à Elasticsearch.
1. La requête arrive sur un nœud
- La requête est reçue par un nœud Elasticsearch.
- Celui-ci devient le nœud coordinateur pour cette requête.
- Son rôle consiste notamment à distribuer le travail aux shards concernés.
2. Les shards concernés reçoivent la requête
- Le coordinateur identifie les shards de l’index concernés et leur transmet la requête.
- Les différents shards peuvent alors travailler en parallèle.
- C’est à ce moment que Lucene entre pleinement en jeu.
- L’analyse de la requête repose notamment sur les mêmes principes d’analyse que ceux utilisés lors de l’indexation.
3. Recherche dans le dictionnaire de termes
- Lucene recherche les termes dans son index inversé.
- Une structure appelée FST — Finite State Transducer participe à cette recherche dans le dictionnaire de termes.
4. Intersection et union des Posting Lists
- Pour chaque terme, Lucene dispose de listes de documents associés, appelées posting lists.
- En fonction de la requête, ces listes peuvent être combinées.
Par exemple :
- must et les opérateurs AND nécessitent une intersection ;
- should et les opérateurs OR peuvent nécessiter une union.
Le moteur peut ainsi déterminer les documents correspondant aux différents critères de recherche.
5. Calcul du score
- Les documents correspondants doivent ensuite être évalués afin de déterminer leur pertinence.
- Le mécanisme de scoring par défaut mentionné dans notre cas est BM25.
Ce score permet notamment de classer les résultats en fonction de leur pertinence par rapport à la requête.
6. Lecture des Doc Values
Si la requête demande un tri ou une agrégation, les Doc Values peuvent être utilisées pour accéder efficacement aux valeurs nécessaires.
C’est une distinction fondamentale : l’index inversé permet notamment de retrouver les documents ; les Doc Values sont particulièrement adaptées aux opérations sur les valeurs de ces documents.
7. Les shards renvoient leurs résultats
- Chaque shard exécute son traitement sur ses propres segments.
- Les résultats sont ensuite retournés au nœud coordinateur.
- Celui-ci doit fusionner les résultats provenant des différents shards afin de construire un résultat global.
8. Elasticsearch récupère le _source
La recherche n’a pas nécessairement besoin de charger immédiatement l’intégralité du document.
Une fois les résultats déterminés, Elasticsearch peut récupérer le _source correspondant aux documents à retourner.
C’est notamment pour cette raison qu’un champ qui n’est pas indexé ne peut pas être utilisé comme s’il faisait partie des structures de recherche : pour l’algorithme de recherche, cette donnée n’est tout simplement pas disponible dans l’index de recherche.
Le coordinateur construit alors la réponse finale et la renvoie sous forme de JSON.
Pourquoi les wildcards peuvent-elles être coûteuses ?
Les wildcards constituent un bon exemple de fonctionnalité qui semble anodine mais dont le coût dépend fortement de la requête.
Prenons deux recherches : abc* et *abc
Elles ne représentent pas la même difficulté pour Lucene.
Lors de l’indexation, Lucene organise son dictionnaire de termes et utilise notamment un FST afin de faciliter certaines recherches en exploitant les préfixes communs.
Une recherche commençant par un préfixe connu, comme : abc* peut donc bénéficier de cette organisation.
Lucene sait qu’il doit chercher du côté des termes commençant par abc.
À l’inverse, avec : *abc le moteur ne connaît pas le préfixe.
Il doit potentiellement parcourir beaucoup plus largement le dictionnaire pour identifier les termes qui se terminent par abc.
Le coût peut donc être nettement plus important.
Bon réflexe : une wildcard placée au début d’une recherche n’a pas le même coût qu’une wildcard utilisée en suffixe. Avant d’utiliser systématiquement *motif*, il faut se demander quel besoin fonctionnel justifie cette recherche et comment le champ est indexé.
Et lorsqu’une wildcard est réellement nécessaire ?
Il existe évidemment des cas où la recherche sur une partie quelconque d’une chaîne est parfaitement légitime.
C’est notamment le cas pour certaines recherches dans :
- des URLs ;
- des adresses e-mail ;
- des identifiants ;
- des chaînes techniques.
Dans ce type de situation, utiliser simplement un champ keyword avec une wildcard n’est pas forcément la meilleure solution.
Elasticsearch propose notamment un type wildcard, conçu pour ce type de recherche et disposant de sa propre stratégie d’indexation.
Le choix du mapping n’est donc pas secondaire. La façon dont une donnée est indexée conditionne les requêtes que l’on pourra exécuter efficacement dessus.
Ce qu’il faut retenir avant de parler d’optimisation
À ce stade, quelques principes permettent déjà de mieux comprendre Elasticsearch.
1. Elasticsearch n’est pas simplement une base de documents
Les données sont organisées dans des index, réparties sur des shards et indexées par Lucene sous forme de segments.
2. Le mapping a un impact direct sur les usages
Un champ text, un champ keyword et un champ wildcard ne répondent pas aux mêmes besoins.
Le choix du type doit donc être guidé par les recherches qui seront réellement effectuées.
3. Les segments sont au cœur du fonctionnement de Lucene
Les segments sont immuables et sont régulièrement fusionnés.
Les indexations, suppressions et mises à jour participent donc à l’évolution de cette structure.
4. Recherche et agrégation ne reposent pas exactement sur les mêmes structures
L’index inversé est central pour la recherche plein texte.
Les Doc Values sont notamment conçues pour les tris et les agrégations.
5. Une requête Elasticsearch implique plusieurs étapes
La requête peut être distribuée sur plusieurs shards, traitée par Lucene, scorée, puis les résultats sont fusionnés par le nœud coordinateur avant de récupérer les documents à retourner.
6. Une requête qui paraît simple peut avoir un coût important
Les wildcards en sont un bon exemple. abc* et *abc ne présentent pas les mêmes caractéristiques en matière de recherche.
Comprendre avant d’optimiser
Lorsqu’une application Elasticsearch commence à ralentir, le réflexe peut être de chercher immédiatement une configuration magique : modifier le nombre de shards, augmenter la mémoire, changer une requête ou ajouter un cache.
Mais sans comprendre ce que fait réellement Elasticsearch, il est difficile de savoir si la modification répond au problème ou ne fait que le déplacer.
Sur un petit volume, beaucoup de choix peuvent sembler fonctionner.
Sur un catalogue de plusieurs millions de références, en revanche, le mapping, le nombre de shards, les segments, les merges, les types de champs et la construction des requêtes peuvent avoir des conséquences bien plus importantes.
C’est précisément pour cette raison qu’il est utile de commencer par les fondamentaux avant de parler d’optimisation.
Dans le prochain article : comment optimiser Elasticsearch ?
Comprendre le fonctionnement interne d’Elasticsearch est une première étape. La suivante consiste à appliquer ces connaissances à des problématiques concrètes de performance.
Dans « Comprendre Elasticsearch #2 – Comment mieux l’optimiser ? », nous irons donc plus loin : choix du mapping, gestion des shards, requêtes, wildcards, agrégations et bonnes pratiques permettant d’améliorer les performances d’un moteur Elasticsearch lorsque les volumes deviennent importants.
Vous avez un moteur Elasticsearch qui montre des signes de ralentissement ou un catalogue volumineux à faire évoluer ? Notre équipe peut vous accompagner dans l’analyse de votre architecture, l’identification des points de blocage et la mise en place de pistes d’optimisation adaptées à vos usages.