Résoudre les problèmes de lenteur des requêtes Elasticsearch | Elastic Blog
Technique

Résoudre les problèmes de lenteur des requêtes Elasticsearch

Elasticsearch est une application très flexible, dotée de nombreuses fonctionnalités, qui propose un éventail de méthodes pour interroger vos données. Malheureusement, il arrive parfois que la vitesse des requêtes ne soit pas à la hauteur de vos espérances. Ce problème vous dit-il quelque chose ? En avez-vous déjà fait l’expérience ? Avec un système distribué comme Elasticsearch, de nombreux facteurs ont une influence sur les performances des requêtes, y compris des facteurs externes comme les paramètres d’équilibrage des charges, la latence du réseau (bande passante, cartes/pilotes réseau), etc.

Dans cet article de blog, nous allons voir ce qui peut être à l’origine de la lenteur des requêtes et comment repérer les requêtes lentes dans Elasticsearch. Nous étudierons plusieurs méthodes de résolution, qui impliquent d’avoir une bonne connaissance du fonctionnement d’Elasticsearch.

Causes fréquentes de lenteur des requêtes Elasticsearch

Avant de nous pencher sur des cas plus complexes, commençons par quelques-unes des causes les plus fréquentes de lenteur des requêtes, et leurs solutions.

Symptôme : Consommation élevée de ressources malgré un état inactif

Chaque partition consomme des ressources (processeur/mémoire). Même s’il n’y a aucune requête d’indexation ou de recherche, la présence de partitions entraîne la surconsommation de ressources de cluster.

Problème

Le cluster compte un si grand nombre de partitions que chaque requête semble prendre du temps à s’exécuter. Une bonne règle générale est de s'assurer que le nombre de partitions non gelées par nœud reste en dessous de 20 par Go de segment de mémoire configuré.

Solution

Réduire le nombre de partitions, geler les index et/ou ajouter d’autres nœuds pour gérer la charge. Pour contrôler efficacement le nombre de partitions, une solution consiste à mettre en place une architecture hot/warm (qui fonctionne très bien pour les index temporels) et d’utiliser la fonctionnalité de substitution/réduction d’Elasticsearch. Avant de réaliser un déploiement, planifiez correctement la capacité afin de déterminer le nombre optimal de partitions pour chaque cas d'utilisation de recherche.

Symptôme : Grand nombre de rejets du pool de threads

Le pool de threads de recherche affiche un nombre croissant de "rejets" qui se cumulent depuis le dernier redémarrage du cluster.

GET /_cat/thread_pool/search?v&h=node_name,name,active,rejected,completed

La réponse se présente comme suit :

node_name             name   active rejected completed
instance-0000000001   search      0       10         0
instance-0000000002   search      0       20         0
instance-0000000003   search      0       30         0

Problème

Les requêtes ciblent un trop grand nombre de partitions et le nombre de cœurs n’est pas suffisant dans le cluster. De ce fait, des tâches sont mises en file d’attente dans le pool de threads de recherche, ce qui entraîne des rejets de recherche. Autre cause fréquente : les E/S de disque sont trop lentes. Les recherches s’accumulent donc dans les files d’attente, ou, dans certains cas, le processeur est complètement saturé. 

Solution

Optez pour un modèle 1 partition primaire : 1 réplique lorsque vous créez des index. Appuyez-vous sur un modèle d’index pour appliquer cette configuration lors de l’indexation. (Le modèle 1P:1R est utilisé par défaut à partir d’Elasticsearch 7.0). À partir de la version 5.1, Elasticsearch prend en charge l’annulation des tâches de recherche, ce qui peut être utile si des requêtes lentes sont détectées dans l’API de gestion des tâches. Pour améliorer les E/S de disque, consultez nos recommandations en matière de stockage et utilisez le matériel recommandé pour bénéficier de performances optimales.

Symptôme : Grande consommation de processeur et latence élevée au niveau de l’indexation

La mise en corrélation des indicateurs montre une consommation élevée du processeur et une latence forte au niveau de l’indexation en cas de surcharge du cluster.

Problème

Le cluster réalise une indexation massive, ce qui affecte les performances de recherche.

Solution

