Azure Monitor의 로그 쿼리 최적화

Azure Monitor Logs는 Azure Data Explorer를 사용하여 로그 데이터를 저장하고 해당 데이터를 분석하기 위한 쿼리를 실행합니다. Azure Monitor Logs는 사용자 대신 Azure Data Explorer 클러스터를 만들고, 관리하고, 유지 관리하고, 로그 분석 워크로드에 맞게 최적화합니다. 쿼리를 실행하면 쿼리가 최적화되어 작업 영역 데이터를 저장하는 적절한 Azure Data Explorer 클러스터로 라우팅됩니다.

Azure Monitor Logs와 Azure Data Explorer는 여러 자동 쿼리 최적화 메커니즘을 사용합니다. 자동 최적화는 성능을 크게 향상하지만, 쿼리 성능을 크게 향상할 수 있는 경우도 가끔 있습니다. 이 문서에서는 성능 고려 사항 및 이를 해결하기 위한 몇 가지 기술에 대해 설명합니다.

대부분의 기술은 Azure Data Explorer 및 Azure Monitor Logs에서 직접 실행되는 쿼리에 공통적으로 사용됩니다. 여러 가지 고유한 Azure Monitor Logs 고려 사항에 대해서도 설명합니다. Azure Data Explorer 최적화 팁에 대한 자세한 내용은 쿼리 모범 사례를 참조하세요.

쿼리를 최적화하면:

  • 더 빨리 실행되고 전체적인 쿼리 실행 시간을 단축합니다.
  • 제한되거나 거부될 가능성이 줄어듭니다.

대시보드, 경고, Azure Logic Apps 및 Power BI와 같은 되풀이 및 동시 사용에 사용되는 쿼리에 특히 주의해야 합니다. 이러한 경우 비효율적인 쿼리의 영향은 상당히 큽니다.

다음은 쿼리 최적화에 대한 자세한 동영상 설명입니다.

쿼리 세부 정보 창

Log Analytics에서 쿼리를 실행한 후 화면의 오른쪽 아래 모서리에 있는 쿼리 세부 정보를 선택하여 쿼리 세부 정보 창을 엽니다. 이 창에서는 쿼리에 대한 여러 성능 지표의 결과가 표시됩니다. 이러한 성능 지표는 다음 섹션에서 설명합니다.

Screenshot that shows the Query Details pane in Azure Monitor Log Analytics.

쿼리 성능 지표

다음 쿼리 성능 지표는 실행되는 모든 쿼리에 제공됩니다.

  • 총 CPU: 모든 컴퓨팅 노드에서 쿼리를 처리하는 데 사용되는 전체 컴퓨팅입니다. 컴퓨팅, 구문 분석 및 데이터 가져오기에 사용된 시간을 나타냅니다.
  • 처리된 쿼리에 사용되는 데이터: 쿼리를 처리하기 위해 액세스한 전체 데이터입니다. 대상 테이블의 크기, 사용된 시간 범위, 적용된 필터 및 참조되는 열 수의 영향을 받습니다.
  • 처리된 쿼리의 시간 범위: 쿼리를 처리하기 위해 액세스한 최신 데이터와 가장 오래된 데이터 사이의 간격입니다. 쿼리에 대해 지정한 명시적 시간 범위의 영향을 받습니다.
  • 처리된 데이터의 사용 기간: 현재와 쿼리를 처리하기 위해 액세스한 가장 오래된 데이터 사이의 간격입니다. 데이터 가져오기의 효율성에 큰 영향을 미칩니다.
  • 작업 영역 수: 암시적 또는 명시적 선택에 따라 쿼리 처리 중에 액세스된 작업 영역의 수입니다.
  • 지역 수: 작업 영역을 암시적 또는 명시적으로 선택하여 쿼리를 처리하는 동안 액세스된 지역 수입니다. 다중 지역 쿼리는 훨씬 덜 효율적이며, 성능 지표는 부분적으로 적용됩니다.
  • 병렬 처리: 시스템이 여러 노드에서 쿼리를 얼마나 실행할 수 있었는지를 나타냅니다. CPU 사용량이 많은 쿼리에만 해당됩니다. 특정 함수 및 연산자의 사용에 영향을 받습니다.

