Teilen über


Optimieren von Protokollabfragen in Azure Monitor

Azure Monitor-Protokolle verwendet Azure Data Explorer, um Protokolldaten zu speichern und Abfragen zum Analysieren dieser Daten auszuführen. Die Azure Data Explorer-Cluster werden automatisch erstellt, verwaltet und gepflegt sowie für Ihre Protokollanalyse-Workload optimiert. Wenn Sie eine Abfrage ausführen, wird diese optimiert und an den entsprechenden Azure Data Explorer-Cluster weitergeleitet, der die Arbeitsbereichsdaten speichert.

Azure Monitor-Protokolle und Azure Data Explorer nutzen eine Vielzahl automatischer Mechanismen zur Abfrageoptimierung. Automatische Optimierungen bieten bereits eine deutliche Steigerung, in einigen Fällen können Sie aber die Abfrageleistung erheblich verbessern. In diesem Artikel werden Leistungsaspekte sowie verschiedene Verfahren zur Behebung von Leistungsproblemen erläutert.

Die meisten Techniken sind für Abfragen, die direkt in Azure Data Explorer und Azure Monitor-Protokolle ausgeführt werden, gemeinsam anwendbar. Darüber hinaus werden auch mehrere spezielle Überlegungen für Azure Monitor-Protokolle erläutert. Weitere Tipps zur Optimierung von Azure Data Explorer finden Sie unter Best Practices für Abfragen.

Optimierte Abfragen bieten folgende Vorteile:

  • Abfragen werden schneller ausgeführt, sodass sich deren Gesamtausführungsdauer verringert.
  • Die Wahrscheinlichkeit einer Drosselung oder Ablehnung ist geringer.

Beachten Sie insbesondere Abfragen, die für die wiederholte und gleichzeitige Nutzung verwendet werden, wie z. B. Dashboards, Warnungen, Azure Logic Apps und Power BI. Die Auswirkung einer ineffektiven Abfrage ist in diesen Fällen beträchtlich.

Dies ist ein ausführliches Video mit einer exemplarischen Vorgehensweise zum Optimieren von Abfragen.

Bereich „Abfragedetails“

Nachdem Sie eine Abfrage in Log Analytics ausgeführt haben, wählen Sie unten rechts im Bildschirm die Option Abfragedetails aus, um den Bereich Abfragedetails zu öffnen. Dieser Bereich zeigt die Ergebnisse mehrerer Leistungsindikatoren für die Abfrage. Diese Leistungsindikatoren werden im folgenden Abschnitt beschrieben.

Screenshot: Bereich „Abfragedetails“ in Azure Monitor Log Analytics.

Abfrageleistungsindikatoren

Die folgenden Abfrageleistungsindikatoren sind für jede ausgeführte Abfrage verfügbar:

  • CPU gesamt: Gesamtcomputezeit, die zum Verarbeiten der Abfrage auf allen Computeknoten benötigt wurde. Dies stellt die Zeit dar, die für die Berechnung, die Analyse und den Datenabruf aufgewendet wurde.
  • Für die verarbeitete Abfrage verwendete Daten: Gesamtdaten, auf die zum Verarbeiten der Abfrage zugegriffen wurde. Beeinflusst durch die Größe der Zieltabelle, die verwendete Zeitspanne, die angewendeten Filter und die Anzahl der referenzierten Spalten.
  • Zeitspanne der verarbeiteten Abfrage: Der Abstand zwischen den neuesten und den ältesten Daten, auf die zum Verarbeiten der Abfrage zugegriffen wurde. Beeinflusst durch den für die Abfrage angegebenen expliziten Zeitbereich.
  • Alter der verarbeiteten Daten: Der Abstand zwischen dem jetzigen Zeitpunkt und den ältesten Daten, auf die zum Verarbeiten der Abfrage zugegriffen wurde. Dies wirkt sich stark auf die Effizienz beim Datenabruf aus.
  • Anzahl von Arbeitsbereichen: Die Anzahl der Arbeitsbereiche, auf die im Rahmen der Abfrageverarbeitung basierend auf der impliziten oder expliziten Auswahl zugegriffen wurde.
  • Anzahl von Regionen: Die Anzahl der Regionen, auf die während der Abfrageverarbeitung basierend auf der impliziten oder expliziten Auswahl von Arbeitsbereichen zugegriffen wurde. Abfragen in mehreren Regionen sind wesentlich weniger effizient, und Leistungsindikatoren bieten nur teilweise Abdeckung.
  • Parallelität: Gibt an, inwieweit das System diese Abfrage auf mehreren Knoten ausführen konnte. Nur für Abfragen mit hoher CPU-Auslastung relevant. Beeinflusst durch die Verwendung bestimmter Funktionen und Operatoren.

