Optimisation des requêtes Elasticsearch lentes : meilleures pratiques pour le réglage des performances
Elasticsearch est un moteur de recherche et d'analyse distribué et puissant, capable de gérer de vastes quantités de données. Cependant, même avec son architecture robuste, des requêtes inefficaces peuvent entraîner des performances lentes, ce qui nuit à l'expérience utilisateur et à la réactivité de l'application. L'identification et la résolution de ces goulots d'étranglement sont essentielles pour maintenir un cluster Elasticsearch sain et très performant.
Cet article explore en profondeur des stratégies pratiques pour améliorer les performances de recherche lentes. Nous verrons comment optimiser la structure de vos requêtes, exploiter efficacement divers mécanismes de mise en cache, et utiliser l'outil intégré Profile API d'Elasticsearch pour identifier la source exacte des problèmes de performance. En appliquant ces meilleures pratiques, vous pouvez réduire considérablement la latence des requêtes et garantir que votre cluster Elasticsearch fonctionne à son efficacité maximale.
Comprendre les goulots d'étranglement des performances des requêtes
Avant de plonger dans les solutions, il est utile de comprendre les raisons courantes des requêtes Elasticsearch lentes. Celles-ci incluent souvent :
- Requêtes complexes : Requêtes comportant plusieurs clauses
bool, des requêtes imbriquées, ou des opérations coûteuses commewildcardouregexpsur de grands ensembles de données. - Récupération de données inefficace : Récupération inutile de
_source, ou récupération d'un grand nombre de documents pour la pagination. - Contraintes de ressources : Insuffisance de CPU, de mémoire ou d'E/S disque sur les nœuds de données.
- Mappings sous-optimaux : Utilisation de types de données incorrects ou non-exploitation des
doc_valuespour les agrégations. - Déséquilibre ou surcharge des Shards : Trop de shards, pas assez de shards, ou distribution inégale des shards/données.
- Absence de mise en cache : Non-utilisation des mécanismes de mise en cache intégrés d'Elasticsearch ou des caches externes au niveau de l'application.
Optimisation de la structure des requêtes
La manière dont vous construisez vos requêtes a un impact profond sur leurs performances. De petits changements peuvent entraîner des améliorations significatives.
1. Récupérer uniquement les champs nécessaires (Filtrage _source et stored_fields)
Par défaut, Elasticsearch renvoie l'intégralité du champ _source pour chaque document correspondant. Si votre application n'a besoin que de quelques champs, récupérer l'intégralité de _source est un gaspillage en termes de bande passante réseau et de temps d'analyse (parsing).
-
Filtrage
_source: Utilisez le paramètre_sourcepour spécifier un tableau de champs à inclure ou à exclure.json GET /my-index/_search { "_source": ["title", "author", "publish_date"], "query": { "match": { "content": "Elasticsearch performance" } } } -
stored_fields: Si vous avez stocké explicitement des champs spécifiques dans votre mapping (par exemple,"store": true), vous pouvez les récupérer directement en utilisantstored_fields. Cela contourne l'analyse de_sourceet peut être plus rapide si_sourceest volumineux.json GET /my-index/_search { "stored_fields": ["title", "author"], "query": { "match": { "content": "Elasticsearch performance" } } }
2. Préférer les types de requêtes efficaces
Certains types de requêtes consomment intrinsèquement plus de ressources que d'autres.
-
Éviter les jokers et les expressions régulières en tête (Leading Wildcards and Regexps) : Les requêtes
wildcard,regexpetprefixsont coûteuses en calcul, surtout lorsqu'elles sont utilisées avec un joker en tête (par exemple,*test). Elles doivent scanner l'intégralité du dictionnaire de termes pour trouver les termes correspondants. Si possible, repensez votre application pour les éviter ou utilisez lescompletion suggesterspour la correspondance de préfixes.```json
Inefficace - éviter le joker en tête
{
"query": {
"wildcard": {
"name.keyword": {
"value": "*search"
}
}
}
}Meilleur - si vous connaissez le préfixe
{
"query": {
"prefix": {
"name.keyword": {
"value": "Elastic"
}
}
}
}
``` -
Utiliser
match_phraseau lieu de clausesmatchmultiples pour les phrases : Pour la correspondance de phrase exacte,match_phraseest plus efficace que de combiner plusieurs requêtesmatchau sein d'une requêtebool. -
constant_scorepour le filtrage : Lorsque vous vous souciez uniquement de savoir si un document correspond à un filtre et non de la qualité de son score, enveloppez votre requête dans une requêteconstant_score. Cela contourne les calculs de scoring, ce qui peut économiser des cycles CPU.json GET /my-index/_search { "query": { "constant_score": { "filter": { "term": { "status": "active" } } } } }
3. Optimiser les requêtes booléennes
- Ordre des clauses : Placez les clauses les plus restrictives (celles qui filtrent le plus de documents) au début de votre requête
bool. Elasticsearch traite les requêtes de gauche à droite, et l'élagage précoce peut réduire considérablement le nombre de documents traités par les clauses suivantes. minimum_should_match: Utilisezminimum_should_matchdans les requêtesboolpour spécifier le nombre minimum de clausesshouldqui doivent correspondre. Cela peut aider à élaguer les résultats rapidement.
4. Pagination efficace (search_after et scroll)
La pagination traditionnelle from/size devient très inefficace pour les pages profondes (par exemple, from: 10000, size: 10). Elasticsearch doit récupérer et trier tous les documents jusqu'à from + size sur chaque shard, puis ignorer from documents.
-
search_after: Pour la pagination profonde en temps réel,search_afterest recommandé. Il utilise l'ordre de tri du dernier document de la page précédente pour trouver l'ensemble de résultats suivant, similaire aux curseurs dans les bases de données traditionnelles. Il est sans état (stateless) et s'adapte mieux à l'échelle.```json
Première requête
GET /my-index/_search
{
"size": 10,
"query": {"match_all": {}},
"sort": [{"timestamp": "asc"}, {"_id": "asc"}]
}Requête suivante utilisant les valeurs de tri du dernier document de la première requête
GET /my-index/_search
{
"size": 10,
"query": {"match_all": {}},
"search_after": [1678886400000, "doc_id_XYZ"],
"sort": [{"timestamp": "asc"}, {"_id": "asc"}]
}
``` -
scrollAPI : Pour la récupération en masse de grands ensembles de données (par exemple, pour la réindexation ou la migration de données), l'APIscrollest idéale. Elle prend un instantané de l'index et renvoie un ID de défilement (scroll ID), qui est ensuite utilisé pour récupérer les lots suivants. Elle ne convient pas à la pagination en temps réel destinée aux utilisateurs.
5. Optimisation des agrégations
Les agrégations peuvent être gourmandes en ressources, en particulier sur les champs à forte cardinalité.
- Pré-calcul des agrégations : Envisagez d'exécuter des agrégations complexes non en temps réel pendant l'indexation ou selon un calendrier pour pré-calculer les résultats et les stocker dans un index séparé.
doc_values: Assurez-vous que les champs utilisés dans les agrégations ontdoc_valuesactivés (ce qui est la valeur par défaut pour la plupart des champs nontext). Cela permet à Elasticsearch de charger efficacement les données pour les agrégations sans charger_source.eager_global_ordinals: Pour les champskeywordfréquemment utilisés dans les agrégationsterms, définireager_global_ordinals: truedans le mapping peut améliorer les performances en pré-construisant les ordinaux globaux. Cela engendre un coût lors de l'actualisation de l'index (index refresh time) mais accélère les agrégations au moment de la requête.
Exploiter les techniques de mise en cache
Elasticsearch offre plusieurs couches de mise en cache qui peuvent accélérer considérablement les requêtes répétées.
1. Cache de requêtes de nœud (Node Query Cache)
- Mécanisme : Met en cache les résultats des clauses de filtre au sein des requêtes
boolqui sont utilisées fréquemment. Il s'agit d'un cache en mémoire au niveau du nœud. - Efficacité : Plus efficace pour les filtres qui sont constants sur de nombreuses requêtes et qui correspondent à un nombre relativement faible de documents (moins de 10 000 documents).
- Configuration : Activé par défaut. Vous pouvez contrôler sa taille avec
indices.queries.cache.size(par défaut 10 % du tas JVM).
2. Cache de requêtes de shard (Shard Request Cache)
- Mécanisme : Met en cache la réponse entière d'une requête de recherche (y compris les hits, les agrégations, et les suggestions) par shard. Il ne fonctionne que pour les requêtes où
size=0et pour les requêtes qui utilisent uniquement des clauses de filtre (sans scoring). - Efficacité : Excellent pour les requêtes de tableau de bord ou les applications analytiques où la même requête (y compris les agrégations) est exécutée à plusieurs reprises avec des paramètres identiques.
-
Comment l'utiliser : Activez-le explicitement dans votre requête en utilisant
"request_cache": true.json GET /my-index/_search?request_cache=true { "size": 0, "query": { "bool": { "filter": [ {"term": {"status.keyword": "active"}}, {"range": {"timestamp": {"gte": "now-1h"}}} ] } }, "aggs": { "messages_per_minute": { "date_histogram": { "field": "timestamp", "fixed_interval": "1m" } } } } -
Mises en garde : Le cache est invalidé chaque fois qu'un shard est actualisé (de nouveaux documents sont indexés ou ceux existants sont mis à jour). N'est utile que pour les requêtes qui renvoient fréquemment des résultats identiques.
3. Cache du système de fichiers (Niveau OS)
- Mécanisme : Le cache du système de fichiers du système d'exploitation joue un rôle essentiel. Elasticsearch s'appuie fortement dessus pour mettre en cache les segments d'index fréquemment consultés.
- Efficacité : Crucial pour les performances des requêtes. Si les segments d'index sont dans la RAM, les E/S disque sont entièrement contournées, ce qui conduit à une exécution des requêtes beaucoup plus rapide.
- Meilleure pratique : Allouez au moins la moitié de la RAM de votre serveur au cache du système de fichiers, et l'autre moitié au tas JVM (heap) d'Elasticsearch. Par exemple, si vous disposez de 64 Go de RAM, allouez 32 Go au tas Elasticsearch et laissez 32 Go pour le cache du système de fichiers de l'OS.
4. Mise en cache au niveau de l'application
- Mécanisme : Implémenter un cache au niveau de votre couche application (par exemple, en utilisant Redis, Memcached ou un cache en mémoire) pour les résultats de recherche fréquemment demandés.
- Efficacité : Peut fournir les temps de réponse les plus rapides en contournant complètement Elasticsearch pour les requêtes répétées. Idéal pour les résultats de recherche statiques ou qui changent lentement.
- Considérations : La stratégie d'invalidation du cache est essentielle. Nécessite une conception minutieuse pour garantir la cohérence des données.
Utilisation de l'API Profile pour l'identification des goulots d'étranglement
L'Profile API est un outil inestimable pour comprendre exactement comment Elasticsearch exécute une requête et où le temps est passé. Il décompose le temps d'exécution pour chaque composant de votre requête et agrégation.
Comment utiliser l'API Profile
Ajoutez simplement "profile": true au corps de votre requête de recherche.
GET /my-index/_search
{
"profile": true,
"query": {
"bool": {
"must": [
{"match": {"title": "Elasticsearch"}},
{"term": {"status.keyword": "published"}}
],
"filter": [
{"range": {"publish_date": {"gte": "2023-01-01"}}}
]
}
},
"aggs": {
"top_authors": {
"terms": {
"field": "author.keyword",
"size": 10
}
}
}
}
Interprétation des résultats de l'API Profile
La réponse inclura une section profile détaillant l'exécution de la requête et de l'agrégation sur chaque shard. Les métriques clés à surveiller comprennent :
description: Le composant spécifique de la requête ou de l'agrégation.time_in_nanos: Le temps passé à exécuter ce composant.breakdown: Des sous-métriques détaillées telles quebuild_scorer_time,collect_time,set_weight_timepour les requêtes, etreduce_timepour les agrégations.children: Composants imbriqués, montrant comment le temps est distribué au sein des requêtes complexes.
Exemple d'interprétation :
Si vous voyez un time_in_nanos élevé pour une WildcardQuery, cela confirme qu'il s'agit d'une partie coûteuse de votre requête. Si collect_time est élevé, cela suggère que la récupération et le traitement des documents après une correspondance est un goulot d'étranglement, potentiellement dû à l'analyse de _source ou à la pagination profonde. Un reduce_time élevé dans les agrégations pourrait indiquer une charge importante lors de la phase de fusion finale (final merge phase).
En examinant ces métriques, vous pouvez identifier les clauses de requête ou les champs d'agrégation spécifiques qui consomment le plus de ressources, puis appliquer les techniques d'optimisation abordées précédemment.
Meilleures pratiques générales pour les performances
Au-delà des optimisations spécifiques aux requêtes, plusieurs meilleures pratiques au niveau du cluster et de l'index contribuent à la performance globale de la recherche.
1. Mappings d'index optimaux
textvs.keyword: Utiliseztextpour la recherche en texte intégral etkeywordpour la correspondance de valeur exacte, le tri et les agrégations. Des types mal assortis peuvent entraîner des requêtes inefficaces.doc_values: Assurez-vous quedoc_valuesest activé pour les champs sur lesquels vous avez l'intention de trier ou d'agréger. Il est activé par défaut pour les typeskeywordet numériques, mais le désactiver explicitement pour un champtextpourrait économiser de l'espace disque au détriment des performances d'agrégation si vous devez ultérieurement agréger sur celui-ci.norms: Désactiveznorms("norms": false) pour les champs où vous n'avez pas besoin de normalisation de la longueur du document (par exemple, les champs d'ID). Cela économise de l'espace disque et améliore la vitesse d'indexation, avec un impact minimal sur les performances des requêtes pour les requêtes sans scoring.index_options: Pour les champstext, utilisezindex_options: docssi vous avez seulement besoin de savoir si un terme existe dans un document, etindex_options: positions(la valeur par défaut) si vous avez besoin de requêtes de phrases et de recherches de proximité.
2. Surveiller la santé et les ressources du cluster
- Statut du cluster Vert (Green Cluster Status) : Assurez-vous que votre cluster est toujours vert. Le statut jaune ou rouge indique des shards non alloués ou manquants, ce qui peut gravement affecter la fiabilité et les performances des requêtes.
- Surveillance des ressources : Surveillez régulièrement le CPU, la RAM, les E/S disque et l'utilisation du réseau sur vos nœuds de données. Les pics dans ces métriques sont souvent corrélés avec des requêtes lentes.
- Tas JVM (JVM Heap) : Gardez un œil sur l'utilisation du tas JVM. Une utilisation élevée peut entraîner de fréquentes pauses dues au garbage collection, rendant les requêtes lentes. Optimisez les requêtes pour réduire la pression sur le tas.
3. Allocation appropriée des Shards
- Trop de Shards : Chaque shard consomme des ressources (CPU, RAM, descripteurs de fichiers). Avoir trop de petits shards sur un nœud peut entraîner une surcharge. Visez des shards de taille raisonnable (par exemple, 10 Go à 50 Go pour la plupart des cas d'utilisation).
- Pas assez de Shards : Limite le parallélisme. Les requêtes sur un index avec trop peu de shards ne pourront pas exploiter efficacement tous les nœuds de données disponibles.
4. Stratégie d'indexation
- Intervalle d'actualisation (Refresh Interval) : Un
refresh_intervalplus bas (par défaut 1 seconde) rend les données visibles plus rapidement mais augmente la charge d'indexation. Pour les charges de travail très axées sur la recherche, envisagez de l'augmenter légèrement (par exemple, 5 à 10 secondes) pour réduire la pression d'actualisation.
Conclusion
L'optimisation des requêtes Elasticsearch lentes est un processus continu qui implique de comprendre vos données, vos modèles d'accès et le fonctionnement interne d'Elasticsearch. En appliquant une construction de requêtes réfléchie, en utilisant efficacement les mécanismes de mise en cache d'Elasticsearch et en tirant parti d'outils de diagnostic puissants comme l'Profile API, vous pouvez améliorer considérablement les performances et la réactivité de vos applications de recherche.
Une surveillance régulière, associée à une analyse approfondie des requêtes lentes spécifiques à l'aide de l'API Profile, vous permettra d'affiner continuellement votre configuration Elasticsearch, garantissant une expérience de recherche rapide et efficace pour vos utilisateurs. N'oubliez pas qu'un index bien structuré et un cluster sain sont les fondations sur lesquelles toutes les optimisations de requêtes sont construites.