총 CPU

모든 쿼리 처리 노드에서 이 쿼리를 처리하는 데 운용된 실제 컴퓨팅 CPU입니다. 대부분의 쿼리는 많은 수의 노드에서 실행되기 때문에 일반적으로 이 총계는 쿼리가 실행되는 데 걸린 기간 보다 훨씬 큽니다.

CPU를 100초 넘게 사용하는 쿼리는 리소스를 과도하게 사용하는 쿼리로 간주됩니다. CPU를 1,000초 넘게 사용하는 쿼리는 악의적인 쿼리로 간주되어 제한될 수 있습니다.

쿼리 처리 시간은 다음에 소요됩니다.

  • 데이터 검색: 오래된 데이터를 검색하는 것이 최근 데이터를 검색하는 것보다 시간이 더 많이 걸립니다.
  • 데이터 처리: 데이터의 논리 및 평가입니다.

Azure Monitor Logs는 쿼리 처리 노드에 소요된 시간 외에도 다음 작업에 시간을 소요합니다.

  • 사용자를 인증하고 사용자가 이 데이터에 액세스하도록 허가되었는지 확인합니다.
  • 데이터 저장소를 찾습니다.
  • 쿼리를 구문 분석합니다.
  • 쿼리 처리 노드를 할당합니다.

이 시간은 쿼리 총 CPU 시간에 포함되지 않습니다.

CPU 사용량이 많은 함수를 사용하기 전에 일찍 레코드 필터링

일부 쿼리 명령 및 함수는 CPU 사용량이 많습니다. JSON 및 XML을 구문 분석하거나 복잡한 정규식을 추출하는 명령의 경우 특히 그렇습니다. 이러한 구문 분석은 parse_json() 또는 parse_xml() 함수를 통해 명시적으로 수행되거나 동적 열을 참조할 때 암시적으로 발생할 수 있습니다.

이러한 함수는 처리 중인 행 수에 비례하여 CPU를 사용합니다. 가장 효율적인 최적화는 쿼리 초기에 where 조건을 추가하는 것입니다. 이렇게 하면 CPU 사용량이 많은 함수가 실행되기 전에 최대한 많은 레코드를 필터링할 수 있습니다.

예를 들어 다음 쿼리는 완전히 똑같은 결과를 산출합니다. 하지만 구문 분석 전에 where 조건이 여러 레코드를 제외하는 두 번째 쿼리가 가장 효율적입니다.

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

평가된 where 절 사용을 피하세요.

데이터 세트에 물리적으로 존재하는 열이 아닌 평가된 열에 where 절이 포함된 쿼리는 효율성이 떨어집니다. 평가된 열을 필터링하면 대규모 데이터 세트가 처리될 때 일부 시스템 최적화를 방해합니다.

예를 들어 다음 쿼리는 완전히 똑같은 결과를 산출합니다. 하지만 where 조건이 기본 제공 열을 참조하는 두 번째 쿼리가 더 효율적입니다.

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

필터링이 필드에서만 수행되는 것은 아니기 때문에 평가된 열이 쿼리 처리 엔진에 의해 암시적으로 생성되는 경우가 있습니다.

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

summarize 및 join에 효과적인 집계 명령 및 차원 사용

max(), sum(), count()avg()와 같은 일부 집계 명령은 그 논리 때문에 CPU에 미치는 영향이 적습니다. 다른 명령은 좀 더 복잡하며 효율적으로 실행할 수 있는 휴리스틱과 예측을 포함하고 있습니다. 예를 들어 dcount()는 HyperLogLog 알고리즘을 사용하여 각 값을 실제로 집계하지 않고도 대규모 데이터 세트의 고유 개수에 근접하는 추정치를 제공합니다.

