Les essentiels de la JVM pour Elasticsearch : métriques, mémoire et monitoring

Elasticsearch est un moteur de recherche et d'analyse basé sur Java ainsi qu'une base vectorielle reposant sur Apache Lucene ; il constitue le noyau de la plateforme Elasticsearch. Pour exécuter Elasticsearch sur ses plateformes prises en charge, vous avez besoin d'une machine virtuelle Java (JVM). La JVM fournit un environnement d'exécution indépendant de la plateforme, et vous pouvez exécuter Elasticsearch dans un environnement virtuel au-dessus du système d'exploitation existant. La JVM abstrait le système d'exploitation et le matériel sous-jacents, de sorte que les applications Java peuvent être en exécution sur n'importe quelle Platform.

Comprendre la gestion de la mémoire de la JVM et la récupération des objets via le garbage collection (GC) est la clé pour résoudre des problèmes tels que java.lang.OutOfMemoryError (code de sortie 127). Ces problèmes surviennent lorsque l’exécution de la JVM manque de mémoire ou en cas de code de sortie 137, ce qui indique généralement que le processus OOM (Out-Of-Memory) killer de l'hôte a arrêté le processus JVM en raison d'une utilisation excessive de la mémoire. Ce blog vous guidera sur la façon d'examiner les modèles d'utilisation de la mémoire et de résoudre ces problèmes de JVM.

J'expliquerai également le rôle de la JVM et comment le corréler à l'aide des API Elasticsearch existantes, en fournissant de précieuses informations sur l'état de la JVM Elasticsearch et en vous aidant à décider si vous souhaitez l'optimiser davantage en fonction de votre propre cas d'utilisation. 

Comme indiqué dans la documentation JVM d'Elasticsearch, les valeurs par défaut des options JVM fournies avec le produit fonctionnent efficacement dans tous les environnements de production pris en charge ; ces valeurs par défaut peuvent gérer la plupart des cas d'utilisation pour les opérations de recherche et d'indexation. Il n'est pas recommandé de modifier les valeurs ou d'ajouter des valeurs personnalisées pour les options ou les paramètres JVM.

Si vous pensez que des modifications seraient bénéfiques pour votre charge de travail, veuillez contacter le support technique Elastic avant de procéder.

Qu’est-ce qu’une JVM ?

Une machine virtuelle Java fait partie intégrante de l'environnement d'exécution Java (JRE), qui est fourni avec un kit de développement Java (JDK).

Organigramme JVM

La JVM est similaire à un programme informatique qui aide à l'exécution des applications Java. Elle n'existe pas en tant que machine physique, mais elle se comporte comme telle en traduisant le code Java en instructions que votre système d'exploitation pris en charge peut comprendre. La JVM prend également en charge des tâches importantes telles que la gestion de la mémoire, le traitement de la récupération de mémoire et la sécurisation de vos applications.

Ensuite, concentrons-nous sur la gestion de la mémoire JVM et le garbage collection — un point majeur dans Elasticsearch que nous vérifions fréquemment lors du dépannage de tout problème de performance mémoire ou lié à une erreur OOM.

Pour comprendre la gestion de la mémoire et le garbage collection, vous devez d'abord comprendre la représentation logique de la zone de segment de mémoire (ou segment de mémoire) où résident tous les objets au démarrage d'une JVM.

Comprendre le segment de mémoire JVM : générations jeune et ancienne

Une zone de segment, ou segment de mémoire, est une combinaison de pools de mémoire où les objets résident en fonction de leur cycle de vie et de leur âge. Un segment de mémoire JVM se compose principalement d'une jeune génération et d'une ancienne génération. La jeune génération est en outre divisée en régions Eden et Survivor, comme illustré ci-dessous. Les objets Java sont alloués dans ces zones en fonction de leur âge et de leur référence, puis sont déplacés des régions jeunes vers les régions anciennes. 

