• 15 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 04/06/2021

Faites grandir votre base

Comme on a pu le voir dans les chapitres précédents, ElasticSearch est vraiment adapté à la recherche d'information dans des documents. La question qui vient juste après, c'est celle du passage à l'échelle :

  1. Comment indexer des quantités de documents arbitrairement grandes ?

  2. Comment répliquer les données pour assurer la résistance aux pannes ?

Ce sont les deux questions qu'on pose au sujet de n'importe quelle solution NoSQL, et chacune y répond de manière différente. Dans le cas d'ElasticSearch, le passage à l'échelle se fait de manière horizontale et en assurant la disponibilité des données, au détriment de la cohérence. Ce dernier point implique qu'un cluster ElasticSearch répondra toujours aux requêtes des utilisateurs avec un délai maximal garanti, et ce même en cas de partition du réseau. Par contre, il n'est pas garanti que la réponse contienne la version la plus récente des données. Il s'agit donc d'une différence importante, par exemple par rapport à MongoDb : en effet, MongoDb assure la cohérence mais pas la disponibilité des données. Pour ElasticSearch, ce choix technique est une conséquence des besoins liés à un moteur de recherche : on s'attend à ce qu'un moteur de recherche réponde avec une latence maximum garantie, mais pas forcément que les résultats soient mis à jour très rapidement.

Modèle de sharding

Voyons un peu comment les données sont distribuées sur un cluster ElasticSearch. Le modèle employé est celui du sharding : comme on l'a vu plus tôt, cela consiste à distribuer les données par bloc (shard) entre les différents nœuds du cluster. Un index est donc décomposé en shards, qui contiennent eux-mêmes les documents :

  • Un index contient un ou plusieurs shards (5 par défaut).

  • À chaque shard correspond un shard primaire et zéro ou plus replica shards (1 par défaut), tous stockés sur des nœuds différents.

  • Chaque shard primaire contient les mêmes documents que ses replica shards.

Exemple de répartition des shards d'un index sur un cluster ElasticSearch composé de 3 nœuds
Exemple de répartition des shards d'un index sur un cluster ElasticSearch composé de 3 nœuds

Alors quel est l'intérêt de répartir les données sur plusieurs shards ? Comme on l'a vu dans la première partie, cela permet de répartir la charge sur plusieurs serveurs. Par exemple, considérons qu'on dispose d'un cluster de cinq nœuds sur lequel sont répartis les cinq shards primaires d'un index contenant des pages web. Si l'on cherche toutes les pages web contenant un certain mot clé, chacun des nœud du cluster ne va avoir à chercher que parmi un cinquième des données. Le temps de réponse devrait donc être divisé par cinq par rapport à un cluster composé d'un seul nœud. Les replica shards sont utilisés exactement de la même manière que les shards primaires : ils contribuent également à la répartition de la charge sur un cluster.

Elasticité

Attention ! À première vue, on pourrait s'attendre à ce que le temps de calcul continue de baisser en ajoutant des nœuds au cluster, mais cela ne va pas être vrai. Si le nombre de nœuds dépasse le nombre de shards primaires, le temps nécessaire pour réaliser une requête ne va pas baisser. Par contre, en ajoutant des replicas sur ces nœuds supplémentaires, le cluster répond à plus de requêtes en même temps sans affecter le délai de réponse. La taille optimale d'un cluster va donc dépendre du scénario envisagé :

Pas de requêtes concurrentes (1 seul utilisateur) :