백분위수 함수는 가장 가까운 순위 백분위수 알고리즘을 사용하여 유사한 근사법을 수행합니다. 몇 가지 명령에는 영향을 줄이기 위한 선택적 매개 변수가 포함됩니다. 예를 들어 makeset() 함수에는 CPU 및 메모리에 큰 영향을 미치는 최대 집합 크기를 정의하는 선택적 매개 변수가 있습니다.

joinsummarize 명령은 대량의 데이터를 처리할 때 CPU 사용률이 높아질 수 있습니다. 복잡성은 summarize에서 by로 사용되거나 join 특성으로 사용되는 열의 가능한 값(카디널리티라고 함)의 수와 직접적인 관련이 있습니다. joinsummarize에 대한 설명과 최적화는 해당 설명서 및 최적화 팁을 참조하세요.

예를 들어 CounterPath는 항상 CounterNameObjectName에 일대일로 매핑되기 때문에 다음 쿼리는 정확히 동일한 결과를 생성합니다. 집계 차원이 더 작은 두 번째 쿼리가 더 효율적입니다.

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

CPU 사용량은 컴퓨팅 집약적인 where 조건 또는 확장 열의 영향을 받을 수 있습니다. equal ==startswith와 같은 모든 간단한 문자열 비교는 CPU에 거의 동일한 영향을 미칩니다. 고급 텍스트 일치는 더 큰 영향을 미칩니다. 특히 has 연산자는 contains 연산자보다 효율적입니다. 문자열 처리 기법 때문에 짧은 문자열보다는 4자를 초과하는 문자열을 찾는 것이 더 효율적입니다.

예를 들어 다음 쿼리는 Computer 명명 정책에 따라 비슷한 결과를 도출합니다. 하지만 두 번째 쿼리가 더 효율적입니다.

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

참고 항목

이 지표는 직접 클러스터의 CPU만 표시합니다. 다중 지역 쿼리에서는 지역 중 하나만 나타냅니다. 다중 작업 영역 쿼리에는 일부 작업 영역이 포함되지 않을 수 있습니다.

문자열 구문 분석이 작동할 때 전체 XML 및 JSON 구문 분석을 피하세요.

XML 또는 JSON 개체를 전체 구문 분석하면 CPU 및 메모리 리소스 사용량이 많을 수 있습니다. 매개 변수가 하나 또는 두 개만 필요하고 XML 또는 JSON 개체가 간단하면 문자열로 구문 분석하는 것이 더 쉬운 경우가 많습니다. 구문 분석 연산자 또는 기타 텍스트 구문 분석 기술을 사용합니다. 성능 향상은 XML 또는 JSON 개체의 레코드 수가 증가할수록 더 중요합니다. 레코드 수가 수천만에 도달하면 반드시 필요합니다.

예를 들어 다음 쿼리는 전체 XML 구문 분석을 수행하지 않고 이전 쿼리와 정확히 동일한 결과를 반환합니다. 이 쿼리는 XML 파일 구조에 대해 몇 가지 가정을 합니다. 예를 들어 FilePath 요소는 FileHash 요소 뒤에 오고 두 요소 모두 특성이 없습니다.

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

처리된 쿼리에 사용되는 데이터

쿼리 처리에서 중요한 요소는 쿼리를 처리하기 위해 검사되고 사용되는 데이터의 볼륨입니다. Azure Data Explorer 다른 데이터 플랫폼에 비해 데이터 볼륨을 획기적으로 줄이는 적극적인 최적화를 사용합니다. 그래도 쿼리에는 사용되는 데이터 볼륨에 영향을 줄 수 있는 중요한 요소가 있습니다.

2,000KB를 초과하는 데이터를 처리하는 쿼리는 리소스를 과도하게 사용하는 쿼리로 간주됩니다. 20,000KB를 초과하는 데이터를 처리하는 쿼리는 악의적인 쿼리로 간주되어 제한될 수 있습니다.

Azure Monitor 로그에서 TimeGenerated 열은 데이터를 인덱싱하는 방법으로 사용됩니다. TimeGenerated 값의 범위를 최대한 좁히면 쿼리 성능이 향상됩니다. 범위를 좁히면 처리해야 하는 데이터의 양이 크게 줄어듭니다.

