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.
| Elasticsearch | Relationnel (approximatif) |
|---|---|
| Cluster | Serveur SGBD |
| Nœud | Instance |
| Index | Table |
| Shard | Partition d'une table |
| Document | Ligne |
| Champ | Colonne |
_id | Clé primaire |
| Mapping | Schéma (CREATE TABLE) |
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.
_doc dans l'URLDepuis 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.
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 -Xmx1gdans notredocker-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é dansGET /_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
greenparce que l'indexnewsest 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/shardssont les trois commandes de diagnostic les plus utiles : mémorisez-les.- En 9.x, on ne met plus
_docdans l'URL de mapping, pas deinclude_type_name, pas de typestring: ces syntaxes sont rejetées.
Si ça ne marche pas
GET /_cluster/healthrenvoie 401 dans Dev Tools → l'utilisateurelasticn'est plus reconnu ; le mot de passe de.enva été changé après le premierup../lab.sh resetpuis./lab.sh up.GET /_cat/indices?vreste vide ou timeout → Elasticsearch n'a pas fini de démarrer ; regardez./lab.sh logs elasticsearchet 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 (typosurproperties) ou un type invalide (stringn'existe plus en 9.x, remplacez partextoukeyword).- Status devient
redaprèsimport-news→ au moins un shard primaire n'a pas pu s'initialiser ;./lab.sh logs elasticsearchcherchera un messagedisk usage exceeded flood-stage watermarkoutranslog corruption, puis./lab.sh reset.
Pour aller plus loin
- Documentation Elasticsearch 9 — Nodes, shards, and replicas
- Documentation Elasticsearch 9 — Cluster health API
- Documentation Elasticsearch 9 — cat APIs
- Documentation Elasticsearch 9 — Size your shards