$\(#nœuds = #shards_primaires\)$

Multiples requêtes concurrentes (nombreux utilisateurs) :

$\(#nœuds = #shards_primaires + #replicas\)$

SHAAAAAAAARDEZ!

Mais trêve de bavardages ! Passons à l'action et faisons grossir notre cluster. Vous allez voir, c'est presque décevant tellement c'est simple. On va juste lancer un nouveau nœud, de la même manière qu'on a fait jusqu'ici. Il y a une unique subtilité si on lance le second nœud sur la même machine : il faut permettre à ElasticSearch de stocker les données de plusieurs nœuds dans le même répertoire. Pour cela, on ajoute la ligne suivante au fichier de configurationconfig/elasticsearch.yml:

node.max_local_storage_nodes: 2

Évidemment, remplacez "2" par le nombre de nœuds que vous comptez lancer sur votre machine. Vous pouvez alors lancer un nœud supplémentaire avec la commande habituelle :

$ ./bin/elasticsearch

Et voilà le travail ! Vous avez désormais deux nœuds qui cohabitent sur la même machine.

Administration du cluster

Maintenant qu'on a un cluster fonctionnel, voyons quelques commandes permettant de visualiser l'état du cluster, et éventuellement de diagnostiquer des problème.

cluster/_health

On commence par une commande dont vous allez vous servir très souvent. La commandecluster/_healthpermet de visualiser l'état de santé du cluster. Par exemple, dans notre cluster composé de deux nœuds et contenant cinq shards répliqués une fois, on obtient :

$ curl localhost:9200/_cluster/health?pretty
{
  "cluster_name" : "elasticsearch",
  "status" : "green",
  "timed_out" : false,
  "number_of_nodes" : 2,
  "number_of_data_nodes" : 2,
  "active_primary_shards" : 5,
  "active_shards" : 10,
  "relocating_shards" : 0,
  "initializing_shards" : 0,
  "unassigned_shards" : 0,
  "delayed_unassigned_shards" : 0,
  "number_of_pending_tasks" : 0,
  "number_of_in_flight_fetch" : 0,
  "task_max_waiting_in_queue_millis" : 0,
  "active_shards_percent_as_number" : 100.0
}

Ici, l'état du cluster est vert. Cela signifie que tous les shards primaires et les replicas sont disponibles. Si certains replicas ne sont pas disponibles, le cluster va passer en état jaune. Par exemple, vous pouvez arrêter le second nœud que vous avez lancé et exécuter la commande à nouveau :

$ curl localhost:9200/_cluster/health?pretty
{
  "cluster_name" : "elasticsearch",
  "status" : "yellow",
  "timed_out" : false,
  "number_of_nodes" : 1,
  "number_of_data_nodes" : 1,
  "active_primary_shards" : 5,
  "active_shards" : 5,
  "relocating_shards" : 0,
  "initializing_shards" : 0,
  "unassigned_shards" : 5,
  "delayed_unassigned_shards" : 5,
  "number_of_pending_tasks" : 0,
  "number_of_in_flight_fetch" : 0,
  "task_max_waiting_in_queue_millis" : 0,
  "active_shards_percent_as_number" : 50.0
}

Inversement, si vous rallumez le nœud que vous venez d'éteindre, le cluster va brièvement rester dans un état jaune, le temps que les shards soient ré-alloués. Vous aurez alors un nombre derelocating_shardsnon nul :

$ curl localhost:9200/_cluster/health?pretty
{
  "cluster_name" : "elasticsearch",
  "status" : "yellow",
  ...
  "relocating_shards" : 2,
  ...
}

Un état jaune n'est pas problématique : il signale que le cluster est en bon état de marche, mais que certains shards ne sont pas alloués comme prévu. Par contre, lorsque certains shards (primaire et replicas) deviennent complètement indisponible, le cluster passe en état rouge. Il est alors dans un état problématique qu'il est urgent de résoudre.

_cat/shards

Si votre cluster est dans un état jaune ou rouge, votre premier réflexe devra être de voir quels shards sont indisponibles. Pour cela, vous utiliserez la commande_cat/shardsqui permet d'afficher l'état de tous les shards. Par exemple, sur notre indexmovies:

$ curl localhost:9200/_cat/shards
movies                        4 p STARTED 1020   1.5mb 127.0.0.1 7PdQQLi
movies                        4 r STARTED 1020   1.5mb 127.0.0.1 ibuo3Se
movies                        3 p STARTED  966   1.4mb 127.0.0.1 7PdQQLi
movies                        3 r STARTED  966   1.4mb 127.0.0.1 ibuo3Se
movies                        1 p STARTED  944   1.4mb 127.0.0.1 7PdQQLi
movies                        1 r STARTED  944   1.4mb 127.0.0.1 ibuo3Se
movies                        2 p STARTED  981   1.5mb 127.0.0.1 7PdQQLi
movies                        2 r STARTED  981   1.5mb 127.0.0.1 ibuo3Se
movies                        0 p STARTED  942   1.4mb 127.0.0.1 7PdQQLi
movies                        0 r STARTED  942   1.4mb 127.0.0.1 ibuo3Se

Et lorsqu'un nœud est éteint, certains shards passent dans l'état "unassigned" :

$ curl localhost:9200/_cat/shards
movies                        4 p STARTED    1020   1.5mb 127.0.0.1 7PdQQLi
movies                        4 r UNASSIGNED                        
movies                        3 p STARTED     966   1.4mb 127.0.0.1 7PdQQLi
movies                        3 r UNASSIGNED                        
movies                        1 p STARTED     944   1.4mb 127.0.0.1 7PdQQLi
movies                        1 r UNASSIGNED                        
movies                        2 p STARTED     981   1.5mb 127.0.0.1 7PdQQLi
movies                        2 r UNASSIGNED                        
movies                        0 p STARTED     942   1.4mb 127.0.0.1 7PdQQLi
movies                        0 r UNASSIGNED

Les différentes colonnes sont, respectivement :

  • le nom de l'index

  • le numéro du shard, numéroté à partir de 0 : comme on peut le voir, l'indexmoviespossède bien cinq shards.

  • une lettre (pour) qui indique s'il s'agit d'un shard primaire ou d'un replica.

  • un statut (STARTEDouUNASSIGNED) faisant référence à la disponibilité du shard.

  • le nombre de documents dans le shard.

  • la taille du shard.

  • l'adresse IP du nœud contenant le shard.

  • le nom du nœud contenant le shard.

_nodes/process

Comme vous pouvez le voir, le nom des nœuds obtenu grâce à la commande précédente est une chaîne de caractères générée aléatoirement, ce qui est peu commode. Pour en apprendre plus sur les nœuds, exécutez :

$ curl localhost:9201/_nodes/process?pretty
{
  "_nodes" : {
    "total" : 2,
    "successful" : 2,
    "failed" : 0
  },
  "cluster_name" : "elasticsearch",
  "nodes" : {
    "ibuo3SebR8-DScQALMNKRA" : {
      "name" : "ibuo3Se",
      "transport_address" : "127.0.0.1:9301",
      "host" : "127.0.0.1",
      "ip" : "127.0.0.1",
      "version" : "5.5.2",
      "build_hash" : "b2f0c09",
      "roles" : [
        "master",
        "data",
        "ingest"
      ],
      "process" : {
        "refresh_interval_in_millis" : 1000,
        "id" : 7408,
        "mlockall" : false
      }
    },
    "7PdQQLilTnyMBLyWAT5IHQ" : {
      "name" : "7PdQQLi",
      "transport_address" : "127.0.0.1:9300",
      "host" : "127.0.0.1",
      "ip" : "127.0.0.1",
      "version" : "5.5.2",
      "build_hash" : "b2f0c09",
      "roles" : [
        "master",
        "data",
        "ingest"
      ],
      "process" : {
        "refresh_interval_in_millis" : 1000,
        "id" : 6730,
        "mlockall" : false
      }
    }
  }
}

Configuration du cluster

Mal nommer les choses, c'est ajouter au malheur du monde

En fait, le nom des nœuds est un paramètre qu'on peut modifier. Il s'agit d'un des nombreux paramètres qu'on retrouve dans le fichier de configurationconfig/elasticsearch.yml. Comme vous pouvez le voir en ouvrant ce fichier, la plupart des paramètres sont commentés :

# Use a descriptive name for the node:
#
#node.name: node-1

Décommentez cette ligne pour affecter explicitement un nom à votre nœud. Cela vous permettra d'avoir des nœuds qui gardent le même nom d'un redémarrage sur l'autre. Attention cependant : vous ne pourrez plus utiliser le même fichier de configuration pour plusieurs nœuds, puisque deux nœuds différents d'un même cluster doivent avoir des noms différents.

Il n'est pas indispensable de modifier le nom des nœuds ; par contre, le nom de votre cluster est assez important. Oui, car votre cluster a un nom ! Il est défini par le paramètrecluster.name. Par défaut, le nom du cluster estelasticsearch, comme vous pouvez le voir dans le résultat de la commande_cluster/health. Et ce nom de cluster est important pour que les nœuds se retrouvent entre eux...

Restons zen

Est-ce que vous vous êtes demandés comment les différents nœuds allumés sur le même réseau local se trouvent et se connectent entre eux pour former un cluster ? Pour effectuer cette découverte, ElasticSearch utilise le module "Zen discovery" : au démarrage, un nœud va chercher ses congénères parmi certaines adresses définies dans le paramètrediscovery.zen.ping.unicast.hosts. S'il y a bien un nœud ElasticSearch à l'adresse indiquée, et que ce nœud a le même nom, alors le nouveau nœud rejoint le cluster.

Évitez le split brain

Vous allez avoir de plus en plus de données à gérer (du moins on vous le souhaite). En conséquence, vous allez probablement ajouter des nœuds à votre cluster. Il y a un problème qu'on rencontre fréquemment quand on fait grossir un cluster : en cas de partition du réseau, on risque de se retrouver avec deux voire trois clusters au lieu d'un seul ! C'est le phénomène de "split brain". C'est une situation catastrophique puisqu'elle peut générer des corruptions de données. Pour l'éviter, définissez le paramètrediscovery.zen.minimum_master_nodes: un nœud ne pourra alors pas rejoindre un cluster s'il voit moins de nœuds que cette valeur. SiNest votre nombre total de nœuds dans le cluster, définissez ce paramètre àN/2 + 1(arrondi à l'inférieur) et vous serez assurés de ne pas avoir de partition de votre cluster.

Modifiez le nombre de shards

Jusqu'à présent, on a utilisé notre index avec le nombre par défaut de shards primaires (5) et de replicas (1). À un moment, vous allez probablement vouloir modifier ces nombres. Il est possible de modifier le nombre de replica shards. Pour cela, il faut modifier les paramètres de notre index. Par exemple, avec la commande suivante on assigne 2 replicas à chaque shard primaire :

$ curl -X PUT localhost:9201/movies/_settings?pretty -d '{
    "index": {
        "number_of_replicas" : 2
    }
}'

Notre cluster va donc passer à 15 shards et à un état jaune, puisqu'il ne contient que deux nœuds : tous les replicas ne pourront donc être assignés.

Quid des shards primaires ? Il se trouve qu'il n'est pas possible de modifier le nombre de shards primaires d'un index existant. Par contre il est possible de définir ce nombre lors de la création de l'index :

curl -X PUT localhost:9201/movies -d '{
    "settings": {
        "index": {
            "number_of_shards" : 6
        }
    }
}'
Exemple de certificat de réussite
Exemple de certificat de réussite