Aller au contenu principal

Module 2 — Concepts clés d'Elasticsearch : cluster, nœud, index, shard, document

Le kit tourne, Kibana répond. Avant d'indexer un seul article, Inès veut que Sami sache lire un cluster : combien de nœuds, combien d'index, combien de shards, quelle couleur, pourquoi. Ce module donne le vocabulaire minimal pour que la moitié des messages d'erreur du reste du cours devienne évidente.

Le vocabulaire, dans l'ordre où on le rencontre

Elasticsearch expose ses concepts par emboîtements successifs. On les prend du haut vers le bas.

Cluster

Un cluster est un groupe d'un ou plusieurs serveurs qui se déclarent membres d'un même ensemble. Le nôtre s'appelle veille — vous le lisez dans docker-compose.yml sous cluster.name=veille. Un cluster a un nom, une santé (green, yellow ou red), une version, et il possède collectivement toutes les données.

Nœud

Un nœud est un processus Elasticsearch qui tourne sur un serveur et qui a rejoint un cluster. Le kit démarre un unique nœud, veille-es, en mode discovery.type=single-node. En production, un cluster de trois nœuds ou plus est la norme, mais un nœud unique suffit largement à explorer les concepts et à traiter les 200 853 articles du corpus.

Index

Un index est le regroupement logique des documents d'un même type — nos articles News forment l'index news. Un index expose deux blocs de configuration : les settings (nombre de shards, nombre de répliques, analyseurs) et le mapping (les champs, leur type, leurs options). Un index vit ensuite dans un ou plusieurs shards physiques.

Shard, réplique

Un shard est une partition Lucene d'un index. Les documents d'un index sont répartis entre ses shards par hachage de l'_id. Chaque shard est un moteur Lucene complet, indépendant, capable d'accueillir de nombreux documents et de servir des requêtes.

Une réplique est une copie d'un shard primaire, placée sur un autre nœud. Elle sert à deux choses : encaisser la perte d'un nœud (haute disponibilité) et absorber du trafic de lecture. Zéro réplique n'a de sens qu'en atelier — un seul nœud ne peut de toute façon pas héberger une réplique de lui-même. C'est le cas du kit.

Document

Un document est un objet JSON stocké dans un index. Il a un identifiant _id (fourni par vous ou généré), un _source (le JSON tel que vous l'avez envoyé) et des métadonnées de version (_seq_no, _primary_term). Un article de News est un document.

Correspondance mentale avec le relationnel

Le tableau suivant aide, à condition de ne pas s'y accrocher : Elasticsearch n'est pas une base relationnelle et ces équivalences sont approximatives.

ElasticsearchRelationnel (approximatif)
ClusterServeur SGBD
NœudInstance
IndexTable
ShardPartition d'une table
DocumentLigne
ChampColonne
_idClé primaire
MappingSchéma (CREATE TABLE)
Pas de jointures

Il n'y a pas d'équivalent Elasticsearch de JOIN. Le champ nested (aperçu au module 4) permet des objets imbriqués, mais on ne relie pas deux index par une clé étrangère. C'est un choix : chaque shard doit pouvoir répondre seul pour rester rapide. Si vous avez besoin de relations, c'est Neo4j (modules 10 à 12).

Regarder le cluster

Ouvrez Kibana Dev Tools (Management → Dev Tools). Toutes les requêtes ci-dessous se collent directement dans la console de gauche ; le raccourci Ctrl-Entrée (Cmd-Entrée sur macOS) les exécute.

GET / — un nœud se présente

GET /

Vous obtenez le nom du nœud (veille-es), le nom du cluster (veille), la version d'Elasticsearch (9.5.3), la version Lucene et le UUID du cluster. C'est la version la plus courte de « je suis vivant, je réponds, voilà qui je suis ».

GET /_cluster/health — le pouls

GET /_cluster/health

Sortie typique sur notre kit :

{
"cluster_name": "veille",
"status": "green",
"number_of_nodes": 1,
"number_of_data_nodes": 1,
"active_primary_shards": 1,
"active_shards": 1,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 0
}

Le champ status prend trois valeurs :

  • green : tous les shards primaires et toutes leurs répliques sont assignés.
  • yellow : tous les primaires sont assignés, mais au moins une réplique manque.
  • red : au moins un shard primaire n'est pas assigné — des documents sont inaccessibles.

Le kit est green parce que l'index news est créé avec number_of_replicas: 0 : il n'y a aucune réplique à placer, donc rien de manquant. Beaucoup de tutoriels démarrent avec le paramètre par défaut d'une réplique et affichent yellow — Sami se demande alors ce qu'il a cassé. Réponse : rien, l'unique nœud ne peut simplement pas héberger la copie de son propre shard primaire.

GET /_cat/nodes?v — qui compose le cluster

./lab.sh es _cat/nodes?v

Ou dans Dev Tools :

GET /_cat/nodes?v

Vous voyez un seul nœud, veille-es, son adresse IP interne, le pourcentage de mémoire et de heap consommés, sa charge et son rôle (cdfhilmrstw : le nœud fait tout à la fois — c'est normal en single-node).

GET /_cat/indices?v — quels index existent

GET /_cat/indices?v

Avant l'import, la sortie ne montre que les index système de Kibana (.kibana_*, .security-*) préfixés par un point. Après ./lab.sh import-news, une ligne apparaît :

health status index    uuid       pri rep docs.count docs.deleted store.size pri.store.size
green open news ... 1 0 200853 0 ... ...

Lecture : un shard primaire (pri: 1), zéro réplique (rep: 0), 200 853 documents, aucun supprimé, un poids d'environ cent quarante méga-octets.

GET /_cat/shards?v — où vivent les shards

GET /_cat/shards/news?v
index shard prirep state   docs   store ip         node
news 0 p STARTED 200853 ... 172.20.0.3 veille-es

Un seul shard primaire (p), démarré, hébergeant les 200 853 documents. prirep = r pour une réplique.

Créer, décrire, supprimer un index à la main

Avant d'indexer News avec le kit, faisons naître et disparaître un petit index de démonstration : Léa veut suivre ses recherches manuelles dans un index séparé.

Créer avec settings et mapping

PUT recherches_lea
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0
},
"mappings": {
"properties": {
"sujet": { "type": "keyword" },
"requete": { "type": "text" },
"date": { "type": "date", "format": "yyyy-MM-dd" },
"resultats": { "type": "integer" }
}
}
}