Pour améliorer les performances d’indexation, une solution consiste à augmenter le paramètre index.refresh_ interval (c’est-à-dire la durée entre le moment où un document est indexé et le moment où il est visible) à 30 secondes par exemple. L’utilisation peut varier. Il est donc nécessaire de faire des tests. Le but : faire en sorte que les partitions n’aient pas un rythme trop soutenu qui impose de créer un segment par seconde (paramètre par défaut).

Pour les cas d'utilisation impliquant une indexation massive, consultez nos recommandations en matière de réglage de l’index pour optimiser les performances de l’index et de la recherche.

Symptôme : Augmentation de la latence suite à l’augmentation des répliques

On observe une certaine latence au niveau des requêtes après que des répliques de partition ont été ajoutées (par exemple, si l’on passe d’une partition à deux). Si le volume de données augmente, alors les données mises en cache sont écartées plus rapidement, ce qui conduit à une augmentation du nombre d’erreurs sur la page du système d'exploitation.

Problème

Le cache du système de fichiers ne dispose pas d’une mémoire suffisante pour mettre en cache les parties les plus interrogées de l’index. Le cache de requête d’Elasticsearch applique une politique d’expulsion selon l’ancienneté : lorsque le cache est plein, les données les plus anciennes sont écartées pour faire de la place aux nouvelles données.

Solution

Réservez au moins 50 % de la RAM physique pour le cache du système de fichiers. Plus vous disposez de mémoire, plus vous pouvez mettre de requêtes en cache, en particulier si le cluster a des problèmes d’E/S. Si l’on part du principe que le bloc de mémoire est correctement configuré, la RAM physique restante pouvant être utilisée pour le cache du système de fichiers aide à améliorer les performances de recherche.

Supposons que vous disposiez d’un serveur RAM de 128 Go, dont 30 Go réservés pour le bloc de mémoire et le reste pour le cache du système de fichiers (parfois appelé cache du système d’exploitation). Le système d'exploitation peut ainsi mettre en cache 4 Ko de blocs de données récemment consultés. Ainsi, si vous lisez les mêmes fichiers encore et encore, vous n’aurez pas besoin d’accéder au disque la plupart du temps ; la requête sera effectuée directement à partir de la mémoire.

Outre le cache du système de fichiers, Elasticsearch se sert du cache de recherche et du cache de requête pour accélérer les recherches. Ces caches peuvent être optimisés à l’aide d’une préférence de requête de recherche pour acheminer certaines requêtes vers le même ensemble de partitions à chaque fois, au lieu d’alterner entre les différentes copies disponibles. Ainsi, le potentiel de ces caches sera exploité de façon appropriée.

Symptôme : Consommation élevée lors du partage de ressources

Le système d'exploitation affiche une utilisation élevée du processeur et des E/S de disque. On constate des gains de performances après l’arrêt d’applications tierces.

Problème

On observe des conflits de ressources (processeur et/ou E/S de disque) entre d’autres processus (p. ex. Logstash) et Elasticsearch.

Solution

Évitez d’exécuter Elasticsearch avec d’autres applications gourmandes en ressources sur un matériel partagé.

Symptôme : Consommation de mémoire élevée lors de l’agrégation de champs uniques

