스테이지 통계

스테이지 통계 (Stats)

multi-stage 통계에 대해 더 배우고 이를 사용해 쿼리를 개선하는 방법을 알아봐요.

출처: 문서

본문

multi-stage 통계(MSE)는 single-stage 통계보다 더 복잡하지만 더 표현력이 풍부해요. single-stage 통계에서는 Apache Pinot가 쿼리에 대한 단일 통계 집합을 반환하는 반면, multi-stage 통계에서는 Apache Pinot가 쿼리 실행의 각 연산자에 대한 통계 집합을 반환해요. 이 통계는 기본적으로 수집되며 모든 MSE 쿼리의 응답에 포함돼요.

Pinot 1.6.0부터 stageStats 트리는 기본적으로 잎-스테이지 파이프라인-브레이커 하위 트리도 포함해요. 기존 소비자가 1.6 이전 형태가 필요하다면 클러스터에 pinot.query.mse.skip.pipeline.breaker.stats=true를 설정해 해당 노드를 다시 억제해요.

각 연산자는 쿼리 실행 중에 수집되는 자체 통계 집합을 가져요. 다양한 연산자 유형과 그 통계에 대해 더 배우려면 Operator Types 섹션을 참고해요.

activeWorkers를 사용해 고르지 않은 작업 분배를 감지해요. LEAF, MAILBOX_RECEIVE, MAILBOX_SEND는 활동을 다르게 집계해요: 잎 작업자는 최소한 하나의 세그먼트가 있을 때, 수신 작업자는 최소한 하나의 행을 받을 때, 전송 작업자는 최소한 하나의 행을 보낼 때 활성 상태예요. 각 값을 스테이지의 parallelism과 비교하고, 연산자 트리를 따라 값을 비교해 스테이지가 어디에서 좁아지는지 확인해요.

MAILBOX_SEND의 경우 maxEmittedRows와 minNonzeroEmittedRows는 데이터를 내보낸 작업자들 사이의 행-수 분포를 보여줘요. 큰 차이는 스큐(skew)를 나타내요. 행을 내보내지 않은 작업자는 minNonzeroEmittedRows에서 제외되며, 유휴 작업자를 식별하려면 activeWorkers를 사용해요.

multi-stage 통계 시각화 도구

multi-stage 통계를 분석하는 권장 방법은 Pinot UI에 포함된 시각화 도구를 사용하는 것이에요. Pinot 컨트롤러 UI에서 쿼리를 실행하고 Visual 버튼을 클릭해 접근할 수 있어요.

그러면 뷰가 아래처럼 각 노드가 연산자를 나타내는 그래프 형식으로 multi-stage 통계만 표시하도록 변경돼요. 각 노드 안에서 연산자 유형과 해당 연산자에 대해 수집된 통계를 볼 수 있어요. 노드는 연산자 간 관계를 나타내는 간선(edge)으로 연결돼요. 부모 연산자는 자식 위에 있고, 간선의 너비는 자식 연산자에 소요된 시간을 나타내요.

예를 들어 ColocatedJoinQuickStart의 다음 쿼리는:

select * 
from userAttributes a 
join userGroups g
on a.userUUID = g.userUUID
join userFactEvents fe
on fe.userUUID = g.userUUID

다음 그래프를 만들어요:

여기에서 5개의 스테이지가 있음을 볼 수 있어요(각 MAILBOX_SEND 연산자 하나씩). 시간의 상당 부분이 스테이지 1의 HASH_JOIN에서 소요되고, 그 다음 userFactEvents 읽기가 이어져요. 또한 스테이지 5(userFactEvents에서 읽는 스테이지)가 40000 행을 반환하는 반면 다른 스테이지는 2494 행을 반환하는 것을 볼 수 있어요. 따라서 Optimizing joins에서 설명한 것처럼 조인의 오른쪽에 더 작은 테이블을 두는 것이 더 좋아요. 그래서 쿼리는 다음과 같이 작성하면 더 빨라요:

select *
from userFactEvents fe
join (
    select *
    from userAttributes a
    join userGroups g
    on a.userUUID = g.userUUID
) as g
on fe.userUUID = g.userUUID