CPU gesamt

Die tatsächliche CPU-Computezeit, die für die Verarbeitung dieser Abfrage auf allen Abfrageverarbeitungsknoten aufgewendet wurde. Da die meisten Abfragen auf einer großen Anzahl von Knoten ausgeführt werden, ist dieser Summe in der Regel viel höher als die eigentliche Ausführungsdauer der Abfrage.

Eine Abfrage, die mehr als 100 CPU-Sekunden beansprucht, wird als Abfrage mit übermäßigem Ressourcenverbrauch betrachtet. Eine Abfrage, die mehr als 1.000 CPU-Sekunden beansprucht, wird als missbräuchliche Abfrage betrachtet und ggf. gedrosselt.

Die Abfrageverarbeitungszeit beinhaltet Folgendes:

  • Datenabruf: Das Abrufen alter Daten beansprucht mehr Zeit als das Abrufen neuer Daten.
  • Datenverarbeitung: Logik und Auswertung der Daten.

Zusätzlich zur Zeit, die für die Abfrageverarbeitungsknoten aufgewendet wird, beansprucht Azure Monitor-Protokolle Zeit für:

  • Authentifizieren des Benutzers und Überprüfen von dessen Zugriffsberechtigung auf diese Daten.
  • Suchen des Datenspeichers.
  • Analysieren der Abfrage.
  • Zuordnen der Abfrageverarbeitungsknoten.

Diese Zeit ist in der CPU-Gesamtzeit der Abfrage nicht enthalten.

Frühzeitiges Filtern von Datensätzen vor der Verwendung von CPU-intensiven Funktionen

Einige der Abfragebefehle und -funktionen bewirken eine hohe CPU-Auslastung. Dies gilt insbesondere für Befehle, mit denen JSON und XML analysiert oder komplexe reguläre Ausdrücke extrahiert werden. Eine solche Analyse kann explizit über die Funktionen parse_json() oder parse_xml() erfolgen oder auch implizit, wenn auf dynamische Spalten verwiesen wird.

Diese Funktionen bewirken eine CPU-Auslastung proportional zur Anzahl der verarbeiteten Zeilen. Die effizienteste Optimierung besteht darin, frühzeitig where-Bedingungen in der Abfrage hinzuzufügen. Auf diese Weise können sie möglichst viele Datensätze herausfiltern, bevor die CPU-intensive Funktion ausgeführt wird.

Die folgenden Abfragen ergeben beispielsweise exakt das gleiche Ergebnis. Die zweite ist jedoch am effizientesten, weil die where-Bedingung vor dem Analysieren viele Datensätze ausschließt:

//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

Vermeiden von ausgewerteten WHERE-Klauseln

Abfragen, die where-Klauseln für eine ausgewertete Spalte enthalten und nicht für Spalten, die physisch im Dataset vorhanden sind, verlieren an Effizienz. Durch das Filtern nach ausgewerteten Spalten werden einige Systemoptimierungen beim Verarbeiten umfangreicher Datenmengen verhindert.

Die folgenden Abfragen ergeben beispielsweise exakt das gleiche Ergebnis. Die zweite ist jedoch effizienter, weil die where-Bedingung auf eine integrierte Spalte verweist:

//less efficient
Syslog
| extend Msg = strcat("Syslog: ",SyslogMessage)
| where  Msg  has "Error"
| count 
//more efficient
Syslog
| where  SyslogMessage  has "Error"
| count 

In einigen Fällen wird die ausgewertete Spalte implizit von der Abfrageverarbeitungs-Engine erstellt, da der Filter nicht nur auf das Feld angewendet wird:

//less efficient
SecurityEvent
| where tolower(Process) == "conhost.exe"
| count 
//more efficient
SecurityEvent
| where Process =~ "conhost.exe"
| count 

Verwenden effektiver Aggregationsbefehle und -dimensionen bei join- und summarize-Operatoren

Einige Aggregationsbefehle wie max(), sum(), count() und avg() weisen aufgrund ihrer Logik geringe Auswirkungen auf die CPU auf. Andere Befehle sind komplexer und umfassen Heuristiken und Schätzungen, die eine effiziente Ausführung ermöglichen. So verwendet dcount() beispielsweise den HyperLogLog-Algorithmus, um eine nahe Schätzung für mehrere umfangreiche Datensätze zu ermöglichen, ohne die einzelne Werte tatsächlich zu zählen.

Die Perzentilfunktionen führen ähnliche Näherungen mithilfe des Algorithmus für den nächstgelegenen Prozentrang aus. Einige der Befehle enthalten optionale Parameter, um ihre Auswirkung zu verringern. Beispielsweise verfügt die makeset()-Funktion über einen optionalen Parameter zum Definieren der maximalen festgelegten Größe, was sich erheblich auf CPU und Arbeitsspeicher auswirkt.

join- und summarize-Befehle können zu einer hohen CPU-Auslastung führen, wenn mit ihnen eine große Datenmenge verarbeitet wird. Ihre Komplexität steht in direktem Bezug zur Anzahl möglicher Werte (Kardinalität genannt) der Spalten, die als by in summarize- oder als join-Attribute verwendet werden. Eine Erläuterung und Informationen zur Optimierung von join und summarize finden Sie in den Artikeln und Optimierungstipps in der jeweiligen Dokumentation.

Beispielsweise wird mit den folgenden Abfragen genau das gleiche Ergebnis erzielt, da CounterPath immer CounterName und ObjectName eins zu eins zugeordnet wird. Die zweite Abfrage ist effizienter, da die Aggregationsdimension kleiner ist:

//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

Die CPU-Auslastung kann auch durch where-Bedingungen oder erweiterte Spalten beeinflusst werden, die intensive Berechnungen erfordern. Alle einfachen Zeichenfolgenvergleiche wie equal == und startswith haben ungefähr dieselben CPU-Auswirkungen. Erweiterte Textübereinstimmungen wirken sich stärker aus. Insbesondere der has-Operator ist effizienter als der contains-Operator. Aufgrund der Verfahren zur Zeichenfolgenverarbeitung ist es effizienter, nach Zeichenfolgen, die länger als vier Zeichen sind, als nach kurzen Zeichenfolgen zu suchen.

Beispielsweise werden mit den folgenden Abfragen je nach Benennungsrichtlinie für Computer ähnliche Ergebnisse erzielt. Die zweite ist jedoch effizienter:

//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 

Hinweis

Dieser Indikator zeigt nur die CPU-Auslastung des unmittelbaren Clusters an. Bei einer Abfrage in mehreren Regionen würde nur eine der Regionen dargestellt. Bei einer Abfrage in mehreren Arbeitsbereichen sind möglicherweise nicht alle Arbeitsbereiche enthalten.

Vermeiden der vollständigen XML- und JSON-Analyse bei funktionierender Zeichenfolgenanalyse

Die vollständige Analyse eines XML- oder JSON-Objekts kann viele CPU- und Speicherressourcen beanspruchen. In vielen Fällen, in denen nur ein oder zwei Parameter erforderlich und die XML- oder JSON-Objekte einfach sind, ist es einfacher, diese als Zeichenfolgen zu analysieren. Verwenden Sie den parse-Operator oder andere Textanalyseverfahren. Die Leistungssteigerung wird noch deutlicher, da die Anzahl der Datensätze im XML- oder JSON-Objekt zunimmt. Dies ist von entscheidender Bedeutung, wenn die Anzahl der Datensätze zehn Millionen erreicht.

