Optimiser les requêtes de journal dans Azure Monitor
Azure Monitor Logs utilise Azure Data Explorer pour stocker les données de journal et exécuter les requêtes d’analyse de ces données. Cette fonctionnalité crée, gère et met à jour les clusters Azure Data Explorer, et les optimise pour votre charge de travail d’analyse des journaux. Quand vous exécutez une requête, celle-ci est optimisée et routée vers le cluster Azure Data Explorer approprié qui stocke les données de l’espace de travail.
Azure Monitor Logs et Azure Data Explorer utilisent de nombreux mécanismes d’optimisation automatique des requêtes. Les optimisations automatiques apportent à elles-seules une amélioration significative, mais dans certains cas, vous pouvez améliorer considérablement les performances de vos requêtes. Cet article explique les considérations relatives aux performances et plusieurs techniques permettant de les corriger.
La plupart des techniques sont communes aux requêtes qui s’exécutent directement sur Azure Data Explorer et Azure Monitor Logs. Plusieurs considérations propres à Azure Monitor Logs sont également abordées. Pour plus d’informations sur les conseils d’optimisation propres à Azure Data Explorer, consultez Bonnes pratiques pour les requêtes.
Les requêtes optimisées :
- S’exécutent plus rapidement et réduisent la durée d’exécution globale des requêtes.
- Ont moins de risques d’être limitées ou rejetées.
Accordez une attention particulière aux requêtes utilisées de façon récurrente ou en simultané, comme dans le cadre des tableaux de bord, des alertes, de Azure Logic Apps et de Power BI. Dans ces cas, l’impact d’une requête inefficace est substantiel.
Voici une vidéo de procédure pas à pas détaillée sur l’optimisation des requêtes.
Volet Détails de requête
Après avoir exécuté une requête dans Log Analytics, sélectionnez Détails de la requête dans le coin inférieur droit de l’écran pour ouvrir le volet Détails de la requête. Ce volet affiche les résultats de plusieurs indicateurs de performances pour la requête. Ces indicateurs de performance sont décrits dans la section suivante.
Indicateurs de performances de requête
Les indicateurs de performance de requête suivants sont disponibles pour chaque requête exécutée :
- Total Unité centrale : calcul global utilisé pour traiter la requête sur tous les nœuds de calcul. Il représente le temps utilisé pour le calcul, l’analyse et l’extraction de données.
- Données utilisées pour la requête traitée : données globales consultées pour traiter la requête. Influencé par la taille de la table cible, l’intervalle de temps utilisé, les filtres appliqués et le nombre de colonnes référencées.
- Intervalle de temps de la requête traitée : intervalle entre les données les plus récentes et les plus anciennes consultées pour traiter la requête. Influencé par l’intervalle de temps explicite spécifié pour la requête.
- Âge des données traitées : intervalle entre maintenant et les données les plus consultées pour traiter la requête. Il influence fortement l’efficacité de l’extraction des données.
- Nombre d’espaces de travail : nombre d’espaces de travail utilisés pendant le traitement de la requête suite à une sélection implicite ou explicite.
- Nombre de régions : nombre de régions utilisées pendant le traitement de la requête suite à une sélection implicite ou explicite d’espaces de travail. Les requêtes multirégions sont bien moins efficaces et les indicateurs de performance offrent une couverture partielle.
- Parallélisme : Indique dans quelle mesure le système a pu exécuter cette requête sur plusieurs nœuds. S’applique uniquement aux requêtes qui ont une consommation élevée du processeur. Influencé par l’utilisation de fonctions et d’opérateurs spécifiques.
Total Unité centrale
Processeur de calcul réel consacré au traitement de cette requête sur tous les nœuds de traitement des requêtes. Du fait que la plupart des requêtes sont exécutées sur un grand nombre de nœuds, ce total est généralement nettement supérieur à la durée d’exécution de la requête.
Une requête qui utilise plus de 100 secondes de processeur est considérée comme une requête consommant une quantité excessive de ressources. Une requête qui utilise plus de 1 000 secondes de processeur est considérée comme une requête abusive et peut faire l’objet d’une limitation.
Le temps de traitement des requêtes est consacré aux opérations suivantes :
- Extraction des données : l’extraction d’anciennes données prend plus de temps que l’extraction de données récentes.
- Traitement des données : logique et évaluation des données.
En plus du temps passé dans les nœuds de traitement de la requête, Azure Monitor Logs passe du temps sur les opérations suivantes :
- Authentification de chaque utilisateur et vérification qu’ils sont autorisés à accéder à ces données.
- Localisation du magasin de données.
- Analyse de la requête.
- Allocation des nœuds de traitement de la requête.
Cette durée n’est pas incluse dans le temps processeur total des requêtes.
Filtrage précoce des enregistrements avant d’utiliser des fonctions consommant beaucoup de ressources processeur
Certaines des commandes et des fonctions de requête sont gourmandes en ressources processeur. Ce cas est particulièrement vrai pour les commandes qui analysent des données JSON et XML ou qui extraient des expressions régulières complexes. Une analyse de ce type peut se produire explicitement par le biais des fonctions parse_json() ou parse_xml(), ou implicitement quand il est fait référence à des colonnes dynamiques.
Ces fonctions consomment des ressources processeur proportionnellement au nombre de lignes qu’elles traitent. L’optimisation la plus efficace consiste à ajouter des conditions where
au début de la requête. Cela permet de filtrer le plus grand nombre d’enregistrements possible avant l’exécution de la fonction gourmande en ressources processeur.
Par exemple, les requêtes suivantes produisent les mêmes résultats. La deuxième est toutefois plus efficace, car la condition where avant l’analyse exclut beaucoup d’enregistrements :
//less efficient
SecurityEvent
| extend Details = parse_xml(EventData)
| extend FilePath = tostring(Details.UserData.RuleAndFileData.FilePath)
| extend FileHash = tostring(Details.UserData.RuleAndFileData.FileHash)
| where FileHash != "" and FilePath !startswith "%SYSTEM32" // Problem: irrelevant results are filtered after all processing and parsing is done
| summarize count() by FileHash, FilePath
//more efficient
SecurityEvent
| where EventID == 8002 //Only this event have FileHash
| where EventData !has "%SYSTEM32" //Early removal of unwanted records
| extend Details = parse_xml(EventData)
| extend FilePath = tostring(Details.UserData.RuleAndFileData.FilePath)
| extend FileHash = tostring(Details.UserData.RuleAndFileData.FileHash)
| where FileHash != "" and FilePath !startswith "%SYSTEM32" // exact removal of results. Early filter is not accurate enough
| summarize count() by FileHash, FilePath
| where FileHash != "" // No need to filter out %SYSTEM32 here as it was removed before
Évitez d’utiliser des clauses WHERE évaluées
Les requêtes qui contiennent des clauses where sur une colonne évaluée plutôt que sur des colonnes qui sont physiquement présentes dans le jeu de données perdent en efficacité. Le filtrage sur les colonnes évaluées empêche certaines optimisations système quand de grands ensembles de données sont gérés.
Par exemple, les requêtes suivantes produisent les mêmes résultats. La deuxième est toutefois plus efficace, car la condition where fait référence à une colonne intégrée :
//less efficient
Syslog
| extend Msg = strcat("Syslog: ",SyslogMessage)
| where Msg has "Error"
| count
//more efficient
Syslog
| where SyslogMessage has "Error"
| count
Dans certains cas, la colonne évaluée est créée implicitement par le moteur de traitement des requêtes, car le filtrage ne se fait pas uniquement sur le champ :
//less efficient
SecurityEvent
| where tolower(Process) == "conhost.exe"
| count
//more efficient
SecurityEvent
| where Process =~ "conhost.exe"
| count
Utilisez des commandes d’agrégation et des dimensions efficaces dans la synthèse et la jointure
Certaines commandes d’agrégation comme max(), sum(), count() et avg() ont un faible impact sur le processeur en raison de leur logique. D’autres commandes sont plus complexes, et incluent des heuristiques et des estimations qui leur permettent de s’exécuter efficacement. Par exemple, dcount() utilise l’algorithme HyperLogLog pour fournir une estimation proche d’un nombre distinct de larges ensembles de données sans compter individuellement les valeurs.
Les fonctions percentile font des approximations similaires avec l’algorithme de calcul de percentile du rang le plus proche. Plusieurs commandes incluent des paramètres facultatifs pour réduire leur impact. Par exemple, la fonction makeset() a un paramètre facultatif pour définir la taille maximale du jeu, ce qui affecte de manière significative le processeur et la mémoire.
Les commandes join et summarize peuvent entraîner une utilisation élevée du processeur quand elles traitent un grand ensemble de données. Leur complexité est directement liée au nombre de valeurs possibles, appelé cardinalité, des colonnes qui sont utilisées comme by
dans summarize
ou comme attributs join
. Pour plus d’informations sur les commandes join
et summarize
, et sur leur optimisation, consultez les articles de la documentation et les conseils d’optimisation qui leur sont consacrés.
Par exemple, les requêtes suivantes produisent exactement le même résultat, car CounterPath
est toujours mappé un-à-un à CounterName
et ObjectName
. La seconde est plus efficace, car la dimension d’agrégation est plus petite :
//less efficient
Perf
| summarize avg(CounterValue)
by CounterName, CounterPath, ObjectName
//make the group expression more compact improve the performance
Perf
| summarize avg(CounterValue), any(CounterName), any(ObjectName)
by CounterPath
La consommation du processeur peut également être impactée par les conditions where
ou par les colonnes étendues nécessitant un calcul intensif. Toutes les comparaisons de chaînes triviales telles que equal == et startswith ont à peu près le même impact sur le processeur. Les correspondances de texte avancées ont plus d’impact. Plus précisément, l’opérateur has est plus efficace que l’opérateur contains. En raison des techniques de traitement des chaînes, il est plus efficace de rechercher des chaînes contenant plus de quatre caractères que des chaînes courtes.
Par exemple, les requêtes suivantes produisent des résultats similaires, en fonction de la stratégie de nommage Computer
. Mais la deuxième est plus efficace :
//less efficient – due to filter based on contains
Heartbeat
| where Computer contains "Production"
| summarize count() by ComputerIP
//less efficient – due to filter based on extend
Heartbeat
| extend MyComputer = Computer
| where MyComputer startswith "Production"
| summarize count() by ComputerIP
//more efficient
Heartbeat
| where Computer startswith "Production"
| summarize count() by ComputerIP
Notes
Cet indicateur présente uniquement le processeur du cluster immédiat. Dans une requête multirégion, il ne représente qu’une seule des régions. Dans une requête couvrant plusieurs espaces de travail, il n’inclut pas toujours tous les espaces de travail.
Évitez l’analyse XML et JSON complète lorsque l’analyse de chaîne fonctionne
L’analyse complète d’un objet XML ou JSON peut consommer beaucoup de ressources processeur et mémoire. Dans de nombreux cas, lorsqu’un seul ou deux paramètres sont nécessaires et que les objets XML ou JSON sont simples, il est plus facile de les analyser en tant que chaînes. Pour cela, utilisez l’opérateur parse ou d’autres techniques d’analyse de texte. L’amélioration des performances est d’autant plus visible que le nombre d’enregistrements dans l’objet XML ou JSON augmente. Cela est essentiel lorsque le nombre d’enregistrements atteint des dizaines de millions.
Par exemple, la requête suivante retourne exactement les mêmes résultats que les requêtes précédentes sans effectuer d’analyse XML complète. La requête fait des hypothèses sur la structure du fichier XML ; par exemple, elle suppose que l’élément FilePath
vient après FileHash
et qu’aucun d’eux n’a d’attributs :
//even more efficient
SecurityEvent
| where EventID == 8002 //Only this event have FileHash
| where EventData !has "%SYSTEM32" //Early removal of unwanted records
| parse EventData with * "<FilePath>" FilePath "</FilePath>" * "<FileHash>" FileHash "</FileHash>" *
| summarize count() by FileHash, FilePath
| where FileHash != "" // No need to filter out %SYSTEM32 here as it was removed before
Données utilisées pour la requête traitée
Un facteur critique dans le traitement de la requête est le volume de données analysé et utilisé. Azure Data Explorer utilise des optimisations agressives qui réduisent considérablement le volume de données par rapport aux autres plateformes de données. Toutefois, il y a des facteurs critiques dans la requête qui peuvent avoir une incidence sur le volume de données utilisé.
Une requête qui traite plus de 2 000 Ko de données est considérée comme une requête consommant une quantité excessive de ressources. Une requête qui traite plus de 20 000 Ko de données est considérée comme une requête abusive et peut faire l’objet d’une limitation.
Dans Journaux Azure Monitor, la colonne TimeGenerated
est utilisée comme mode d’indexation des données. En limitant les valeurs TimeGenerated
à une plage aussi étroite que possible, vous améliorez les performances des requêtes. La plage étroite limite considérablement la quantité de données à traiter.
Évitez l’utilisation inutile des opérateurs de recherche et d’union
Un autre facteur d’augmentation des données traitées est l’utilisation d’un grand nombre de tables. Ce scénario se produit généralement quand les commandes search *
et union *
sont utilisées. Ces commandes forcent le système à évaluer et à analyser les données de toutes les tables de l’espace de travail. Dans certains cas, l’espace de travail peut comporter des centaines de tables. Évitez autant que possible l’utilisation de la commande search *
ou d’une recherche sans la limiter à une table spécifique.
Par exemple, les requêtes suivantes produisent exactement le même résultat, mais la dernière est la plus efficace :
// This version scans all tables though only Perf has this kind of data
search "Processor Time"
| summarize count(), avg(CounterValue) by Computer
// This version scans all strings in Perf tables – much more efficient
Perf
| search "Processor Time"
| summarize count(), avg(CounterValue) by Computer
// This is the most efficient version
Perf
| where CounterName == "% Processor Time"
| summarize count(), avg(CounterValue) by Computer
Ajoutez des filtres précoces à la requête
Une autre méthode pour réduire le volume de données consiste à placer les conditions where tôt dans la requête. La plateforme Azure Data Explorer inclut un cache qui lui permet de savoir quelles partitions contiennent des données pertinentes pour une condition where
spécifique. Par exemple, si une requête contient where EventID == 4624
, elle distribue la requête uniquement aux nœuds qui gèrent des partitions avec des événements correspondants.
Par exemple, les requêtes suivantes produisent exactement le même résultat, mais la deuxième est la plus efficace :
//less efficient
SecurityEvent
| summarize LoginSessions = dcount(LogonGuid) by Account
//more efficient
SecurityEvent
| where EventID == 4624 //Logon GUID is relevant only for logon event
| summarize LoginSessions = dcount(LogonGuid) by Account
Ne faites pas d’analyses multiples des mêmes données sources en utilisant les fonctions d’agrégation conditionnelles et la fonction materialize
Lorsqu’une requête comporte plusieurs sous-requêtes fusionnées au moyen d’opérateurs de jointure ou d’union, chaque sous-requête analyse l’intégralité de la source séparément. Ensuite, elle fusionne les résultats. Cette action calcule le nombre de fois où les données sont analysées, un facteur critique dans les jeux de données volumineux.
Pour éviter ce scénario, vous pouvez utiliser les fonctions d’agrégation conditionnelles. La plupart des fonctions d’agrégation utilisées dans un opérateur summary ont une version conditionnée que vous pouvez utiliser pour un seul opérateur summarize avec plusieurs conditions.
Par exemple, les requêtes suivantes affichent le nombre d’événements de connexion et le nombre d’événements d’exécution de processus pour chaque compte. Elles retournent les mêmes résultats, mais la première requête analyse les données deux fois. La deuxième requête les analyse une seule fois :
//Scans the SecurityEvent table twice and perform expensive join
SecurityEvent
| where EventID == 4624 //Login event
| summarize LoginCount = count() by Account
| join
(
SecurityEvent
| where EventID == 4688 //Process execution event
| summarize ExecutionCount = count(), ExecutedProcesses = make_set(Process) by Account
) on Account
//Scan only once with no join
SecurityEvent
| where EventID == 4624 or EventID == 4688 //early filter
| summarize LoginCount = countif(EventID == 4624), ExecutionCount = countif(EventID == 4688), ExecutedProcesses = make_set_if(Process,EventID == 4688) by Account
Un autre cas où les sous-requêtes ne sont pas nécessaires est le préfiltrage pour un opérateur parse, qui garantit que seuls les enregistrements correspondant à un modèle spécifique sont traités. Elles ne sont pas nécessaires, car l’opérateur parse et d’autres opérateurs similaires retournent des résultats vides en l’absence de correspondance avec le modèle. Les deux requêtes suivantes retournent exactement les mêmes résultats, mais la deuxième requête analyse les données une seule fois. Dans la deuxième requête, chaque commande parse est pertinente uniquement pour ses événements. L’opérateur extend
montre ensuite comment faire référence à une situation de données vides :
//Scan SecurityEvent table twice
union(
SecurityEvent
| where EventID == 8002
| parse EventData with * "<FilePath>" FilePath "</FilePath>" * "<FileHash>" FileHash "</FileHash>" *
| distinct FilePath
),(
SecurityEvent
| where EventID == 4799
| parse EventData with * "CallerProcessName\">" CallerProcessName1 "</Data>" *
| distinct CallerProcessName1
)
//Single scan of the SecurityEvent table
SecurityEvent
| where EventID == 8002 or EventID == 4799
| parse EventData with * "<FilePath>" FilePath "</FilePath>" * "<FileHash>" FileHash "</FileHash>" * //Relevant only for event 8002
| parse EventData with * "CallerProcessName\">" CallerProcessName1 "</Data>" * //Relevant only for event 4799
| extend FilePath = iif(isempty(CallerProcessName1),FilePath,"")
| distinct FilePath, CallerProcessName1
Lorsque la requête précédente n’autorise pas l’utilisation de sous-requêtes, une autre technique consiste à indiquer au moteur de requête qu’une seule source de données est utilisée dans chacune d’elles au moyen de la fonction materialize(). Cette technique est utile quand les données sources proviennent d’une fonction qui est utilisée plusieurs fois dans la requête. La fonction Materialize
est efficace lorsque la sortie de la sous-requête est bien plus petite que l’entrée. Le moteur de requête mettra en cache et réutilisera la sortie dans toutes les occurrences.
Réduisez le nombre de colonnes récupérées
Du fait qu’Azure Data Explorer est un magasin de données en colonnes, les colonnes sont récupérées indépendamment les unes des autres. Le nombre de colonnes récupérées influence directement le volume de données global. Vous ne devez inclure dans la sortie que les colonnes nécessaires ; pour ce faire, résumez les résultats ou projetez les colonnes concernées.
Azure Data Explorer dispose de plusieurs optimisations pour réduire le nombre de colonnes récupérées. S’il détermine qu’une colonne n’est pas nécessaire, notamment si elle n’est pas référencée dans la commande summarize, il ne la récupère pas.
Par exemple, il est possible que la deuxième requête traite trois fois plus de données, car elle doit extraire non pas une colonne, mais trois colonnes :
//Less columns --> Less data
SecurityEvent
| summarize count() by Computer
//More columns --> More data
SecurityEvent
| summarize count(), dcount(EventID), avg(Level) by Computer
Intervalle de temps de la requête traitée
Tous les journaux dans Journaux Azure Monitor sont partitionnés en fonction de la colonne TimeGenerated
. Le nombre de partitions accessibles est directement lié à l’intervalle de temps. La réduction de l’intervalle de temps est la méthode la plus efficace pour garantir l’exécution d’une requête d’invite.
Une requête qui couvre plus de 15 jours est considérée comme une requête consommant une quantité excessive de ressources. Une requête qui porte sur un intervalle de temps supérieur à 90 jours est considérée comme une requête abusive et peut faire l’objet d’une limitation.
Vous pouvez définir la période en utilisant le sélecteur d’intervalle de temps dans l’écran Log Analytics, comme cela est décrit dans Étendue de requête de journal et intervalle de temps dans la fonctionnalité Log Analytics d’Azure Monitor. Cette méthode est recommandée, car l’intervalle de temps sélectionné est passé au back-end via les métadonnées de requête.
Une autre méthode consiste à inclure explicitement une condition where sur TimeGenerated
dans la requête. Utilisez cette méthode, car elle garantit que l’intervalle de temps est fixe, même quand la requête est exécutée à partir d’une interface différente.
Vérifiez que toutes les parties de la requête ont des filtres TimeGenerated
. Quand une requête a des sous-requêtes qui extraient des données de différentes tables ou de la même table, chaque sous-requête doit inclure sa propre condition where.
Vérifiez que toutes les sous-requêtes ont le filtre TimeGenerated
Par exemple, dans la requête suivante, la table Perf
est analysée uniquement pour le dernier jour. La table Heartbeat
est analysée pour l’ensemble de son historique, soit une période pouvant aller jusqu’à deux ans :
Perf
| where TimeGenerated > ago(1d)
| summarize avg(CounterValue) by Computer, CounterName
| join kind=leftouter (
Heartbeat
//No time span filter in this part of the query
| summarize IPs = makeset(ComputerIP, 10) by Computer
) on Computer
Ce type d’erreur se produit généralement quand arg_max() est utilisé pour rechercher l’occurrence la plus récente. Par exemple :
Perf
| where TimeGenerated > ago(1d)
| summarize avg(CounterValue) by Computer, CounterName
| join kind=leftouter (
Heartbeat
//No time span filter in this part of the query
| summarize arg_max(TimeGenerated, *), min(TimeGenerated)
by Computer
) on Computer
Vous pouvez facilement corriger cela en ajoutant un filtre temporel dans la requête interne :
Perf
| where TimeGenerated > ago(1d)
| summarize avg(CounterValue) by Computer, CounterName
| join kind=leftouter (
Heartbeat
| where TimeGenerated > ago(1d) //filter for this part
| summarize arg_max(TimeGenerated, *), min(TimeGenerated)
by Computer
) on Computer
Ce problème peut également se produire quand vous effectuez un filtrage par intervalle de temps juste après une opération union sur plusieurs tables. Lorsque vous effectuez l’union, chaque sous-requête doit être délimitée. Vous pouvez utiliser une instruction let pour garantir la cohérence de l’étendue.
Par exemple, la requête suivante analyse toutes les données des tables Heartbeat
et Perf
, et pas seulement celles du jour passé :
Heartbeat
| summarize arg_min(TimeGenerated,*) by Computer
| union (
Perf
| summarize arg_min(TimeGenerated,*) by Computer)
| where TimeGenerated > ago(1d)
| summarize min(TimeGenerated) by Computer
Pour corriger la requête :
let MinTime = ago(1d);
Heartbeat
| where TimeGenerated > MinTime
| summarize arg_min(TimeGenerated,*) by Computer
| union (
Perf
| where TimeGenerated > MinTime
| summarize arg_min(TimeGenerated,*) by Computer)
| summarize min(TimeGenerated) by Computer
Limitations de mesure de l’intervalle de temps
La mesure est toujours supérieure à la période spécifiée. Par exemple, si le filtre appliqué à la requête est de 7 jours, le système peut analyser 7,5 ou 8,1 jours. Cette variance est due au fait que le système partitionne les données en blocs de taille variable. Pour s’assurer que tous les enregistrements pertinents seront analysés, le système analyse la partition entière. Ce processus peut durer plusieurs heures, voire plus d’une journée.
Il existe plusieurs cas où le système ne peut pas fournir une mesure exacte de l’intervalle de temps. Cette situation arrive généralement lorsque la période d’une requête est inférieure à un jour ou que les requêtes s’étendent sur plusieurs espaces de travail.
Important
Cet indicateur présente uniquement les données traitées dans le cluster immédiat. Dans une requête multirégion, il ne représente qu’une seule des régions. Dans une requête couvrant plusieurs espaces de travail, il n’inclut pas toujours tous les espaces de travail.
Âge des données traitées
Azure Data Explorer utilise plusieurs niveaux de stockage : en mémoire, sur des disques SSD locaux et dans des objets Blob Azure (beaucoup plus lents). Plus les données sont récentes, plus il est probable qu’elles soient stockées dans un niveau plus performant avec une latence plus faible, ce qui réduit la durée de la requête et la consommation des ressources processeur. Outre pour les données elles-mêmes, le système dispose d’un cache pour les métadonnées. Plus les données sont anciennes, moins il est probable que leurs métadonnées se trouvent dans un cache.
Une requête qui traite des données datant de plus de 14 jours est considérée comme une requête consommant une quantité excessive de ressources.
Certaines requêtes nécessitent l’utilisation d’anciennes données, mais il peut aussi arriver que d’anciennes données soient utilisées par erreur. Ce scénario se produit quand les requêtes sont exécutées sans que soit fourni d’intervalle de temps dans leurs métadonnées et quand certaines références de table n’incluent pas de filtre sur la colonne TimeGenerated
. Dans ces deux cas, le système analyse toutes les données qui sont stockées dans la table. Lorsque la durée de conservation des données est longue, elle peut couvrir de longs intervalles de temps. Par conséquent, les données qui sont aussi anciennes que la période de conservation des données sont analysées.
Voici quelques exemples possibles :
- Le fait de ne pas définir l’intervalle de temps dans Log Analytics avec une sous-requête qui n’est pas limitée. Reportez-vous à l’exemple précédent.
- Utilisation de l’API sans les paramètres d’intervalle de temps facultatifs.
- Utilisation d’un client qui ne force pas un intervalle de temps, comme le connecteur Power BI.
Consultez les exemples et les notes de la section précédente, car ils s’appliquent également dans ce cas.
Nombre de régions
Il y a des situations où une même requête peut être exécutée dans plusieurs régions. Par exemple :
- Quand plusieurs espaces de travail sont listés explicitement et qu’ils se trouvent dans des régions différentes.
- Quand une requête dont l’étendue est limitée à des ressources récupère des données et que les données sont stockées dans plusieurs espaces de travail qui se trouvent dans des régions différentes.
L’exécution interrégionale d’une requête nécessite que le système sérialise et transfère dans le back-end de grands blocs de données intermédiaires qui sont généralement beaucoup plus volumineux que les résultats finaux de la requête. Elle limite également la capacité du système à effectuer des optimisations et des heuristiques, ainsi qu’à utiliser des caches.
S’il n’y a pas vraiment de raison d’analyser toutes ces régions, ajustez l’étendue afin qu’elle couvre moins de régions. Si l’étendue des ressources est réduite, mais que de nombreuses régions sont toujours utilisées, cela peut provenir d’une configuration inappropriée. Par exemple, les journaux d’audit et les paramètres de diagnostic sont peut-être envoyés à différents espaces de travail dans plusieurs régions ou il y a peut-être plusieurs configurations de paramètres de diagnostic.
Une requête qui s’étend sur plus de trois régions est considérée comme une requête consommant une quantité excessive de ressources. Une requête qui s’étend sur plus de six régions est considérée comme une requête abusive et peut faire l’objet d’une limitation.
Important
Lorsqu’une requête est exécutée dans plusieurs régions à la fois, les mesures du processeur et des données ne sont pas exactes, car elles représentent les mesures d’une seule de ces régions.
Nombre d’espaces de travail
Les espaces de travail sont des conteneurs logiques qui permettent de séparer et d’administrer des données de journaux. Le back-end optimise le positionnement des espaces de travail sur les clusters physiques au sein de la région sélectionnée.
L’utilisation de plusieurs espaces de travail peut résulter d’instances dans les cas suivants :
- Plusieurs espaces de travail sont listés explicitement.
- Une requête dont l’étendue est limitée à des ressources récupère les données, et les données sont stockées dans plusieurs espaces de travail.
L’exécution interrégionale et intercluster d’une requête nécessite que le système sérialise et transfère dans le back-end de grands blocs de données intermédiaires qui sont généralement beaucoup plus volumineux que les résultats finaux de la requête. Elle limite également la capacité du système à effectuer des optimisations et des heuristiques, ainsi qu’à utiliser des caches.
Une requête qui s’étend sur plus de cinq espaces de travail est considérée comme une requête consommant une quantité excessive de ressources. Les requêtes ne peuvent pas s’étendre sur plus de 100 espaces de travail.
Important
- Dans certains scénarios avec plusieurs espaces de travail, les mesures du processeur et des données ne sont pas exactes, car elles représentent les mesures de seulement quelques-uns de ces espaces de travail.
- Les requêtes entre espaces de travail ayant un identificateur explicite (ID d’espace de travail ou ID de Azure Ressource consomment moins de ressources et sont plus performantes.
Parallélisme
Azure Monitor Logs utilise de grands clusters Data Explorer Azure pour exécuter les requêtes. Ces clusters variables en taille peuvent potentiellement atteindre des dizaines de nœuds de calcul. Le système met automatiquement à l’échelle les clusters en fonction de la capacité et de la logique de positionnement de l’espace de travail.
Pour qu’une requête s’exécute efficacement, elle est partitionnée et distribuée aux nœuds de calcul en fonction des données nécessaires à son traitement. Dans certaines situations, le système ne peut pas effectuer cette étape de manière efficace, ce qui peut allonger considérablement la durée de la requête.
Les comportements de requête qui peuvent réduire le parallélisme sont les suivants :
- Utilisation de fonctions de sérialisation et de fenêtre, telles que les fonctions next(), prev() et row ainsi que l’opérateur serialize. Les fonctions de série chronologique et d’analytique utilisateur peuvent être utilisées dans certains de ces cas. Une sérialisation inefficace peut également se produire si les opérateurs suivants ne sont pas utilisés à la fin de la requête : range, sort, order, top, top-hitters et getschema.
- L’utilisation de la fonction d’agrégation dcount() force le système à avoir une copie centrale des valeurs distinctes. Quand le volume de données est élevé, envisagez d’utiliser les paramètres facultatifs de la fonction
dcount
pour réduire la précision. - Dans de nombreux cas, l’opérateur join diminue le parallélisme global. Envisagez une opération
shuffle join
comme alternative quand les performances sont problématiques. - Dans les requêtes dont l’étendue est limitée à des ressources, les vérifications du contrôle d’accès en fonction du rôle (RBAC) Kubernetes ou Azure RBAC en préexécution peuvent traîner en longueur en présence d’un grand nombre d’attributions de rôles Azure. Cette situation peut augmenter la durée des vérifications et par là-même, réduire le parallélisme. Par exemple, une requête peut être exécutée sur un abonnement qui comprend des milliers de ressources, et où de nombreuses attributions de rôles sont définies au niveau de chaque ressource, et non sur l’abonnement ou sur le groupe de ressources.
- Si une requête traite de petits blocs de données, son parallélisme est faible, car le système ne la répartit pas sur beaucoup nœuds de calcul.
Étapes suivantes
Documentation de référence sur le langage de requête Kusto (KQL)