불필요한 search 및 union 연산자 사용을 피하세요.

처리할 데이터 양을 늘리는 또 다른 요인은 많은 수의 테이블을 사용하는 것입니다. 이 상황은 search *union * 명령을 사용할 때 주로 발생합니다. 이러한 명령은 시스템이 작업 영역에 있는 모든 테이블의 데이터를 평가하고 검사하도록 합니다. 경우에 따라 작업 영역에 수백 개의 테이블이 있을 수도 있습니다. 특정 테이블로 범위를 지정하지 않고 search * 또는 다른 검색을 사용하지 마세요.

예를 들어 다음 쿼리는 정확히 동일한 결과를 생성하지만 마지막 쿼리가 가장 효율적입니다.

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

쿼리에 초기 필터 추가

데이터 볼륨을 줄이는 또 다른 방법은 쿼리의 초기에 where 조건을 사용하는 것입니다. Azure Data Explorer 플랫폼에는 특정 where 조건과 관련된 데이터가 포함된 파티션을 알 수 있는 캐시가 포함되어 있습니다. 예를 들어 쿼리에 where EventID == 4624가 포함된 경우 일치하는 이벤트가 있는 파티션을 처리하는 노드에만 쿼리를 배포합니다.

다음 예제 쿼리는 정확히 동일한 결과를 생성하지만 두 번째 쿼리가 더 효율적입니다.

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

조건부 aggregation 함수 및 materialize 함수를 사용하여 동일한 원본 데이터를 여러 번 검사하지 마세요.

하나의 쿼리에 join 또는 union 연산자를 사용하여 병합된 하위 쿼리가 여러 개 있으면 각각의 하위 쿼리가 전체 원본을 개별적으로 검사합니다. 그 후 결과를 병합합니다. 이렇게 되면 데이터 검사 횟수가 몇 배로 증가하며, 이는 대규모 데이터 세트에서 상당한 영향을 미칩니다.

이러한 상황을 방지하는 기법은 조건부 집계 함수를 사용하는 것입니다. 요약 연산자에서 사용되는 대부분의 집계 함수에는 조건화된 버전이 있어서 여러 조건에 단일 요약 연산자를 사용할 수 있습니다.

예를 들어 다음 쿼리는 각 계정에 대한 로그인 이벤트 수와 프로세스 실행 이벤트 수를 보여줍니다. 동일한 결과를 반환하지만 첫 번째 쿼리는 데이터를 두 번 검사합니다. 두 번째 쿼리는 한 번만 검사합니다.

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

하위 쿼리가 불필요한 또 다른 상황은 특정 패턴과 일치하는 레코드만 처리하도록 구문 분석 연산자를 사전 필터링하는 경우입니다. 패턴이 일치하지 않을 경우 구문 분석 연산자 및 기타 유사한 연산자가 빈 결과를 반환하기 때문에 하위 쿼리가 필요 없습니다. 다음 두 쿼리는 정확히 동일한 결과를 반환하지만 두 번째 쿼리는 데이터를 한 번만 검사합니다. 두 번째 쿼리에서 각 구문 분석 명령은 해당 이벤트에만 해당됩니다. 나중에 나오는 extend 연산자는 빈 데이터 상황을 참조하는 방법을 보여줍니다.

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

이전 쿼리로는 하위 쿼리 사용을 피할 수 없는 경우에 사용할 수 있는 또 다른 기법은 materialize() 함수를 사용하여 각 하위 쿼리에 단일 원본 데이터가 사용되고 있음을 쿼리 엔진에 힌트로 알리는 것입니다. 이 기법은 원본 데이터가 쿼리 내에서 여러 번 사용되는 함수에서 오는 경우에 유용합니다. Materialize는 하위 쿼리의 출력이 입력보다 훨씬 작은 경우에 효과적입니다. 쿼리 엔진은 모든 경우에 출력을 캐시하고 다시 사용합니다.

검색되는 열 수 줄이기