기본적으로 시각화 도구는 가장 중요한 통계만 표시해요. 모든 통계를 표시하려면 시각화 도구의 왼쪽 하단에 있는 Show details 버튼을 클릭해요.

그려지는 그래프는 보통 트리 모양 구조지만, spools를 사용할 때처럼 일부 경우에는 방향성 비순환 그래프(DAG)일 수 있어요.

JSON 형식

Pinot UI 통계 시각화 도구는 multi-stage 통계를 보는 편리한 방법이지만, 때로는 원시 JSON 형식을 보고 싶을 수도 있어요. 예를 들어 통계를 프로그래밍 방식으로 분석하거나 다른 시각화 도구를 사용하고 싶을 수 있어요. 그러려면 쿼리의 JSON 응답에서 stageStats 필드를 읽으면 돼요.

쿼리가 스트리밍 통계 전송(SET streamStats=true 또는 broker 기본값 pinot.broker.mse.stream.stats=true)을 사용할 때, 응답에는 streamStatsCoverage 배열도 포함돼요. 이 배열은 스테이지 ID로 인덱싱되며 몇 명의 작업자가 깨끗하게 응답했는지, 몇 명이 broker가 병합할 수 없는 통계를 보고했는지, 그 스테이지에 몇 명의 작업자가 여전히 누락되었는지 보고해요.

스트리밍 모드에서 mailbox send 연산자는 통계를 해당 블록에 붙이기 전에 진행 중인 end-of-stream 호출을 계산해요. 이는 부모의 마지막 호출이 아직 등록되지 않았다는 이유만으로 파생된 selfExecutionTimeMs, selfClockTimeMs, selfAllocatedMB, selfGcTimeMs 값이 음수가 되는 것을 방지해요. mailbox send 스냅샷에는 통계를 직렬화하고 end-of-stream 블록을 전달하는 데 사용된 이후 시간은 포함되지 않아요.

{
  "streamStatsCoverage": [
    null,
    {"responded": 4, "mergeFailed": 0, "missing": 0},
    {"responded": 3, "mergeFailed": 0, "missing": 1}
  ]
}

예를 들어 이전 섹션에서 사용한 것과 같은 쿼리는 다음과 같은 stageStats를 반환해요:

{
  ...,
  "stageStats": {
    "type": "MAILBOX_RECEIVE",
    "executionTimeMs": 18,
    "emittedRows": 2494,
    "fanIn": 4,
    "rawMessages": 18,
    "deserializedBytes": 219393,
    "upstreamWaitMs": 80,
    "children": [
      {
        "type": "MAILBOX_SEND",
        "executionTimeMs": 75,
        "emittedRows": 2494,
        "stage": 1,
        "parallelism": 4,
        "fanOut": 1,
        "rawMessages": 14,
        "serializedBytes": 216854,
        "serializationTimeMs": 4,
        "children": [
          {
            "type": "HASH_JOIN",
            "executionTimeMs": 70,
            "emittedRows": 2494,
            "timeBuildingHashTableMs": 73,
            "children": [
              {
                "type": "MAILBOX_RECEIVE",
                "emittedRows": 2494,
                "fanIn": 4,
                "inMemoryMessages": 18,
                "rawMessages": 12,
                "deserializedBytes": 2085,
                "upstreamWaitMs": 131,
                "children": [
                  {
                    "type": "MAILBOX_SEND",
                    "executionTimeMs": 23,
                    "emittedRows": 2494,
                    "stage": 2,
                    "parallelism": 4,
                    "fanOut": 4,
                    "inMemoryMessages": 14,
                    "children": [
                      {
                        "type": "HASH_JOIN",
                        "executionTimeMs": 21,
                        "emittedRows": 2494,
                        "timeBuildingHashTableMs": 20,
                        "children": [
                          {
                            "type": "MAILBOX_RECEIVE",
                            "executionTimeMs": 1,
                            "emittedRows": 10000,
                            "fanIn": 2,
                            "inMemoryMessages": 6,
                            "rawMessages": 18,
                            "deserializedBytes": 221576,
                            "deserializationTimeMs": 3,
                            "upstreamWaitMs": 61,
                            "children": [
                              {
                                "type": "MAILBOX_SEND",
                                "executionTimeMs": 11,
                                "emittedRows": 10000,
                                "stage": 3,
                                "parallelism": 2,
                                "fanOut": 4,
                                "inMemoryMessages": 4,
                                "rawMessages": 12,
                                "serializedBytes": 220890,
                                "serializationTimeMs": 6,
                                "children": [
                                  {
                                    "type": "LEAF",
                                    "table": "userAttributes",
                                    "executionTimeMs": 8,
                                    "emittedRows": 10000,
                                    "numDocsScanned": 10000,
                                    "totalDocs": 10000,
                                    "numEntriesScannedPostFilter": 40000,
                                    "numSegmentsQueried": 4,
                                    "numSegmentsProcessed": 4,
                                    "numSegmentsMatched": 4,
                                    "threadCpuTimeNs": 4733524
                                  }
                                ]
                              }
                            ]
                          },
                          {
                            "type": "MAILBOX_RECEIVE",
                            "executionTimeMs": 7,
                            "emittedRows": 2494,
                            "fanIn": 2,
                            "inMemoryMessages": 10,
                            "rawMessages": 26,
                            "deserializedBytes": 46102,
                            "deserializationTimeMs": 3,
                            "upstreamWaitMs": 40,
                            "children": [
                              {
                                "type": "MAILBOX_SEND",
                                "executionTimeMs": 4,
                                "emittedRows": 2494,
                                "stage": 4,
                                "parallelism": 2,
                                "fanOut": 4,
                                "inMemoryMessages": 8,
                                "rawMessages": 20,
                                "serializedBytes": 45422,
                                "serializationTimeMs": 4,
                                "children": [
                                  {
                                    "type": "LEAF",
                                    "table": "userGroups",
                                    "executionTimeMs": 5,
                                    "emittedRows": 2494,
                                    "numDocsScanned": 2494,
                                    "totalDocs": 2494,
                                    "numEntriesScannedPostFilter": 4988,
                                    "numSegmentsQueried": 8,
                                    "numSegmentsProcessed": 8,
                                    "numSegmentsMatched": 8,
                                    "threadCpuTimeNs": 1423051
                                  }
                                ]
                              }
                            ]
                          }
                        ]
                      }
                    ]
                  }
                ]
              },
              {
                "type": "MAILBOX_RECEIVE",
                "executionTimeMs": 48,
                "emittedRows": 40000,
                "fanIn": 2,
                "inMemoryMessages": 10,
                "rawMessages": 30,
                "deserializedBytes": 1755012,
                "deserializationTimeMs": 7,
                "upstreamWaitMs": 133,
                "children": [
                  {
                    "type": "MAILBOX_SEND",
                    "executionTimeMs": 30,
                    "emittedRows": 40000,
                    "stage": 5,
                    "parallelism": 2,
                    "fanOut": 4,
                    "inMemoryMessages": 8,
                    "rawMessages": 24,
                    "serializedBytes": 1754652,
                    "serializationTimeMs": 15,
                    "children": [
                      {
                        "type": "LEAF",
                        "table": "userFactEvents",
                        "executionTimeMs": 21,
                        "emittedRows": 40000,
                        "numDocsScanned": 40000,
                        "totalDocs": 40000,
                        "numEntriesScannedPostFilter": 320000,
                        "numSegmentsQueried": 8,
                        "numSegmentsProcessed": 8,
                        "numSegmentsMatched": 8,
                        "threadCpuTimeNs": 32716947
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  },
  ...
}

트리의 각 노드는 실행되는 하나의 연산을 나타내며, 트리 구조 형태는 EXPLAIN PLAN 명령으로 얻을 수 있는 쿼리의 논리 계획과 유사하지만(동일하지는 않음) 그래요.

JSON 형식을 사용할 때 통계는 항상 트리 구조예요. spools를 사용할 때도 마찬가지예요. 이 경우 spool된 스테이지는 트리에 여러 번 포함돼요. 각 연산자의 stage 필드를 보고 같은 스테이지 ID를 가진 연산자들을 연결해 DAG를 직접 만들어야 해요.

더 알아보기 (Learn more)