Optimisation des requêtes Elasticsearch lentes : bonnes pratiques pour le réglage des performances
Diagnostiquez et améliorez les requêtes Elasticsearch lentes grâce à une meilleure forme de requête, une pagination optimisée, une mise en cache, des mappings adaptés et l'API Profile.
Optimisation des requêtes Elasticsearch lentes : bonnes pratiques pour le réglage des performances
Les requêtes Elasticsearch lentes proviennent généralement de l'une des quatre causes suivantes : la requête demande trop de données, le mapping rend la requête coûteuse, le cluster manque de ressources, ou l'application répète des recherches coûteuses qui devraient être mises en cache ou repensées. La solution dépend de la cause identifiée.
Avant de tout réécrire, capturez une requête lente réelle avec son index, ses filtres, son tri, ses agrégations, la profondeur de page, la taille de la réponse et le temps d'exécution. Une agrégation de tableau de bord, une requête d'autocomplétion et un travail d'exportation sollicitent Elasticsearch de manière différente.
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 avec plusieurs clauses
bool, requêtes imbriquées, ou opérations coûteuses commewildcardouregexpsur de grands ensembles de données. - Récupération inefficace des données : Récupération inutile de
_source, ou récupération d'un grand nombre de documents pour la pagination. - Contraintes de ressources : CPU, mémoire ou E/S disque insuffisants sur les nœuds de données.
- Mappings sous-optimaux : Utilisation de types de données incorrects ou non-exploitation de
doc_valuespour les agrégations. - Déséquilibre ou surcharge des shards : Trop de shards, pas assez de shards, ou répartition inégale des shards/données.
- Absence de cache ou mauvaise adaptation du cache : Répétition de recherches coûteuses sans utiliser la mise en cache des requêtes, le contexte de filtre, ou la mise en cache au niveau de l'application lorsque cela est approprié.
Optimisation de la structure des requêtes
La façon 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 vos documents sont volumineux et que l'interface utilisateur n'a besoin que d'un titre, d'un ID et d'un horodatage, la récupération du document complet gaspille la bande passante réseau et le temps d'analyse.
Filtrage
_source: Utilisez le paramètre_sourcepour spécifier un tableau de champs à inclure ou exclure.GET /my-index/_search { "_source": ["title", "author", "publish_date"], "query": { "match": { "content": "Elasticsearch performance" } } }stored_fields: Si vous avez explicitement stocké des champs spécifiques dans votre mapping ("store": true), vous pouvez les récupérer avecstored_fields. La plupart des déploiements ne stockent pas beaucoup de champs de cette façon, donc le filtrage_sourceest la solution la plus courante.GET /my-index/_search { "stored_fields": ["title", "author"], "query": { "match": { "content": "Elasticsearch performance" } } }
2. Privilégier les types de requêtes efficaces
Certains types de requêtes sont intrinsèquement plus gourmands en ressources que d'autres.
Éviter les wildcards en tête et les regexp larges : Les requêtes
wildcardetregexppeuvent être coûteuses, surtout avec des wildcards en tête comme*test. Les requêtesprefixsont généralement plus gérables que les recherches avec wildcard en tête, mais elles nécessitent toujours des mappings sensés et une entrée limitée.# Inefficace - éviter le wildcard en tête { "query": { "wildcard": { "name.keyword": { "value": "*search" } } } } # Mieux - si vous connaissez le préfixe { "query": { "prefix": { "name.keyword": { "value": "Elastic" } } } }Utiliser
match_phrasepour l'intention de phrase : Si l'utilisateur recherche une phrase exacte,match_phraseexprime mieux cette intention que plusieurs clausesmatchnon liées. Ce n'est pas toujours moins cher, mais cela évite de renvoyer des documents qui contiennent les mots éloignés les uns des autres.Contexte de filtre pour les conditions oui/non : Lorsque vous vous souciez uniquement de savoir si un document correspond à une condition, placez cette condition dans le contexte
filterou utilisezconstant_score. Cela évite un travail de scoring inutile et est plus favorable au cache.GET /my-index/_search { "query": { "constant_score": { "filter": { "term": { "status": "active" } } } } }
3. Optimiser les requêtes booléennes
- Utiliser des filtres pour les contraintes structurées : Placez les ID de tenant, les valeurs de statut, les plages de dates et les balises exactes dans
filter, pas dansmust, sauf si elles nécessitent un scoring. Elasticsearch peut réorganiser et optimiser les clauses en interne, donc ne vous fiez pas à l'ordre JSON comme principal outil de performance. - Utiliser
minimum_should_matchintentionnellement : Cela peut améliorer la pertinence et réduire les correspondances larges, mais le définir trop haut peut masquer des résultats valides.
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 jeter les documents from.
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 et s'adapte mieux.# 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"}] }API
scroll: Pour la récupération en masse de grands ensembles de données, comme le réindexage ou les exportations,scrollpeut encore être utile. Pour les versions plus récentes d'Elasticsearch et les analyses complètes d'index de longue durée, envisagez également point-in-time plussearch_after. Scroll ne convient pas à la pagination en temps réel destinée aux utilisateurs.
5. Optimiser les agrégations
Les agrégations peuvent être gourmandes en ressources, en particulier sur les champs à haute cardinalité.
- Pré-calcul des agrégations : Envisagez d'exécuter des agrégations complexes non temps réel lors de 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é (ce qui est la valeur par défaut pour la plupart des champs non textuels). Cela permet à Elasticsearch de charger les données pour les agrégations efficacement 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 entraîne un coût au moment de l'actualisation de l'index mais accélère les agrégations au moment de la requête.
Exploiter les techniques de mise en cache
Elasticsearch offre plusieurs niveaux 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
- Mécanisme : Met en cache les résultats des clauses de filtre dans les requêtes
boolqui sont utilisées fréquemment. C'est un cache en mémoire au niveau du nœud. - Efficacité : Plus efficace pour les clauses de filtre répétées. Ne comptez pas dessus pour chaque requête ; Elasticsearch décide ce qui vaut la peine d'être mis en cache.
- Configuration : Activé par défaut. Vous pouvez contrôler sa taille avec
indices.queries.cache.size(10% du tas par défaut).
2. Cache de requêtes de shard
Mécanisme : Met en cache les résultats de recherche au niveau du shard, le plus souvent pour les requêtes lourdes en agrégations avec
size=0. C'est un bon choix pour les requêtes de tableau de bord répétées sur des données qui ne changent pas à chaque seconde.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 de manière répétée avec des paramètres identiques.
Comment l'utiliser : Activez-le explicitement dans votre requête en utilisant
"request_cache": true.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é (nouveaux documents indexés ou existants mis à jour). Utile uniquement pour les requêtes qui renvoient des résultats identiques fréquemment.
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 critique. Elasticsearch s'appuie fortement sur lui 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 complètement contournées, ce qui conduit à une exécution beaucoup plus rapide des requêtes.
- Bonne pratique : Laissez une quantité substantielle de RAM pour le cache du système de fichiers. Un point de départ courant est de maintenir le tas JVM autour de la moitié de la mémoire système, en gardant à l'esprit les limites habituelles du tas Elasticsearch, puis validez avec votre charge de travail.
4. Mise en cache au niveau de l'application
- Mécanisme : Implémentez un cache au niveau de votre couche applicative (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 à évolution lente.
- 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'API Profile est un outil inestimable pour comprendre exactement comment Elasticsearch exécute une requête et où le temps est passé. Elle décompose le temps d'exécution pour chaque composant de votre requête et de votre 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 des requêtes et des agrégations sur chaque shard. Les métriques clés à rechercher incluent :
description: Le composant de requête ou d'agrégation spécifique.time_in_nanos: Le temps passé à exécuter ce composant.breakdown: Sous-métriques détaillées commebuild_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é dans les requêtes complexes.
Exemple d'interprétation :
Si vous voyez un time_in_nanos élevé pour un 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 sont un goulot d'étranglement, probablement en raison de l'analyse _source ou d'une pagination profonde. Un reduce_time élevé dans les agrégations peut indiquer une charge lourde pendant la phase de fusion finale.
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 discutées précédemment.
Bonnes pratiques générales pour les performances
Au-delà des optimisations spécifiques aux requêtes, plusieurs bonnes pratiques à l'échelle du cluster et de l'index contribuent aux performances globales de la recherche.
1. Mappings d'index optimaux
textvs.keyword: Utiliseztextpour la recherche en texte intégral etkeywordpour la correspondance exacte, le tri et les agrégations. Des types mal adaptés peuvent conduire à des requêtes inefficaces.doc_values: Assurez-vous quedoc_valuessont activés pour les champs que vous avez l'intention de trier ou d'agréger. Ils sont activés par défaut pour la plupart des types de champs qui prennent en charge le tri et les agrégations, tels que les champskeyword, numériques, de date, booléens et IP. Les champstextsimples sont destinés à la recherche en texte intégral ; utilisez un sous-champkeywordlorsque vous avez besoin d'une correspondance exacte ou d'une agrégation.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 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 phrase et de recherches de proximité.
2. Surveiller la santé du cluster et les ressources
- Statut du cluster : Le vert est l'objectif. Le jaune signifie qu'un ou plusieurs shards de réplica ne sont pas assignés ; les recherches peuvent encore fonctionner, mais la résilience est réduite et les performances peuvent en souffrir. Le rouge signifie que des shards primaires sont manquants et que certaines données sont indisponibles.
- Surveillance des ressources : Surveillez régulièrement l'utilisation du CPU, de la RAM, des E/S disque et du réseau sur vos nœuds de données. Les pics dans ces métriques sont souvent corrélés à des requêtes lentes.
- Tas JVM : Gardez un œil sur l'utilisation du tas JVM. Une utilisation élevée peut entraîner des pauses fréquentes du garbage collector, 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. De nombreux petits shards créent des frais généraux. Les shards de quelques dizaines de gigaoctets sont courants, mais la bonne taille dépend du tas, du modèle de requête, des objectifs de récupération et du matériel.
- 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 : Un
refresh_intervalplus bas (1 seconde par défaut) rend les données visibles plus rapidement mais augmente la surcharge d'indexation. Pour les charges de travail lourdes en recherche, envisagez de l'augmenter légèrement (par exemple, 5 à 10 secondes) pour réduire la pression d'actualisation.
Le flux de travail pratique est simple : trouvez la vraie requête lente, profilez-la, réduisez la quantité de données qu'elle touche, et faites correspondre le mapping à la façon dont les utilisateurs recherchent. Si la requête est déjà propre, examinez la disposition des shards, la pression du tas, le cache du système de fichiers et les E/S disque. Elasticsearch est rapide lorsque la conception de l'index, la forme de la requête et les ressources du cluster sont en accord.