Azure Data Explorer는 열 형식 데이터 저장소이므로 모든 열 검색은 다른 열과 독립적입니다. 검색되는 열의 수는 전체 데이터 볼륨에 직접적인 영향을 줍니다. 결과를 요약하거나 특정 열을 예상하는 데 필요한 열만 출력에 포함해야 합니다.

Azure Data Explorer에는 검색된 열 수를 줄이기 위한 몇 가지 최적화 기능이 있습니다. 열이 필요하지 않다고 판단되면(예: summarize 명령에서 참조되지 않는 경우) 검색하지 않습니다.

예를 들어 두 번째 쿼리는 하나의 열이 아닌 세 개의 열을 가져와야 하기 때문에 세 배 더 많은 데이터를 처리할 가능성이 있습니다.

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

처리된 쿼리의 시간 범위

Azure Monitor 로그의 모든 로그는 TimeGenerated 열에 따라 분할됩니다. 액세스되는 파티션의 수는 시간 범위와 직접적인 관련이 있습니다. 시간 범위를 줄이는 것이 신속한 쿼리 실행을 보장하는 가장 효율적인 방법입니다.

시간 범위가 15일을 초과하는 쿼리는 과도한 리소스를 사용하는 쿼리로 간주됩니다. 시간 범위가 90일을 초과하는 쿼리는 악성 쿼리로 간주되어 제한될 수 있습니다.

Azure Monitor Log Analytics의 로그 쿼리 범위 및 시간 범위에 설명된 대로 Log Analytics 화면의 시간 범위 선택기를 사용하여 시간 범위를 설정할 수 있습니다. 이 방법은 선택한 시간 범위가 쿼리 메타데이터를 사용하여 백 엔드로 전달되기 때문에 권장하는 방법입니다.

다른 방법은 쿼리의 TimeGenerated에 대해 where 조건을 명시적으로 포함하는 것입니다. 이 방법을 사용하면 쿼리가 다른 인터페이스에서 사용되는 경우에도 시간 범위가 고정되기 때문에 이 방법을 사용해야 합니다.

쿼리의 모든 부분에 TimeGenerated 필터가 있어야 합니다. 쿼리에 여러 테이블 또는 동일한 테이블에서 데이터를 가져오는 하위 쿼리가 있는 경우 각 쿼리는 자체적인 where 조건을 포함해야 합니다.

모든 하위 쿼리에 TimeGenerated 필터 포함

예를 들어 다음 쿼리에서 Perf 테이블은 마지막 날짜에 대해서만 검사됩니다. Heartbeat 테이블은 모든 기록에 대해 검사되며, 기록되는 최대 기간은 2년입니다.

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

이러한 실수가 발생하는 일반적인 경우는 arg_max()를 사용하여 가장 최근에 발생한 항목을 찾는 경우입니다. 예시:

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

내부 쿼리에 시간 필터를 추가하면 이 상황을 쉽게 해결할 수 있습니다.

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

이 문제의 또 다른 사례는 여러 테이블에 대해 union을 수행한 직후에 시간 범위 필터링을 수행하는 경우입니다. union을 수행할 때, 하위 쿼리마다 범위를 지정해야 합니다. let 문을 사용하면 범위를 일관적으로 유지할 수 있습니다.

예를 들어 다음 쿼리는 HeartbeatPerf 테이블에서 지난 하루가 아닌 모든 데이터를 검사합니다.

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

쿼리를 수정하는 방법은 다음과 같습니다.

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

시간 범위 측정값 제한

측정값은 항상 지정된 실제 시간보다 큽니다. 예를 들어 쿼리의 필터가 7일인 경우 시스템은 7.5일 또는 8.1일을 검사할 수 있습니다. 이렇게 변하는 이유는 시스템이 데이터를 다양한 크기의 청크로 분할하기 때문입니다. 관련 레코드를 모두 검사하기 위해 시스템은 전체 파티션을 검사합니다. 이 프로세스는 적게는 몇 시간, 많게는 하루 넘게 걸릴 수 있습니다.