La majeure partie de l'utilisation de la mémoire tas (heap) dans l'espace Eden est rapidement récupérée. Toute mémoire tas (heap) non libérée est déplacée vers l'un des espaces survivor (S0 ou S1), où elle est généralement récupérée selon un modèle de décroissance exponentielle. S'il persiste au-delà, il est déplacé vers l'espace Tenured (espace ancien).

Segment de mémoire JVM total = Génération jeune (régions Eden et Survivor S0 et S1) + Génération ancienne (Tenured)

Représentation logique de la zone de tas (sans se concentrer sur le récupérateur de mémoire utilisé)
Représentation logique de la zone de tas (sans se concentrer sur le récupérateur de mémoire utilisé)

Zone/région de jeune génération
Lorsqu’une application Java démarre, elle crée de nouveaux objets et les alloue à la zone appelée segment de mémoire. L’espace Eden de la jeune génération est le premier endroit où l’application alloue les objets nouvellement créés. Cette zone est réservée à l’allocation de nouveaux objets. Lorsqu’elle est pleine, les objets éligibles sont soit supprimés (garbage collected), soit promus vers d’autres régions.

Lorsqu'une récupération de mémoire mineure se produit, les objets référencés sont déplacés vers une autre sous-section, la région Survivor S0. Les objets non référencés sont supprimés pour libérer de l'espace dans Eden. Cette région contient les objets qui ont survécu au processus de récupération de mémoire, d'où le nom région Survivor

Le même cycle se répète lorsque l'espace Eden est à nouveau plein. Cette fois, les objets ayant survécu à Eden et à S0 seront déplacés vers la région S1, et ce processus se poursuit. 

Les objets ayant survécu aux récupérations de mémoire dans Eden, S0 et S1 seront promus vers l'ancienne génération en fonction du calculateur d'âge.

Zone/région de l'ancienne génération
L'ancienne génération (aussi appelée génération tenured) est une collection d'objets à longue durée de vie ayant survécu aux multiples événements de garbage collection dans la jeune génération (GC mineurs). Les objets ayant survécu à un certain nombre de cycles dans l'espace survivant sont déplacés par l'algorithme vers l'ancienne génération, ce qu'on appelle aussi promotion d'objet.

graphes de taux de GC et de segment JVM

Le modèle en dents de scie du GC ci-dessus, issu de Stack Monitoring, montre que lorsqu'une collecte de déchets mineure se produit, le segment de mémoire de la JVM diminue car le GC supprime les objets indésirables de la mémoire. Après quelques cycles, les objets s'accumulent et le GC (récupérateur de mémoire) se déclenche à nouveau pour les supprimer. 

Méthode de récupération de mémoire

Elasticsearch est transféré avec une version JDK fournie prise en charge, et les détails sont documentés dans les notes de publication. Jusqu'à la version 6.4, Elasticsearch utilisait le récupérateur de mémoire concurrent mark sweep (CMS), qui a été déprécié dans le JDK9. Elasticsearch utilise désormais le récupérateur de mémoire Garbage-First (G1) comme méthode GC par défaut. Il offre également de meilleures performances par rapport à la méthode CMS GC. Le mécanisme interne de G1GC est complexe et implique de nombreuses phases, mais décomposons-en les bases. 

Pour utiliser G1GC, utilisez l'indicateur JVM -XX:+UseG1GC dans l'option JVM d'Elasticsearch. Dans G1GC, la zone totale du tas sera divisée en régions de taille égale -XX:G1HeapRegionSize=1m qui peuvent varier de 1 Mo à 32 Mo selon la taille du tas. Les générations Eden, Survivor et old sont des ensembles logiques de ces régions et ne sont pas contiguës. En dehors de cela, il existe une région « humongous » et une région libre/disponible à l'intérieur de la zone du tas. La région « humongous » est utilisée pour allouer des objets dont la taille dépasse la moitié de la taille de la région.

Allocation du segment de mémoire (région) dans G1GC
Allocation du segment de mémoire (région) dans G1GC