Performances faibles lors de l’interrogation de champs agrégés contenant des valeurs uniques (p. ex. ID, noms d'utilisateur, adresses e-mail, etc.). Lors de l’analyse du dump mémoire, vous devriez voir des objets Java avec des termes tels que "search", "buckets", "aggregation", etc., utilisant une grande part du bloc de mémoire.

Problème

Les agrégations s’exécutent sur des champs à forte cardinalité nécessitant un grand volume de ressources pour récupérer de nombreux buckets. Il peut également y avoir des agrégations imbriquées impliquant des champs imbriqués et/ou des champs de jointure.

Solution

Pour améliorer les performances des agrégations de termes à forte cardinalité, lisez cet article de blog rédigé par mon collègue de l’équipe de conseil : https://www.elastic.co/fr/blog/improving-the-performance-of-high-cardinality-terms-aggregations-in-elasticsearch (en anglais).

Pour optimiser davantage les performances, découvrez nos recommandations sur les champs imbriqués et les champs de jointure.

Requêtes lentes occasionnelles

De façon générale, les requêtes qui sont lentes de façon occasionnelle ou intermittente peuvent être améliorées grâce à nos recommandations sur l’optimisation de l’index et l’optimisation de la recherche. Ces requêtes lentes occasionnelles devraient être mises en corrélation étroite avec un ou plusieurs indicateurs de monitoring :

Elasticsearch dispose d’une autre fonctionnalité utile appelée Adaptive Replica Selection (ARS) (sélection de réplique adaptative) qui permet au nœud coordinateur de connaître la charge s’exécutant sur les nœuds de données et de choisir la copie de partition la plus adaptée pour effectuer une recherche. Résultat : les performances de recherche sont optimisées et la latence est réduite. Cette fonctionnalité peut être d’une grande aide lors des ralentissements occasionnels, car elle répartit la charge de façon égale lors de la requête. À partir de la version 7.0 d’Elasticsearch, l’ARS est activée par défaut.

Requêtes lentes permanentes

Dans le cas d’une requête lente permanente, ce que nous pouvons essayer de faire, c’est de supprimer des fonctionnalités de la requête l’une après l’autre, puis de vérifier si la requête est toujours lente. En reproduisant le problème de performance à partir de la requête la plus simple possible, nous sommes en mesure d’isoler et d’identifier le problème plus facilement :

  • La requête est-elle toujours lente sans la mise en surbrillance ?
  • La requête est-elle toujours lente sans les agrégations ?
  • Est-elle toujours lente si la taille est définie sur 0 ? (lorsque la taille est définie sur 0, Elasticsearch met en cache les résultats des requêtes de recherche pour accélérer la recherche)

Est-il possible d’y appliquer des recommandations en matière d’optimisation de la recherche ?

Pour résoudre les problèmes, voici quelques actions utiles à exécuter :

  • Obtenir une réponse à la requête avec le profil activé.
  • Recueillir les résultats des threads hot des nœuds avec la requête qui s’exécute dans une boucle while(true). Cela permettra de comprendre l’utilisation du processeur.
  • Établir un profil de la requête à l’aide de cette version conviviale de l’API de profil.

Si une requête provient d’une visualisation Kibana, utilisez le visualization spy panel (panneau des coulisses de la visualisation) (version 6.3 de Kibana et précédentes) ou le dashboard inspect panel (panneau d’inspection du tableau de bord) (à partir de la version 6.4 de Kibana) pour afficher et exporter la requête de recherche réelle et l’importer dans l’API de profil pour effectuer une analyse plus poussée.

Détection de requêtes lentes ou lourdes

Il est parfois difficile de détecter les requêtes lentes ou lourdes (c.-à-d. nécessitant beaucoup de ressources), étant donné que plusieurs requêtes ou threads sont traités simultanément au sein d’une application distribuée comme Elasticsearch. Les choses se compliquent davantage lorsqu’aucun contrôle n’est exercé sur les utilisateurs qui exécutent des requêtes lourdes ralentissant les performances du cluster (p. ex. cycles longs de récupération de mémoire), ou pire, provoquant une saturation de la mémoire.

À partir de la version 7.0 d’Elasticsearch, une nouvelle stratégie de disjoncteur fait son apparition. Celle-ci mesure l’utilisation réelle de la mémoire lorsqu’une partie du bloc de mémoire est réservée. Cette nouvelle stratégie améliore la résilience des nœuds par rapport aux requêtes lourdes qui surchargent le cluster. Elle est activée par défaut et peut être contrôlée avec le nouveau paramètre de cluster indices.breaker.total.use_real_memory.

Il s’agit là des cas les plus courants. Pour les cas non abordés dans ces solutions, il peut être utile de recueillir le dump mémoire après un crash en raison de la saturation de la mémoire ou le dump mémoire d’une JVM en cours d’exécution pour mieux comprendre la cause profonde.

Elasticsearch dispose aussi d’un autre atout dans sa manche : la limite non stricte du nombre maximal de buckets. Il s’agit d’un paramètre de protection visant à préserver le cluster des situations de saturation de la mémoire. Ce paramètre interrompt l’exécution de la requête de recherche si le nombre maximal de buckets est atteint (par défaut, 10 000 dans la version 7.0). Cela peut être le cas lors de l’exécution de plusieurs couches d’agrégations, par exemple.

Pour identifier les requêtes potentiellement lourdes, nous pouvons définir un paramètre de disjoncteur (indices.breaker.request.limit), en commençant par déterminer un seuil faible pour isoler les requêtes, puis en augmentant ensuite ce seuil de façon graduelle pour affiner notre recherche.

Slowlogs

Pour identifier les requêtes lentes, vous pouvez également activer les slowlogs dans Elasticsearch. Un slowlog intervient spécifiquement au niveau de la partition, c’est-à-dire uniquement au niveau des nœuds de données. Les nœuds coordinateurs/clients sont exclus car ils ne détiennent aucune donnée (index/partitions).

Les slowlogs permettent de répondre à des questions, telles que :

  • Combien de temps la requête a-t-elle mis ?
  • Quel était le contenu du corps de la requête de recherche ?

Exemple de sortie de slowlog :

[2019-02-11T16:47:39,882][TRACE][index.search.slowlog.query] [2g1yKIZ] [logstash-20190211][4] took[10.4s], took_millis[10459], total_hits[16160], types[], stats[], 
search_type[QUERY_THEN_FETCH], total_shards[10], source[{"size":0,"query":{"bool":{"must":[{"range":{"timestamp":{"from":1549266459837,"to":1549871259837,"include_lower":true,
"include_upper":true,"format":"epoch_millis","boost":1.0}}}],"adjust_pure_negative":true,"boost":1.0}},"_source":{"includes":[],"excludes":[]},"stored_fields":"*","docvalue_fields":
[{"field":"timestamp","format":"date_time"},{"field":"utc_time","format":"date_time"}],"script_fields":{"hour_of_day":{"script":{"source":"doc['timestamp'].value.getHourOfDay()",
"lang":"painless"},"ignore_failure":false}},"aggregations":{"maxAgg":{"max":{"field":"bytes"}},"minAgg":{"min":{"field":"bytes"}}}}], id[]],