Réponse attendue :

{ "acknowledged": true, "shards_acknowledged": true, "index": "recherches_lea" }

Notez ce que le module 4 démontera : sujet est en keyword (valeurs comparées exactement, utilisables en agrégation), requete en text (analysé pour la recherche), date avec un format explicite pour éviter que le mapping dynamique n'aille deviner.

API 9.x, sans _doc dans l'URL

Depuis Elasticsearch 7, les types nommés ont disparu. On écrit PUT /recherches_lea avec le mapping à la racine ; les vieux tutoriels avec PUT /recherches_lea/_doc/_mapping ou include_type_name=true sont dépréciés et rejetés en 9.x.

Décrire

GET recherches_lea

Renvoie les settings (avec des valeurs par défaut ajoutées par Elasticsearch : creation_date, uuid, version), le mapping tel qu'enregistré, et les aliases (aucun ici). Pour n'obtenir que la partie mapping :

GET recherches_lea/_mapping

Ajuster un setting réplicable

Certains settings sont dynamiques (modifiables à chaud) : number_of_replicas, refresh_interval. D'autres sont statiques (fixés à la création) : number_of_shards. Passer d'un shard à deux shards impose une réindexation.

PUT recherches_lea/_settings
{ "index": { "refresh_interval": "5s" } }

Le module 3 exploitera ce levier pendant l'import : on met refresh_interval à 30s le temps de charger 200 853 documents, puis on le remet à 1s. C'est un doublement du débit d'indexation à l'œil nu.

Supprimer

DELETE recherches_lea

Réponse {"acknowledged": true}. Le shard est démonté, ses fichiers Lucene effacés, l'index disparaît de _cat/indices.

Suppressions

DELETE est irréversible côté cluster : pas de corbeille, pas de rollback. Sur un cluster de production, il est prudent d'activer action.destructive_requires_name=true pour interdire DELETE _all ou DELETE *. Le kit reste permissif pour ne pas gêner l'apprentissage.

Ce que change un shard, ce que change une réplique

Deux paramètres, deux effets à ne pas confondre.

  • Plus de shards primaires = un même index peut être réparti sur plus de nœuds, chaque shard reçoit moins de documents, l'indexation et la recherche parallèlisent plus. Le coût : chaque shard consomme de la mémoire (buffers, structures Lucene) et coordonne ses requêtes. La règle empirique publiée par Elastic est de garder chaque shard entre dix et cinquante giga-octets, et de ne pas dépasser une vingtaine de shards par giga-octet de heap.
  • Plus de répliques = plus de résilience et plus de capacité de lecture, aucun bénéfice pour l'indexation (au contraire : chaque écriture est répliquée). Une réplique n'a de sens que sur un autre nœud que le primaire ; deux répliques n'ont d'intérêt qu'à partir de trois nœuds.

