Configuration de la JVM pour Solr et elasticsearch

Dans cet article, je décris la configuration de la JVM pour Solr et elasticsearch. Les paramètres conseillés sont les mêmes pour Solr et elasticsearch car bien que fournissant des fonctionnalités pas strictement identiques, ils s’appuient sur le même socle Lucene. Chaque cas d’usage de Solr et d’elasticsearch étant unique, ces paramètres ne constituent qu’une configuration par défaut satisfaisante dans la majorité des cas, mais qu’il peut être nécessaire d’adapter en fonction des besoins. Quoi qu’il en soit, un paramétrage de base doit être validé avant un passage en production en reproduisant le mieux possible l’utilisation qui sera faite du moteur (volumétrie de documents, volumétrie et stratégies de mises à jour, topologie des facettes, utilisation des filtres, volumétrie et types des requêtes, …).

Les paramètres en question portent sur l’allocation de la mémoire et le garbage collector. Comme exemple, on considère que le serveur est dédié au noeud Solr ou elasticsearch et qu’il est équipé de 8Go de RAM, soit 7,5 Go disponible.

Comme indiqué dans l’article « La bonne version de Java pour Solr », il faut utiliser une distribution Java OpenJDK ou Oracle en version 1.7u55 ou > ou idéalement une version 1.8 ou >.

Paramètres de la JVM liés à la mémoire

Xms et Xmx

Ces 2 paramètres contrôlent l’allocation initiale et maximum de la mémoire « Heap ».

Règle n°1 : allouer approximativement 40% de la mémoire disponible sur le serveur

Règle n°2 : positionner la mémoire initiale et la mémoire maximum à la même valeur afin d’éviter la fragmentation.

Règle n°3 : ne pas allouer plus de 31Go de mémoire Heap afin de permettre à la JVM d’optimiser les accès aux objets dans la Heap memory avec la technologies des Compressed Oops. A partir de 32Go de mémoire la technologie des Compressed Oops est désactivée. Voir cette expérience qui met en évidence le phénomène « Why 35GB Heap is Less Than 32GB – Java JVM Memory Oddities« .

Si votre serveur est équiper d’une très grande quantité de mémoire, il peut être judicieux d’y installer plusieurs noeuds qui tourneront chacun dans leur propre JVM.

Donc, pour 7,5Go de RAM disponible, nous allouons 3Go :

-Xms3g –Xmx3g

PermSize et MaxPermSize (Java 7 uniquement)

PermSize et MaxPermSize contrôlent l’allocation de la « permanent generation memory size ». C’est dans cette zone mémoire que sont placées les définitions des classes. Une erreur « OutOfMemeryError: PermGen space » est possible en cas de chargement d’un grand nombre de classes. Je suggère d’allouer 512Mo :

-XX:MaxPermSize=512m -XX:PermSize=512m

Etrangement, la communauté elasticsearch ne suggère pas de modifier ces paramètres.

Paramètres de la JVM liés au Garbage collector

Pour une taille de « Heap memory » inférieure à 8Go, nous utilisons l’algorithme « Concurrent-Mark-Sweep » (CMS) collector. Les paramètres liés à la GC dans ce cas là sont :

-XX:+UseConcMarkSweepGC -XX:CMSMaxAbortablePrecleanTime=6000 
-XX:+CMSParallelRemarkEnabled -XX:+ParallelRefProcEnabled -XX:CMSTriggerPermRatio=80 
-XX:CMSInitiatingOccupancyFraction=70 -XX:+UseCMSInitiatingOccupancyOnly 
-XX:CMSFullGCsBeforeCompaction=1 -XX:+CMSScavengeBeforeRemark -XX:PretenureSizeThreshold=64m
-XX:+ParallelRefProcEnabled

Le paramètre principale ici est :

-XX:+UseConcMarkSweepGC

Solr suggère dans son script de démarrage d’utiliser les autres paramètres liés au tuning fin du CMS collector, alors qu’elasticsearch suggère de ne pas les inclure et laisser la JVM fonctionner avec les valeurs par défaut.

Note : les paramètres suivants sont obsolètes avec java 8

-XX:CMSTriggerPermRatio=80 -XX:CMSFullGCsBeforeCompaction=1

Java 8 et les dernières version de Java 7 implémentent un nouvel algorithme de garbage collection : G1 (Garbage First) Collector. Cet algorithme évite les longues pauses qui bloquent la JVM particulièrement lorsque la taille de la « Heap memory » est très importante (Xmx positionné à 8 Go ou plus). Les paramètres liés à la GC dans ce cas sont :

-XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:G1HeapRegionSize=8m -XX:MaxGCPauseMillis=200

Si votre JVM utilise une taille importante de mémoire Heap et que vous rencontrez des phénomènes de blocages ponctuels de la JVM, vous pouvez tester cet algorithme.

Autres paramètres de la JVM

Voici quelques paramètres couramment suggérés par la communauté Solr et pas par la communauté elasticsearch.

-XX:NewRatio=3 -XX:SurvivorRatio=4 
-XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=8

Monitoring de la JVM

En cas de problème de performance, la recherche de solutions passe par le monitoring de la JVM. Les outils de base pour cela sont :

Le monitoring système simple avec la commande « top » de votre distribution Linux.

Cette commande permet d’avoir une vue rapide du fonctionnement du serveur.

  • mémoire utilisé
  • swap
  • io

Le monitoring avec JMX et Jconsole

Jconsole permet de monitorer en détail le fonctionnement de la JVM. Il fournit des informations telles que :

  • informations générales sur la JVM
  • utilisation de la mémoire et visualisation des cycle de garbage collection
  • information sur les threads
  • informations sur le chargement des classes