Vue détaillée du message de slowlog :

ÉlémentDescription
[2019-02-11T16:47:39,882]Date de la requête
[TRACE]Niveau de log
[index.search.slowlog.query]Phase de requête du slowlog de recherche
[2g1yKIZ]Nom du nœud
[logstash-20190211]Nom de l’index
[4]Numéro de partition sur la requête exécutée
took[10.4s]Durée de traitement sur la partition [4] Remarque : lorsque nous étudions les slowlogs, il ne faut pas ajouter les durées des différentes partitions, car ces partitions peuvent s’exécuter en parallèle.
took_millis[10459]Durée en millisecondes
total_hits[16160]Nombre total de résultats
search_type[QUERY_THEN_FETCH]Type de recherche
total_shards[10]Nombre total de partitions de l’index
source[]Corps de la requête exécutée

Logs d’audit

Les clients ayant souscrit un abonnement Gold ou Platinum, comprenant des fonctionnalités de sécurité Elastic, peuvent activer les logs d’audit pour obtenir plus de détails sur la requête. (Les utilisateurs peuvent commencer avec une version d’essai de 30 jours pour tester les fonctionnalités de sécurité Elastic.) Le logging d'audit vous aide à répondre à des questions telles que :

  • Quand la requête a-t-elle eu lieu ?
  • Qui en est à l’origine ?
  • Quel était le contenu de la requête ?

Il est nécessaire de régler les paramètres d’audit, car ceux définis par défaut sont plutôt larges :

  1. Pour activer les logs d’audit de sécurité : définissez xpack.security.audit.enabled: true dans elasticsearch.yml.
  2. Pour activer le logging ou l’indexation dans les résultats des audits de sécurité : définissez xpack.security.audit.outputs:[logfile, index] dans elasticsearch.yml.

    Remarques :
    • Le paramètre xpack.security.audit.outputs concerne uniquement les versions 6.0 à 6.2 et les versions 5.x. La version 7.0 n’accepte plus ce paramètre. Elle utilise par défaut la sortie json (<nom du cluster>_audit.json) lorsque xpack.security.audit.enabled est défini sur true.
    • Dans le cadre de la résolution, nous vous recommandons d’opter pour les logs plutôt que l’index, car la verbosité du logging d'audit peut exercer une contrainte non voulue sur les performances du cluster et entraîner une extension de l’index de sécurité au-delà de la taille escomptée.
    • Le mode d’audit peut être en effet très prolixe. Aussi, pensez à le désactiver une fois la résolution terminée.
  3. Inclure l’accès authentication_success dans votre liste d’évènements : définissez xpack.security.audit.logfile.events.include: authentication_success dans elasticsearch.yml.

    Remarques :
    • Ce paramètre ne fait pas partie des événements par défaut. Si vous le définissez, il écrasera ceux proposés par défaut.
    • Si vous devez ajouter (non pas remplacer) un événement de plus, écrivez d’abord la liste des événements par défaut existants, puis ajoutez le paramètre ci-dessus après la dernière entrée.
    • Générez la sortie du corps de la requête dans les événements d’audit : définissez xpack.security.audit.logfile.events.emit_request_body: true dans elasticsearch.yml.