Sur les 200 853 documents de News, un unique shard tient largement (le stockage total est d'environ cent quarante méga-octets). En production sur des milliards de documents, on découpe en dizaines de shards répartis sur plusieurs data nodes.

Heap Java et disque, en une page

Elasticsearch tourne sur la JVM. Deux tensions le déterminent :

  • Le heap Java (-Xms1g -Xmx1g dans notre docker-compose.yml) : la moitié de la RAM du conteneur, pas plus de trente et un giga-octets en production (au-delà, la JVM change de mode de pointeurs et perd de l'efficacité). Vous lisez le heap consommé dans GET /_cat/nodes?v&h=name,heap.percent.
  • Le disque : Elasticsearch surveille l'espace libre et, par défaut, passe automatiquement les index en lecture seule quand un seuil est atteint (low, high, flood_stage). Le kit désactive ce comportement (cluster.routing.allocation.disk.threshold_enabled=false) pour ne pas gêner l'atelier. En production, on garde ce mécanisme et on ajoute de l'espace.

Une commande utile pour surveiller les deux d'un coup :

GET /_cat/nodes?v&h=name,heap.percent,ram.percent,disk.used_percent

À vous

Exercice 1 — Lire un cluster

Dans Kibana Dev Tools, exécutez GET /_cluster/health. Notez status, number_of_nodes, active_primary_shards, active_shards, unassigned_shards. Puis exécutez GET /_cat/indices?v. Combien d'index sont présents ? Combien ne sont pas des index système (préfixés par un point) ?

Solution

Statut green, un nœud, un ou plusieurs shards primaires assignés selon les index système déjà créés par Kibana (.kibana_*, .security-*, .apm-* — la liste évolue). Zéro shard non assigné. Avant l'import, aucun index utilisateur : seulement les index système. Après ./lab.sh import-news, un index utilisateur s'ajoute : news.

Exercice 2 — Créer un mini-index, le supprimer

Créez un index notes_veille avec un seul shard, zéro réplique, deux champs (titre en text, tag en keyword). Vérifiez son apparition dans _cat/indices, décrivez-le, puis supprimez-le.

Solution
PUT notes_veille
{
"settings": { "number_of_shards": 1, "number_of_replicas": 0 },
"mappings": {
"properties": {
"titre": { "type": "text" },
"tag": { "type": "keyword" }
}
}
}
GET _cat/indices/notes_veille?v
GET notes_veille
DELETE notes_veille

Vérifiez que le second GET _cat/indices/notes_veille?v renvoie l'erreur index_not_found_exception : la suppression est effective.

Exercice 3 — Simuler un yellow, revenir à green

Créez un index avec une réplique et observez ce qu'il devient sur notre cluster à un nœud. Puis remettez zéro réplique et revérifiez.

PUT test_yellow
{ "settings": { "number_of_shards": 1, "number_of_replicas": 1 } }
GET _cluster/health/test_yellow

Que dit status ? Modifiez le nombre de répliques à chaud :

PUT test_yellow/_settings
{ "index": { "number_of_replicas": 0 } }

Revérifiez la santé, puis supprimez l'index.

Solution

Le premier GET /_cluster/health/test_yellow renvoie "status": "yellow" avec unassigned_shards: 1 : la réplique demandée n'a nulle part où aller (un seul nœud). Après avoir passé number_of_replicas à zéro, la santé de cet index revient à green immédiatement — Elasticsearch libère l'attente d'assignation.

DELETE test_yellow

Retenez la logique : yellow sur un cluster à un nœud n'est pas une panne, c'est un choix de configuration. Sur un cluster de trois nœuds ou plus, un yellow persistant mérite un vrai diagnostic (GET _cluster/allocation/explain).

Points à retenir

  • Cluster → nœud → index → shard → document : cinq niveaux, dans cet ordre.
  • Un index Elasticsearch a des settings (shards, répliques, refresh) et un mapping (champs, types).
  • green = tout est assigné ; yellow = un shard primaire est là, une réplique manque ; red = un primaire manque.
  • Le kit est green parce que l'index news est configuré avec zéro réplique — logique sur un cluster à un nœud.
  • Un shard est un moteur Lucene autonome ; on en règle le nombre à la création, on peut modifier le nombre de répliques à chaud.
  • _cat/indices, _cat/nodes, _cat/shards sont les trois commandes de diagnostic les plus utiles : mémorisez-les.
  • En 9.x, on ne met plus _doc dans l'URL de mapping, pas de include_type_name, pas de type string : ces syntaxes sont rejetées.

Si ça ne marche pas

  • GET /_cluster/health renvoie 401 dans Dev Tools → l'utilisateur elastic n'est plus reconnu ; le mot de passe de .env a été changé après le premier up. ./lab.sh reset puis ./lab.sh up.
  • GET /_cat/indices?v reste vide ou timeout → Elasticsearch n'a pas fini de démarrer ; regardez ./lab.sh logs elasticsearch et attendez [YELLOW] to [GREEN] sur les index système.
  • illegal_argument_exception, mapper_parsing_exception à la création d'un index → une clé du mapping est mal orthographiée (typo sur properties) ou un type invalide (string n'existe plus en 9.x, remplacez par text ou keyword).
  • Status devient red après import-news → au moins un shard primaire n'a pas pu s'initialiser ; ./lab.sh logs elasticsearch cherchera un message disk usage exceeded flood-stage watermark ou translog corruption, puis ./lab.sh reset.

Pour aller plus loin