Le logging des cycles de garbage collection

Un log dédié peut être créé pour obtenir des informations sur les cycles de garbage collection.

Les paramètres conseillés à mettre en place sont presque identiques entre Solr et elasticsearch. En voici une compilation :

-verbose:gc 
-XX:+PrintHeapAtGC 
-XX:+PrintGCDetails 
-XX:+PrintGCDateStamps 
-XX:+PrintTenuringDistribution
-XX:+PrintClassHistogram 
-XX:+PrintGCApplicationStoppedTime

Configuration de Solr

Tout les paramètres sont à mettre en place dans le fichier bin/solr.in.sh

Pour Java 7, nous avons :

# Increase Java Min/Max Heap as needed to support your indexing / query needs
SOLR_JAVA_MEM="-Xms3g –Xmx3g -XX:MaxPermSize=512m -XX:PermSize=512m"

# These GC settings have shown to work well for a number of common Solr workloads
GC_TUNE="-XX:NewRatio=3 \
-XX:SurvivorRatio=4 \
-XX:TargetSurvivorRatio=90 \
-XX:MaxTenuringThreshold=8 \
-XX:+UseConcMarkSweepGC \
-XX:+CMSScavengeBeforeRemark \
-XX:PretenureSizeThreshold=64m \
-XX:CMSFullGCsBeforeCompaction=1 \
-XX:+UseCMSInitiatingOccupancyOnly \
-XX:CMSInitiatingOccupancyFraction=70 \
-XX:CMSTriggerPermRatio=80 \
-XX:CMSMaxAbortablePrecleanTime=6000 \
-XX:+CMSParallelRemarkEnabled \
-XX:+ParallelRefProcEnabled"

# Enable verbose GC logging 
GC_LOG_OPTS="-verbose:gc -XX:+PrintHeapAtGC -XX:+PrintGCDetails \ 
-XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintTenuringDistribution"

Pour Java 8, nous avons :

# Increase Java Min/Max Heap as needed to support your indexing / query needs
SOLR_JAVA_MEM="-Xms3g –Xmx3g"

# These GC settings have shown to work well for a number of common Solr workloads
GC_TUNE="-XX:NewRatio=3 \
-XX:SurvivorRatio=4 \
-XX:TargetSurvivorRatio=90 \
-XX:MaxTenuringThreshold=8 \
-XX:+UseConcMarkSweepGC \
-XX:+CMSScavengeBeforeRemark \
-XX:PretenureSizeThreshold=64m \
-XX:+UseCMSInitiatingOccupancyOnly \
-XX:CMSInitiatingOccupancyFraction=70 \
-XX:CMSMaxAbortablePrecleanTime=6000 \
-XX:+CMSParallelRemarkEnabled \
-XX:+ParallelRefProcEnabled"

# Enable verbose GC logging 
GC_LOG_OPTS="-verbose:gc -XX:+PrintHeapAtGC -XX:+PrintGCDetails \ 
-XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintTenuringDistribution"

Configuration de elasticsearch

La méthode suggérée par elasticsearch est soit d’utiliser des variables d’environnement, soit de passer les paramètres en option de la ligne de commande de lancement du demon.

La communauté elasticsearch suggère d’utiliser le plus possibles les paramètres par défaut de la JVM. Les variables d’environnement disponibles pour configurer la JVM sont donc limitées à 2 :

Positionnement de la mémoire heap :

export ES_HEAP_SIZE=3g

Limitation optionnelle de la mémoire directe :

export ES_DIRECT_SIZE=2g

Pour affiner les paramètres lorsque cela est nécessaire, il faut éditer le fichier bin/elascticsearch.in.sh

Configuration de l’OS

Swap

En cas d’utilisation élevée de la mémoire, le serveur va automatiquement utiliser une technique de swap avec le disque dur et y placer des données de la mémoire. Par défaut, ce swap peut commencer dès 60% d’occupation de la mémoire, ce qui va très certainement arriver puisque l’on cherche à l’utiliser au maximum. Ces opérations de swap vont contrecarrer tous nos efforts d’optimisation. Afin d’indiquer à l’OS de n’utiliser le swap qu’en tout dernier recours lors d’une saturation totale de la mémoire, il est possible de placer le paramètre suivant dans le fichier « /etc/sysctl.conf »

vm.swappiness = 1

Le swap commencera lorsque moins de 1% de la mémoire restera disponible.

Il est nécessaire de redémarrer le serveur suite à cette modification de paramètre.

Descripteurs de fichiers

Lucene est amené à ouvrir un très grand nombre de fichiers simultanément. L’OS limite le nombre de fichiers ouvert simultanément, mais cette limite peut être augmentée de différentes façons selon la distribution Linux.

Pour Debian et CentOS, une solution consiste à ajouter ces 2 lignes dans le fichier   “/etc/security/limits.conf”.

root soft nofile 32768
root hard nofile 32768

Il est nécessaire de redémarrer le serveur suite à cette modification de paramètre.

Et ajouter cette commande au début des scripts de démarrage de Solr et elasticsearch.

ulimit -n 32768

Mmap

Lucene utilise une technologie de mise en cache de ses index dans la mémoire directe basée sur mmapfs et niofs. Les limites par défaut des OS concernant mmap sont variables selon la distribution et trop basses, ce qui peut provoquer des erreurs « OutOfMemoryException ».

Afin d’augmenter cette limite, il est possible de placer le paramètre suivant dans le fichier « /etc/sysctl.conf »

vm.max_map_count=262144

Il est nécessaire de redémarrer le serveur suite à cette modification de paramètre.