Le G1GC a une cible de temps de pause (-XX:MaxGCPauseMillis=200) qu'il tente d'atteindre pendant la récupération de mémoire. Cela signifie que pendant la récupération de mémoire, il garantit que la JVM ne subit pas de pause supérieure à cette valeur, ce qui le rend meilleur que CMS ou d'autres récupérateurs où cette option n'existe pas. Dans l'ensemble, il est recommandé de conserver les paramètres de GC par défaut, ce qui maintiendra la pause GC dans les limites pour des performances améliorées. 

API Elasticsearch pour l'état de la JVM

Toute tâche qu'Elasticsearch souhaite effectuer devra demander à la JVM d'allouer de la mémoire afin de l'exécuter. Comme indiqué ci-dessus, la JVM gère la mémoire pour le compte d'Elasticsearch afin que ce dernier n'ait pas à s'en soucier. Cependant, les administrateurs peuvent souhaiter vérifier le statut de la JVM via les API Elasticsearch afin de s'assurer que tout progresse comme prévu.

Pour vérifier tous les paramètres qui ont été spécifiés ou fournis en tant qu'arguments d'entrée pour la JVM Elasticsearch, utilisez l'API node info avec l'attribut JVM (également décrit dans la documentation).

GET _nodes/_all/jvm 

Cette API fournira les détails de la configuration du Node, y compris les arguments d'entrée JVM, les pools de mémoire et la taille du segment de mémoire. 

De plus, si vous souhaitez vérifier les indicateurs de la JVM Elasticsearch pour la disponibilité d'un Node particulier, le tas utilisé dans tous les pools de mémoire ou les statistiques sur le garbage collection, vous pouvez utiliser l'API Node statistics. Elle fournira des indicateurs détaillés sur chacune des métriques mentionnées.

GET _nodes/stats/jvm
or
GET _nodes/stats/jvm?filter_path=nodes.*.jvm

Remarque : Elasticsearch ne recommande pas de prendre des décisions basées sur la valeur heap_percent obtenue à partir de l'API Node stats. Notre interface utilisateur calcule la pression mémoire, et l'utilisateur devrait prendre les mesures nécessaires en fonction du calcul décrit dans ce blog.

Vérification des métriques JVM à l'aide de l'outil JDK intégré

Si vous êtes un utilisateur avancé et que vous souhaitez vérifier les statistiques en temps réel d’une JVM en cours d’exécution sans utiliser les API Elasticsearch, vous pouvez utiliser la version Java fournie et exécuter jstat pour vérifier les statistiques en direct. Pour ce faire, recherchez d’abord le répertoire d’installation de Java ou le répertoire bin, ainsi que le PID du processus Elasticsearch. Depuis le répertoire bin, utilisez l’outil jstat :

./jstat -gcutil <PID> 2000

Ici, 2000 correspond à l’intervalle en millisecondes après lequel les statistiques seront récupérées et affichées sur la CLI. Cette commande est utile pour identifier la fréquence à laquelle le GC se produit dans toutes les zones et la vitesse à laquelle les pools de mémoire sont remplis par les objets (taux de création et de promotion des objets). 

Il ne s'agit pas d'une alternative aux API mentionnées ci-dessus, mais plutôt d'un outil destiné au débogage au niveau développeur.

Retour sur

Dans cet article, nous avons abordé les concepts de base des machines virtuelles Java, les différents pools de mémoire, l'importance du garbage collection et la manière de vérifier les statistiques JVM d'Elasticsearch. Dans le prochain article, nous aborderons les paramètres JVM importants et la manière d'interroger ses métriques actuelles dans Elasticsearch.  

Si vous avez des questions ou si vous souhaitez obtenir un support technique supplémentaire, n'hésitez pas à nous contacter. Nous sommes toujours là et très heureux de vous aider !

La publication et la date de publication de toute fonctionnalité ou fonction décrite dans le présent article restent à la seule discrétion d'Elastic. Toute fonctionnalité ou fonction qui n'est actuellement pas disponible peut ne pas être livrée à temps ou ne pas être livrée du tout.