Avec ces paramètres, vous pouvez monitorer la requête utilisateur comme suit.

  • Utilisateur : louisong
  • Heure de la requête : 2019-05-01T19:26:53,554 (UTC)
  • Point de terminaison de la requête : _msearch (signifie généralement qu’il s’agit d’une requête provenant d’une visualisation ou d’un tableau de bord Kibana)
  • Corps de la requête : à partir de "request.body": dans le log ci-dessous :

    {"@timestamp":"2019-05-01T19:26:53,554", "node.id":"Z1z_64sIRcy4dW2eqyqzMw", "event.type":"rest", "event.action":"authentication_success", "user.name":"louisong", "origin.type":"rest", "origin.address":"127.0.0.1:51426", "realm":"default_native", "url.path":"/_msearch", "url.query":"rest_total_hits_as_int=true&ignore_throttled=true", "request.method":"POST", "request.body":"{\"index\":\"*\",\"ignore_unavailable\":true,\"preference\":1556709820160}\n{\"aggs\":{\"2\":{\"terms\":{\"field\":\"actions\",\"size\":5,\"order\":{\"_count\":\"desc\"},\"missing\":\"__missing__\"}}},\"size\":0,\"_source\":{\"excludes\":[]},\"stored_fields\":[\"*\"],\"script_fields\":{},\"docvalue_fields\":[{\"field\":\"access_token.user_token.expiration_time\",\"format\":\"date_time\"},{\"field\":\"canvas-workpad.@created\",\"format\":\"date_time\"},{\"field\":\"canvas-workpad.@timestamp\",\"format\":\"date_time\"},{\"field\":\"creation_time\",\"format\":\"date_time\"},{\"field\":\"expiration_time\",\"format\":\"date_time\"},{\"field\":\"maps-telemetry.timeCaptured\",\"format\":\"date_time\"},{\"field\":\"task.runAt\",\"format\":\"date_time\"},{\"field\":\"task.scheduledAt\",\"format\":\"date_time\"},{\"field\":\"updated_at\",\"format\":\"date_time\"},{\"field\":\"url.accessDate\",\"format\":\"date_time\"},{\"field\":\"url.createDate\",\"format\":\"date_time\"}],\"query\":{\"bool\":{\"must\":[{\"range\":{\"canvas-workpad.@timestamp\":{\"format\":\"strict_date_optional_time\",\"gte\":\"2019-05-01T11:11:53.498Z\",\"lte\":\"2019-05-01T11:26:53.498Z\"}}}],\"filter\":[{\"match_all\":{}},{\"match_all\":{}}],\"should\":[],\"must_not\":[]}},\"timeout\":\"30000ms\"}\n", "request.id":"qrktsPxyST2nVh29GG7tog"}
        

    Conclusion

    Dans cet article, nous avons abordé les causes fréquentes à l’origine de la lenteur des requêtes et les solutions pour y remédier. Nous avons également vu différentes méthodes pour identifier les requêtes lentes occasionnelles et permanentes. Les requêtes lentes sont généralement un symptôme d’un problème de performance plus vaste au niveau du cluster, qu’il est nécessaire de résoudre.

    Chez Elasticsearch, nous cherchons constamment à améliorer les temps de requête et nous mettons tout en œuvre pour accélérer les performances de recherche avec nos versions ultérieures. J’espère que cet article vous a donné des pistes pour gérer les requêtes lentes. N’hésitez pas à publier vos questions sur notre forum de discussion Elasticsearch si vous souhaitez approfondir le sujet. Bonne recherche !