Beispielsweise gibt die folgende Abfrage genau die gleichen Ergebnisse zurück wie die obigen Abfragen, ohne dabei eine vollständige XML-Analyse durchzuführen. Die Abfrage stellt Annahmen zur XML-Dateistruktur auf, z. B. dass das FilePath-Element nach FileHash kommt und keines der Elemente über Attribute verfügt:

//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

Für die verarbeitete Abfrage verwendete Daten

Ein kritischer Faktor bei der Verarbeitung der Abfrage ist die Menge an Daten, die für die Abfrageverarbeitung überprüft und verwendet werden. Azure Data Explorer verwendet aggressive Optimierungen, mit denen das Datenvolumen im Vergleich zu anderen Datenplattformen drastisch reduziert wird. Dennoch gibt es kritische Faktoren in der Abfrage, die sich auf das verwendete Datenvolumen auswirken können.

Eine Abfrage, die mehr als 2.000 KB Daten verarbeitet, wird als Abfrage mit übermäßigem Ressourcenverbrauch betrachtet. Eine Abfrage, die mehr als 20.000 KB Daten verarbeitet, wird als missbräuchliche Abfrage betrachtet und ggf. gedrosselt.

In Azure Monitor-Protokolle wird die Spalte TimeGenerated als Methode zum Indizieren der Daten verwendet. Wenn Sie die TimeGenerated-Werte auf einen möglichst engen Bereich beschränken, wird die Abfrageleistung verbessert. Der enge Bereich schränkt die Menge der zu verarbeitenden Daten erheblich ein.

Vermeiden der unnötigen Verwendung von search- und union-Operatoren

Ein weiterer Faktor, der die Menge der verarbeiteten Daten erhöht, ist die Verwendung einer Vielzahl von Tabellen. Dieses Szenario tritt normalerweise bei den Befehlen search * und union * auf. Durch diese Befehle wird das System gezwungen, Daten aus allen Tabellen im Arbeitsbereich zu bewerten und zu überprüfen. In einigen Fällen können Hunderte von Tabellen im Arbeitsbereich vorhanden sein. Vermeiden Sie die Verwendung von search * oder einer anderen Suche ohne Beschränkung auf eine bestimmte Tabelle.

Beispielsweise wird mit den folgenden Abfragen genau das gleiche Ergebnis erzielt, allerdings ist die letzte am effizientesten:

// 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

Hinzufügen früher Filter zur Abfrage

Eine andere Methode, um das Datenvolumen zu verringern, ist eine frühzeitige Verwendung von where-Bedingungen in der Abfrage. Die Azure Data Explorer-Plattform umfasst einen Cache, durch den angegeben wird, welche Partitionen Daten enthalten, die für eine bestimmte where-Bedingung relevant sind. Wenn eine Abfrage z. B. where EventID == 4624 enthält, wird die Abfrage nur an Knoten verteilt, die Partitionen mit übereinstimmenden Ereignissen verarbeiten.

Mit den folgenden Beispielabfragen wird genau das gleiche Ergebnis erzielt, doch ist die zweite Abfrage effizienter:

//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

Vermeiden mehrerer Überprüfungen derselben Quelldaten mithilfe bedingter Aggregationsfunktionen und der materialize-Funktion

Wenn eine Abfrage mehrere Unterabfragen enthält, die mithilfe von join- oder union-Operatoren zusammengeführt werden, überprüft jede Unterabfrage die gesamte Quelle separat. Anschließend werden die Ergebnisse zusammengeführt. Dadurch vervielfacht sich die Anzahl der Datenüberprüfungen – ein kritischer Faktor bei umfangreichen Datasets.