시스템이 시간 범위의 정확한 측정을 제공할 수 없는 경우가 몇 가지 있습니다. 이 상황은 대부분 쿼리의 범위가 하루 미만이거나 다중 작업 영역 쿼리일 때 발생합니다.

Important

이 지표는 직접 클러스터에서 처리된 데이터만 표시합니다. 다중 지역 쿼리에서는 지역 중 하나만 나타냅니다. 다중 작업 영역 쿼리에는 일부 작업 영역이 포함되지 않을 수 있습니다.

처리된 데이터의 사용 기간

Azure Data Explorer는 여러 스토리지 계층(메모리 내, 로컬 SSD 디스크 및 훨씬 느린 Azure Blob)을 사용합니다. 데이터가 최신일수록 대기 시간이 더 짧은 고성능 계층에 저장되므로 쿼리 시간과 CPU 사용량이 감소할 가능성이 높습니다. 데이터 자체 외에 시스템에는 메타데이터용 캐시도 있습니다. 데이터가 오래될수록 해당 메타데이터가 캐시에 있을 가능성이 낮습니다.

14일 넘게 지난 데이터를 처리하는 쿼리는 과도한 리소스를 사용하는 쿼리로 간주됩니다.

오래된 데이터를 꼭 사용해야 하는 쿼리도 있지만, 오래된 데이터를 실수로 사용하는 경우도 있습니다. 이 상황은 메타데이터에 시간 범위를 제공하지 않고 쿼리가 실행되고 일부 테이블 참조에 TimeGenerated 열에 대한 필터가 포함되지 않는 경우에 발생합니다. 이 경우 시스템은 테이블에 저장된 모든 데이터를 검사합니다. 데이터 보존 기간이 길면 긴 시간 범위를 포함할 수 있습니다. 결과적으로 데이터 보존 기간만큼 오래된 데이터가 검사됩니다.

예를 들면 다음과 같습니다.

  • 제한되지 않는 하위 쿼리로 Log Analytics에서 시간 범위를 설정하지 않는 경우. 이전 예제를 참조하세요.
  • 시간 범위 선택적 매개 변수 없이 API를 사용하는 경우
  • 시간 범위를 강제 적용하지 않는 클라이언트(예: Power BI 커넥터)를 사용합니다.

이전 섹션의 예제와 메모도 이 상황과 관련이 있으니 참조하세요.

지역 수

여러 지역에서 단일 쿼리가 실행될 수 있는 여러 상황이 있습니다. 예시:

  • 여러 작업 영역이 명시적으로 나열되고 서로 다른 지역에 있는 경우
  • 리소스 범위 쿼리가 데이터를 가져오고 데이터가 서로 다른 지역에 있는 여러 작업 영역에 저장되어 있는 경우

지역 간 쿼리를 실행하려면 시스템을 직렬화하고 백엔드에서 일반적으로 쿼리 최종 결과보다 훨씬 큰 대량의 중간 데이터 청크를 전송해야 합니다. 또한 최적화 및 휴리스틱을 수행하고 캐시를 사용하는 시스템의 기능을 제한합니다.

모든 지역을 검사할 이유가 없다면 범위를 조정하여 지역 수를 줄입니다. 리소스 범위를 최소화했지만 여전히 많은 지역이 사용되는 경우에는 구성 오류가 원인일 수 있습니다. 예를 들어 감사 로그 및 진단 설정이 다른 지역의 다른 작업 영역으로 전송되거나 여러 진단 설정 구성이 있을 수 있습니다.

3개가 넘는 지역에 걸쳐 있는 쿼리는 과도한 리소스를 사용하는 쿼리로 간주됩니다. 6개가 넘는 지역에 걸쳐 있는 쿼리는 악의적인 쿼리로 간주되어 제한될 수 있습니다.

Important

여러 지역에서 쿼리를 실행하면 CPU 및 데이터 측정이 정확하지 않으며 지역 중 하나의 측정값만 나타냅니다.

작업 영역 수