Eine Technik, um dieses Szenario zu vermeiden, ist die Verwendung von bedingten Aggregationsfunktionen. Die meisten Aggregationsfunktionen, die in einem summarischen Operator verwendet werden, verfügen über eine bedingte Version, bei der Sie einen einzelnen summarize-Operator mit mehreren Bedingungen verwenden können.

Die folgenden Abfragen zeigen beispielsweise die Anzahl der Anmeldeereignisse und die Anzahl der Prozessausführungsereignisse für jedes Konto. Sie geben dieselben Ergebnisse zurück, bei der ersten Abfrage werden die Daten jedoch zweimal überprüft. Die zweite Abfrage überprüft sie nur einmal:

//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

Ein weiterer Fall, in dem Unterabfragen unnötig sind, ist das Vorfiltern für den parse-Operator, um sicherzustellen, dass nur Datensätze verarbeitet werden, die einem bestimmten Muster entsprechen. Dies ist unnötig, weil der parse-Operator und andere ähnliche Operatoren leere Ergebnisse zurückgeben, wenn das Muster nicht übereinstimmt. Die folgenden beiden Abfragen geben exakt die gleichen Ergebnisse zurück, bei der zweiten Abfrage werden die Daten jedoch nur einmal überprüft. Bei der zweiten Abfrage ist jeder parse-Befehl nur für die jeweiligen Ereignisse relevant. Der darauf folgende extend-Operator veranschaulicht, wie auf leere Daten Bezug genommen wird:

//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

Wenn in der obigen Abfrage die Verwendung von Unterabfragen nicht vermieden werden kann, besteht eine weitere Technik darin, die Abfrage-Engine mithilfe der materialize()-Funktion darauf hinzuweisen, dass jeweils eine einzige Datenquelle verwendet wird. Dieses Verfahren ist nützlich, wenn die Quelldaten aus einer Funktion stammen, die mehrmals innerhalb der Abfrage verwendet wird. Materialize ist effektiv, wenn die Ausgabe der Unterabfrage viel kleiner als die Eingabe ist. Die Ausgabe wird von der Abfrage-Engine zwischengespeichert und in allen Vorkommen wiederverwendet.

Reduzieren der Anzahl von abgerufenen Spalten

Da es sich bei Azure Data Explorer um einen spaltenbasierten Datenspeicher handelt, ist das Abrufen jeder Spalten von den anderen Spalten unabhängig. Die Anzahl der abgerufenen Spalten wirkt sich unmittelbar auf das Gesamtdatenvolumen aus. Sie sollten nur die benötigten Spalten in die Ausgabe einschließen, indem Sie die Ergebnisse mit summarize zusammenfassen oder die jeweiligen Spalten mit project projizieren.

Azure Data Explorer bietet mehrere Optimierungen, um die Anzahl der abgerufenen Spalten zu verringern. Wenn festgestellt wird, dass eine Spalte nicht erforderlich ist (z. B. wenn im summarize-Befehl nicht darauf verwiesen wird), wird sie auch nicht abgerufen.

Beispielsweise werden bei der zweiten Abfrage u. U. dreimal mehr Daten verarbeitet, da nicht nur eine Spalte, sondern drei Spalten abgerufen werden müssen:

//Less columns --> Less data
SecurityEvent
| summarize count() by Computer  
//More columns --> More data
SecurityEvent
| summarize count(), dcount(EventID), avg(Level) by Computer  

Zeitspanne der verarbeiteten Abfrage

Alle Protokolle in Azure Monitor-Protokolle werden gemäß der Spalte TimeGenerated partitioniert. Die Anzahl der Partitionen, auf die zugegriffen wird, stehen in direktem Bezug zur Zeitspanne. Eine Verringerung des Zeitbereichs ist die effizienteste Methode, um eine schnelle Abfrageausführung zu gewährleisten.

Eine Abfrage mit einer Zeitspanne von mehr als 15 Tagen wird als Abfrage mit übermäßigem Ressourcenverbrauch betrachtet. Eine Abfrage mit einer Zeitspanne von mehr als 90 Tagen wird als missbräuchliche Abfrage betrachtet und ggf. gedrosselt.