작업 영역은 로그 데이터를 분리하고 관리하는 데 사용되는 논리적 컨테이너입니다. 백 엔드는 선택한 지역 내에서 실제 클러스터의 작업 영역 배치를 최적화합니다.

다음과 같은 경우 인스턴스의 여러 작업 영역이 사용될 수 있습니다.

  • 여러 작업 영역이 명시적으로 나열됩니다.
  • 리소스 범위 쿼리가 데이터를 가져오고 데이터가 여러 작업 영역에 저장됩니다.

지역 간 쿼리 또는 클러스터 간 쿼리를 실행하려면 시스템을 직렬화하고 백엔드에서 일반적으로 쿼리 최종 결과보다 훨씬 큰 대량의 중간 데이터 청크를 전송해야 합니다. 또한 최적화 및 휴리스틱을 수행하고 캐시를 사용하는 시스템의 기능을 제한합니다.

5개가 넘는 작업 영역에 걸쳐 있는 쿼리는 과도한 리소스를 사용하는 쿼리로 간주됩니다. 100개가 넘는 작업 영역으로 쿼리를 확장할 수 없습니다.

Important

  • 일부 다중 작업 영역 시나리오에서는 CPU 및 데이터 측정값이 정확하지 않으며 일부 작업 영역에만 측정값을 나타냅니다.
  • 명시적 식별자가 있는 작업 영역 간 쿼리: 작업 영역 ID 또는 작업 영역 Azure 리소스 ID는 리소스를 적게 사용하고 성능이 더 높습니다.

병렬 처리

Azure Monitor Logs는 대규모 Azure Data Explorer 클러스터를 사용하여 쿼리를 실행합니다. 이러한 클러스터는 규모가 다르며 최대 수십 개의 컴퓨팅 노드까지 확장 가능합니다. 시스템은 작업 영역 배치 논리 및 용량에 따라 클러스터의 크기를 자동으로 조정합니다.

쿼리를 효율적으로 실행하기 위해 처리에 필요한 데이터를 기반으로 쿼리를 분할하고 컴퓨팅 노드에 배포합니다. 시스템이 이 단계를 효율적으로 수행할 수 없는 경우가 있으며, 이 경우 쿼리 기간이 길어질 수 있습니다.

병렬 처리를 줄일 수 있는 쿼리 동작은 다음과 같습니다.

  • serialize 연산자, next(), prev(), row 함수와 같은 직렬화 및 기간 함수 사용하는 경우. 이러한 경우에 시계열 및 사용자 분석 함수를 사용할 수 있습니다. 비효율적인 직렬화는 쿼리 끝이 아닌 곳에 range, sort, order, top, top-hittersgetschema 연산자를 사용하는 경우에도 발생할 수 있습니다.
  • dcount() 집계 함수를 사용하면 시스템이 고유한 값의 중앙 복사본을 갖게 됩니다. 데이터 규모가 큰 경우에는 dcount 함수 선택적 매개 변수를 사용하여 정확도를 낮추는 것이 좋습니다.
  • 많은 경우 join 연산자는 전반적인 병렬 처리를 감소시킵니다. 성능에 문제가 있는 경우 대안으로 shuffle join을 고려할 수 있습니다.
  • 리소스 범위 쿼리에서 Azure 역할 할당이 많으면 사전 실행 Kubernetes RBAC(역할 기반 액세스 제어) 또는 Azure RBAC 검사가 오래 걸릴 수 있습니다. 이 때문에 검사가 길어져서 병렬 처리가 줄어들 수 있습니다. 수천 개의 리소스가 있는 구독에서 쿼리가 실행되고 각 리소스에는 구독 또는 리소스 그룹이 아닌 리소스 수준의 역할 할당이 많이 있는 상황을 예로 들 수 있습니다.
  • 쿼리가 작은 데이터 청크를 처리하는 경우 시스템이 데이터를 여러 컴퓨팅 노드에 분산하지 않기 때문에 병렬 처리 속도가 낮습니다.

다음 단계

Kusto 쿼리 언어에 대한 참조 문서