Sie können den Zeitbereich mithilfe der Zeitbereichsauswahl im Log Analytics-Bildschirm festlegen (siehe Beschreibung unter Protokollabfragebereich und Zeitbereich in Azure Monitor Log Analytics). Diese Methode wird empfohlen, da der ausgewählte Zeitbereich mithilfe der Abfragemetadaten an das Back-End übermittelt wird.

Eine alternative Methode ist das explizite Einschließen einer where-Bedingung für TimeGenerated in die Abfrage. Verwenden Sie diese Methode, weil dann auch bei Verwendung der Abfrage über eine andere Oberfläche eine feste Zeitspanne gewährleistet ist.

Sorgen Sie dafür, dass alle Teile der Abfrage über TimeGenerated-Filter verfügen. Wenn eine Abfrage Unterabfragen zum Abrufen von Daten aus verschiedenen Tabellen oder derselben Tabelle enthält, muss jede Abfrage eine eigene where-Bedingung enthalten.

Sicherstellen, dass alle Unterabfragen über den TimeGenerated-Filter verfügen

In der folgenden Abfrage wird die Perf-Tabelle beispielsweise nur für den letzten Tag überprüft. Die Heartbeat-Tabelle wird für den gesamten Verlauf überprüft, der bis zu zwei Jahre umfassen kann:

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

Ein solcher Fehler tritt häufig auf, wenn arg_max() für die Suche nach dem letzten Auftreten verwendet wird. Beispiel:

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

Sie können diese Situation problemlos korrigieren, indem in der inneren Abfrage ein Zeitfilter hinzugefügt wird:

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

Ein weiteres Beispiel für diesen Fehler ist die Ausführung einer Zeitbereichsfilterung unmittelbar nach einer union-Anweisung über mehrere Tabellen. Bei der Ausführung der union-Anweisung sollte jede Unterabfrage einen Bereich aufweisen. Sie können eine let-Anweisung verwenden, um Bereichskonsistenz zu gewährleisten.

Mit der folgenden Abfrage werden beispielsweise alle Daten in den Tabellen Heartbeat und Perf und nicht nur der letzte Tag überprüft:

Heartbeat 
| summarize arg_min(TimeGenerated,*) by Computer
| union (
    Perf 
    | summarize arg_min(TimeGenerated,*) by Computer) 
| where TimeGenerated > ago(1d)
| summarize min(TimeGenerated) by Computer

So korrigieren Sie die Abfrage:

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

Einschränkungen bei der Zeitspannenmessung

Die Messung ist immer größer als die tatsächlich angegebene Zeit. Wenn der Filter für die Abfrage z. B. 7 Tage beträgt, überprüft das System möglicherweise 7,5 oder 8,1 Tage. Diese Streuung daran, dass die Daten vom System in Blöcke mit variabler Größe partitioniert werden. Um sicherzustellen, dass alle relevanten Datensätze überprüft werden, überprüft das System die ganze Partition. Dieser Prozess kann mehrere Stunden und sogar mehr als einen Tag dauern.

Es gibt mehrere Fälle, in denen das System keine genaue Messung des Zeitbereichs liefern kann. Diese Situation tritt meistens auf, wenn die Abfrage weniger als einen Tag umfasst, oder bei Abfragen mit mehreren Arbeitsbereichen.

Wichtig

Dieser Indikator zeigt nur Daten an, die im unmittelbaren Cluster verarbeitet werden. Bei einer Abfrage in mehreren Regionen würde nur eine der Regionen dargestellt. Bei einer Abfrage in mehreren Arbeitsbereichen sind möglicherweise nicht alle Arbeitsbereiche enthalten.

Alter der verarbeiteten Daten

Azure Data Explorer verwendet mehrere Speicherebenen: In-Memory, lokale SSD-Datenträger und wesentlich langsamere Azure-Blobs. Je neuer die Daten sind, desto höher ist die Wahrscheinlichkeit, dass sie auf einer leistungsfähigeren Ebene mit geringerer Latenz gespeichert sind, wodurch Abfragedauer und CPU-Auslastung reduziert werden. Abgesehen von den Daten selbst verfügt das System auch über einen Cache für Metadaten. Je älter die Daten sind, desto geringer ist die Wahrscheinlichkeit, dass sich deren Metadaten im Cache befinden.

Eine Abfrage, die Daten mit einem Alter von mehr als 14 Tagen verarbeitet, wird als Abfragen mit übermäßigem Ressourcenverbrauch betrachtet.

Für einige Abfragen müssen alte Daten verwendet werden, es gibt jedoch auch Fälle, in denen alte Daten versehentlich verwendet werden. Dieses Szenario tritt auf, wenn Abfragen ohne Angabe eines Zeitbereichs in den Metadaten ausgeführt werden und nicht alle Tabellenverweise Filter für die TimeGenerated-Spalte enthalten. In diesen Fällen überprüft das System alle Daten, die in dieser Tabelle gespeichert sind. Bei einer langen Datenaufbewahrungsdauer kann sie lange Zeitbereiche abdecken. Daher werden Daten, die so alt sind wie der Datenaufbewahrungszeitraum, überprüft.

Hier einige Beispiele für solche Fälle:

  • Keine Festlegung des Zeitbereichs in Log Analytics mit einer Unterabfrage, die nicht beschränkt ist. Siehe obiges Beispiel.
  • Verwendung der API ohne die optionalen Parameter für den Zeitbereich.
  • Verwendung eines Clients, der keinen Zeitbereich vorschreibt, z. B. der Power BI-Connector.

Sehen Sie sich die Beispiele und Hinweise im vorherigen Abschnitt an, da auch diese in diesem Fall zutreffen.

Number of regions (Anzahl von Regionen)

Es gibt Situationen, in denen eine einzelne Abfrage über verschiedene Regionen ausgeführt werden kann. Beispiel:

  • Wenn mehrere Arbeitsbereiche explizit aufgelistet sind und sich diese in verschiedenen Regionen befinden.
  • Wenn eine Abfrage mit Ressourcenbereich Daten abruft und die Daten in mehreren Arbeitsbereichen gespeichert sind, die sich in verschiedenen Regionen befinden.

Für eine regionsübergreifende Abfrageausführung muss das System im Back-End große Zwischendatenblöcke serialisieren und übertragen, die in der Regel wesentlich größer sind als die letztendlichen Ergebnisse der Abfrage. Außerdem wird die Fähigkeit des Systems beschränkt, Optimierungen und Heuristik durchzuführen sowie Caches zu verwenden.

Wenn es keinen Grund für die Überprüfung all dieser Regionen gibt, sollten Sie den Bereich so anpassen, dass er weniger Regionen umfasst. Wenn der Ressourcenbereich minimiert ist, aber dennoch viele Bereiche verwendet werden, kann eine Fehlkonfiguration die Ursache sein. Beispielsweise können Überwachungsprotokolle und Diagnoseeinstellungen an verschiedene Arbeitsbereiche in unterschiedlichen Regionen gesendet werden, oder es können mehrere Konfigurationen für Diagnoseeinstellungen vorhanden sein.

Eine Abfrage, die sich über mehr als 3 Regionen erstrecken, wird als Abfrage mit übermäßigem Ressourcenverbrauch betrachtet. Eine Abfrage, die sich über mehr als 6 Regionen erstrecken, wird als missbräuchliche Abfrage betrachtet und ggf. gedrosselt.

Wichtig

Wenn eine Abfrage über mehrere Regionen ausgeführt wird, sind die CPU- und Datenmessungen nicht exakt, und die Messungen werden nur für eine der Regionen dargestellt.

Anzahl von Arbeitsbereichen

Arbeitsbereiche sind logische Container, die zum Trennen und Verwalten von Protokolldaten verwendet werden. Das Back-End optimiert die Platzierung von Arbeitsbereichen in physischen Clustern innerhalb der ausgewählten Region.

Die Verwendung mehrerer Arbeitsbereiche kann folgende Ursachen haben:

  • Es werden explizit mehrere Arbeitsbereiche aufgelistet.
  • Eine Abfrage mit Ressourcenbereich ruft Daten ab, und die Daten sind in mehreren Arbeitsbereichen gespeichert.

Für eine regions- und clusterübergreifende Ausführung von Abfragen muss das System im Back-End große Zwischendatenblöcke serialisieren und übertragen, die in der Regel wesentlich größer sind als die letztendlichen Ergebnisse der Abfrage. Außerdem wird die Fähigkeit des Systems beschränkt, Optimierungen und Heuristik durchzuführen sowie Caches zu verwenden.

Eine Abfrage, die sich über mehr als 5 Arbeitsbereiche erstreckt, wird als Abfrage mit übermäßigem Ressourcenverbrauch betrachtet. Abfragen können sich nicht über mehr als 100 Arbeitsbereiche erstrecken.

Wichtig

  • In einigen Szenarien mit mehreren Arbeitsbereichen sind die CPU- und Datenmessungen nicht exakt und die Messungen werden nur für einige der Arbeitsbereiche dargestellt.
  • Arbeitsbereichsübergreifende Abfragen mit einem expliziten Bezeichner (Arbeitsbereichs-ID oder Azure-Ressourcen-ID des Arbeitsbereichs) verbrauchen weniger Ressourcen und sind leistungsfähiger.

Parallelität

Azure Monitor-Protokolle verwenden große Azure Data Explorer-Cluster zum Ausführen von Abfragen. Diese Cluster sind unterschiedlich groß und können Dutzenden von Computeknoten umfassen. Das System skaliert die Cluster automatisch gemäß der Platzierungslogik und Kapazität von Arbeitsbereichen.

Zum effizienten Ausführen einer Abfrage wird sie basierend auf den Daten, die für die Verarbeitung benötigt werden, partitioniert und an Computeknoten verteilt. In einigen Situationen kann das System diesen Schritt nicht effizient ausführen, sodass es zu einer langen Abfragedauer kommen kann.

Hier einige Abfrageverhalten, die zur einer Verringerung der Parallelität führen können:

  • Verwendung von Serialisierungs- und Fensterfunktionen, z. B. dem serialize-Operator, next(), prev() und den row-Funktionen. In einigen dieser Fälle können Zeitreihen- und Benutzeranalysefunktionen verwendet werden. Eine ineffiziente Serialisierung kann auch auftreten, wenn die folgenden Operatoren nicht am Ende der Abfrage verwendet werden: range, sort, order, top, top-hitters und getschema.
  • Durch Verwendung der Aggregationsfunktion dcount() wird das System gezwungen, eine zentrale Kopie der eindeutigen Werte zu verwenden. Wenn die Datenmenge groß ist, sollten Sie die optionalen Parameter der dcount-Funktion verwenden, um die Genauigkeit zu verringern.
  • In vielen Fällen verringert der join-Operator die Gesamtparallelität. Erwägen Sie shuffle join als Alternative, wenn die Leistung problematisch ist.
  • Bei Abfragen im Ressourcenbereich kann es bei der rollenbasierten Zugriffssteuerung (RBAC) von Kubernetes oder RBAC-Prüfungen von Azure vor der Ausführung bei einer sehr großen Anzahl von Azure-Rollenzuweisungen zu Verzögerungen kommen. Diese Situation kann zu längeren Überprüfungen führen, was wiederum eine geringere Parallelität bewirkt. Beispielsweise kann eine Abfrage für ein Abonnement ausgeführt werden, bei dem Tausende von Ressourcen vorhanden sind, und jede Ressource verfügt über viele Rollenzuweisungen auf der Ressourcenebene und nicht auf Ebene des Abonnements oder der Ressourcengruppe.
  • Wenn eine Abfrage kleine Datenblöcke verarbeitet, ist die Parallelität gering, weil sie vom System nicht auf viele Computeknoten verteilt wird.

Nächste Schritte

Referenzdokumentation für die Abfragesprache Kusto