쿼리 계획 및 타임라인

쿼리 작업 내에 삽입된 BigQuery에는 진단 쿼리 계획과 타이밍 정보가 포함되어 있습니다. 이는 다른 데이터베이스 및 분석 시스템에서 EXPLAIN과 같은 문으로 제공되는 정보와 유사합니다. 이 정보는 jobs.get과 같은 메서드의 API 응답에서 가져올 수 있습니다.

장기간 실행 중인 쿼리의 경우 BigQuery는 통계를 주기적으로 업데이트합니다. 이러한 업데이트는 작업 상태가 폴링되는 비율과 상관없이 진행되지만 일반적으로 최소 주기는 30초입니다. 또한 실행 리소스를 사용하지 않는 쿼리 작업(예: 테스트 실행 요청 또는 캐시된 결과에서 제공할 수 있는 결과)에는 추가 진단 정보가 포함되지 않지만 다른 통계가 포함될 수 있습니다.

배경

BigQuery는 쿼리 작업 실행 시 선언적 SQL 문을 실행 그래프로 변환하고 일련의 쿼리 스테이지로 세분화하는데, 각 쿼리 스테이지에는 더 세분화된 실행 단계 조합이 있습니다. BigQuery는 크게 분산된 병렬 아키텍처를 사용하여 이러한 쿼리를 실행하며, 스테이지는 많은 잠재적 작업자들이 동시에 실행할 수 있는 작업 단위를 모델링합니다. 스테이지는 빠른 분산 셔플 아키텍처를 사용하여 서로 통신합니다.

쿼리 계획 내에서 작업 단위작업자라는 용어는 특히 동시 로드에 대한 정보를 전달하기 위해 사용됩니다. BigQuery 내의 다른 곳에서는 컴퓨팅, 메모리, I/O 리소스를 비롯한 쿼리 실행의 여러 측면을 추상화한 '슬롯'이라는 용어가 사용될 수 있습니다. 최상위 작업 통계는 이 추상화된 계정을 통해 쿼리의 totalSlotMs 추정을 사용함으로써 개별 쿼리 비용의 추정치를 제공합니다.

쿼리 실행 아키텍처의 또 다른 중요한 속성은 동적이라는 것입니다. 즉, 쿼리가 실행되는 동안 쿼리 계획이 수정될 수 있습니다. 쿼리가 실행 중인 동안에 스테이지를 도입하는 목적은 쿼리 작업자 전체의 데이터 배포를 향상시키기 위한 경우가 많습니다. 실제로 이러한 쿼리 계획에서 스테이지에는 일반적으로 리파티션 스테이지라는 라벨이 지정됩니다.

쿼리 계획 외에도 쿼리 작업에는 쿼리 작업자 사이에서 완료, 보류, 활성 상태의 작업 단위에 대한 설명을 제공하는 실행 타임라인이 노출됩니다. 쿼리에는 활성 작업자를 포함한 여러 스테이지가 동시에 있을 수 있으므로, 타임라인은 쿼리의 전반적인 진행 상황을 보여주기 위한 것입니다.

Google Cloud 콘솔을 사용하여 정보 보기

Google Cloud 콘솔에서 실행 세부정보 버튼(결과 버튼 옆에 있음)을 클릭하면 완료된 쿼리에 대한 쿼리 계획의 세부정보를 볼 수 있습니다.

쿼리 계획

쿼리 계획 정보

API 응답 내에서 쿼리 계획은 쿼리 스테이지 목록으로 표시됩니다. 목록의 각 항목은 스테이지별 개요 통계, 세부 단계 정보, 스테이지 타이밍 분류를 보여줍니다. Google Cloud 콘솔 내부에서 모든 세부정보가 렌더링되지는 않지만 API 응답 내에 모든 정보가 있을 수 있습니다.

스테이지 개요

각 스테이지의 개요 필드에 포함될 수 있는 항목은 다음과 같습니다.

API 필드 설명
id 스테이지의 고유 숫자 ID입니다.
name 스테이지의 간단한 요약 이름입니다. 스테이지 내의 steps는 실행 단계에 대한 추가 세부정보를 제공합니다.
status 스테이지의 실행 상태입니다. 가능한 상태로는 PENDING, RUNNING, COMPLETE, FAILED, CANCELLED가 있습니다.
inputStages 스테이지의 종속성 그래프를 구성하는 ID 목록입니다. 예를 들어 JOIN 스테이지에는 주로 JOIN 관계의 왼쪽과 오른쪽에서 데이터를 준비하는 독립 스테이지 2개가 필요합니다.
startMs 스테이지 내에서 첫 작업자가 실행을 시작한 시간을 나타내는 이폭 밀리초 단위의 타임스탬프입니다.
endMs 마지막 작업자가 실행을 완료한 시간을 나타내는 이폭 밀리초 단위의 타임스탬프입니다.
steps 스테이지 내의 세부적인 실행 단계 목록입니다. 자세한 내용은 다음 섹션을 참조하세요.
recordsRead 모든 스테이지 작업자에 대한 레코드 수 기준의 스테이지 입력 크기입니다.
recordsWritten 모든 스테이지 작업자에 대한 레코드 수 기준의 스테이지 출력 크기입니다.
parallelInputs 스테이지에서 동시 로드가 가능한 작업 단위 수입니다. 스테이지 및 쿼리에 따라 테이블 내의 열 형식 세그먼트 수 또는 중간 셔플 내의 파티션 수수를 나타낼 수 있습니다.
completedParallelInputs 완료된 스테이지 내의 작업 단위 수입니다. 일부 쿼리의 경우 스테이지 내 일부 입력을 완료하지 않아도 스테이지가 완료될 수 있습니다.
shuffleOutputBytes 쿼리 스테이지 내 모든 작업자에 대한 총 바이트 수를 나타냅니다.
shuffleOutputBytesSpilled 스테이지 간에 중요한 데이터를 전송하는 쿼리를 디스크 기반 전송으로 대체해야 할 수도 있습니다. 분산된 바이트 통계는 디스크로 분산된 데이터의 양을 알려줍니다. 이는 최적화 알고리즘에 따라 다르므로 특정 쿼리에 대해 확정적이지 않습니다.

스테이지별 타이밍 분류

쿼리 스테이지는 상대적 형태 및 절대적 형태로 스테이지 타이밍 분류를 제공합니다. 각 실행 스테이지는 독립 작업자 1명 이상이 수행한 작업을 나타내므로, 평균 시간과 가장 느린 시간으로 정보가 제공됩니다. 즉, 스테이지의 모든 작업자에 대한 평균 성과와 지정된 분류에서 가장 시간이 오래 걸린 롱테일 작업자의 성과가 표시됩니다. 평균 및 최대 시간은 절대 표시와 상대 표시로 더 세분화됩니다. 비율 기반 통계의 경우 임의의 세그먼트에 있는 작업자 중 가장 시간이 오래 걸린 작업자 시간의 비율로 데이터가 제공됩니다.

Google Cloud 콘솔은 상대적 타이밍 표시를 사용하여 스테이지 타이밍을 나타냅니다.

스테이지 타이밍 정보는 다음과 같이 보고됩니다.

상대적 타이밍 절대적 타이밍 비율 분자
waitRatioAvg waitMsAvg 평균 작업자가 예약되기 위해 대기한 시간입니다.
waitRatioMax waitMsMax 가장 시간이 오래 걸린 작업자가 예약되기 위해 대기한 시간입니다.
readRatioAvg readMsAvg 평균 작업자가 입력 데이터를 읽는 데 걸린 시간입니다.
readRatioMax readMsMax 가장 시간이 오래 걸린 작업자가 입력 데이터를 읽는 데 걸린 시간입니다.
computeRatioAvg computeMsAvg 평균 작업자가 CPU의 제한을 받은 시간입니다.
computeRatioMax computeMsMax 가장 시간이 오래 걸린 작업자가 CPU의 제한을 받은 시간입니다.
writeRatioAvg writeMsAvg 평균 작업자가 출력 데이터를 쓰는 데 걸린 시간입니다.
writeRatioMax writeMsMax 가장 시간이 오래 걸린 작업자가 출력 데이터를 쓰는 데 걸린 시간입니다.

단계 개요

단계에는 스테이지 내의 각 작업자가 실행하는 작업이 포함되며 순서가 지정된 작업 목록으로 제공됩니다. 각 단계 작업에는 카테고리가 있으며 일부 작업은 보다 세부적인 정보를 제공합니다. 쿼리 계획의 작업 카테고리는 다음과 같습니다.

단계 카테고리 설명
READ 입력 테이블 또는 중간 셔플에서 열 한 개 이상 읽기. 읽은 처음 16개 열만 단계 세부정보에서 반환됩니다.
WRITE 출력 테이블 또는 중간 셔플에 열을 하나 이상 쓰기, 스테이지에서 HASH 파티션을 나눈 출력의 경우, 파티션 키에 사용된 열도 포함됩니다.
COMPUTE 표현식 평가 및 SQL 함수
FILTER WHERE, OMIT IF, HAVING 절에서 사용됩니다.
SORT 열 키와 정렬 순서가 포함된 ORDER BY 연산
AGGREGATE GROUP BY 또는 COUNT과 같은 절의 집계를 구현합니다.
LIMIT LIMIT 절을 구현합니다.
JOIN JOIN과 같은 절의 조인을 구현합니다. 조인 유형과 조인 조건을 포함할 수 있습니다.
ANALYTIC_FUNCTION 윈도우 함수 호출('분석 함수'라고도 함)
USER_DEFINED_FUNCTION 사용자 정의 함수 호출

단계 세부정보 이해하기

BigQuery는 단계 내에서 각 단계가 수행한 작업을 설명하는 단계 세부정보를 제공합니다. 단계의 단계를 이해해야 쿼리 성능 문제의 소스를 파악할 수 있습니다.

단계의 단계 세부정보를 확인하려면 다음 단계를 따르세요.

  1. 쿼리 결과 창에서 실행 그래프를 클릭합니다.

    실행 그래프 탭

  2. 관심 있는 단계를 클릭하여 단계 정보가 포함된 패널을 엽니다.

  3. 단계 정보가 있는 패널에서 단계 세부정보 섹션으로 이동합니다.

    단계 세부정보가 포함된 실행 그래프

각 단계는 단계에서 수행한 작업을 설명하는 하위 단계로 구성됩니다. 하위 단계에서는 변수를 사용하여 단계 간의 관계를 설명합니다. 변수는 달러 기호로 시작하고 그 뒤에 고유한 숫자가 옵니다.

다음은 단계 간에 공유되는 변수가 있는 단계의 단계 세부정보의 예입니다.

READ
$30:l_orderkey, $31:l_quantity
FROM lineitem

AGGREGATE
GROUP BY $100 := $30
$70 := SUM($31)

WRITE
$100, $70
TO __stage00_output
BY HASH($100)

예의 단계 세부정보는 다음을 수행합니다.

  1. 스테이지가 $30$31 변수를 사용하여 lineitem 테이블에서 l_orderkeyl_quantity 열을 읽었습니다.

  2. $30$31 변수에 집계된 스테이지는 집계 결과를 각각 $100$70 변수에 저장합니다.

  3. 스테이지가 $100$70 변수의 결과를 셔플에 썼습니다. 스테이지는 $100를 사용하여 스테이지의 결과를 셔플로 정렬했습니다.

쿼리의 실행 그래프가 복잡하여 완전한 단계 단계 세부정보를 제공하면 쿼리 정보를 가져올 때 페이로드 크기 문제가 발생하는 경우 BigQuery에서 단계 세부정보를 자를 수 있습니다.

질문 텍스트로 단계 이해하기

프리뷰 중에 지원을 받으려면 bq-query-inspector-feedback@google.com으로 이메일을 보내세요.

스테이지의 단계가 쿼리와 어떤 관련이 있는지 파악하기 어려울 수 있습니다. 쿼리 텍스트 섹션에는 일부 단계가 원래 쿼리 텍스트와 어떤 관련이 있는지 표시됩니다.

쿼리 텍스트 섹션에서는 원본 쿼리 텍스트의 여러 부분을 강조 표시하고 강조 표시된 원본 쿼리 텍스트 바로 앞에 있는 쿼리 텍스트로 다시 매핑되는 단계를 보여줍니다. 강조 표시된 원래 질문 텍스트 부분 바로 위의 단계만 강조 표시된 질문 텍스트에 적용됩니다.

단계 쿼리 텍스트가 포함된 실행 그래프입니다.

스크린샷 예시에는 다음과 같은 매핑이 표시됩니다.

  • AGGREGATE: GROUP BY $100 := $30 단계는 select l_orderkey 쿼리 텍스트에 다시 매핑됩니다.

  • READ: FROM lineitem 단계는 select ... from lineitem 쿼리 텍스트에 다시 매핑됩니다.

  • AGGREGATE: $70 := SUM($31) 단계는 sum(l_quantity) 쿼리 텍스트에 다시 매핑됩니다.

일부 단계는 쿼리 텍스트에 다시 매핑할 수 없습니다.

쿼리에서 뷰를 사용하고 단계의 단계에 뷰의 쿼리 텍스트에 대한 매핑이 있는 경우 쿼리 텍스트 섹션에 뷰 이름과 뷰의 쿼리 텍스트가 매핑과 함께 표시됩니다. 하지만 뷰가 삭제되거나 뷰에 대한 bigquery.tables.get IAM 권한이 손실되면 쿼리 텍스트 섹션에 뷰의 단계 단계 매핑이 표시되지 않습니다.

해석 및 최적화 단계

다음 섹션에서는 쿼리 계획의 단계를 해석하는 방법을 설명하고 쿼리를 최적화하는 방법을 제공합니다.

READ 단계

READ 단계는 스테이지가 처리를 위해 데이터에 액세스하고 있음을 의미합니다. 데이터는 쿼리에서 참조되는 테이블 또는 셔플 메모리에서 직접 읽을 수 있습니다. 이전 단계의 데이터를 읽으면 BigQuery는 셔플 메모리에서 데이터를 읽습니다. 스캔된 데이터 양은 주문형 슬롯을 사용할 때 비용에 영향을 미치고 예약된 슬롯을 사용할 때 성능에 영향을 미칩니다.

잠재적인 성능 문제

  • 파티셔닝되지 않은 테이블의 대규모 스캔: 쿼리에 데이터의 일부만 필요한 경우 테이블 스캔이 비효율적일 수 있습니다. 파티셔닝이 좋은 최적화 전략이 될 수 있습니다.
  • 필터 비율이 작은 대규모 테이블 스캔: 필터가 스캔된 데이터를 효과적으로 줄이지 못하고 있음을 나타냅니다. 필터 조건을 수정하는 것이 좋습니다.
  • 디스크에 셔플 바이트가 스필 오버됨: 클러스터에서 유사한 데이터를 유지할 수 있는 클러스터링과 같은 최적화 기법을 사용하여 데이터가 효과적으로 저장되지 않았음을 나타냅니다.

최적화

  • 타겟팅된 필터링: WHERE 절을 전략적으로 사용하여 쿼리에서 최대한 빨리 관련 없는 데이터를 필터링합니다. 이렇게 하면 쿼리에서 처리해야 하는 데이터 양이 줄어듭니다.
  • 파티션 나누기 및 클러스터링: BigQuery는 테이블 파티션 나누기 및 클러스터링을 사용하여 특정 데이터 세그먼트를 효율적으로 찾습니다. READ 단계에서 스캔되는 데이터를 최소화하려면 일반적인 쿼리 패턴을 기반으로 테이블을 파티션으로 나누고 클러스터링해야 합니다.
  • 관련 열 선택: SELECT * 문을 사용하지 마세요. 대신 특정 열을 선택하거나 SELECT * EXCEPT를 사용하여 불필요한 데이터를 읽지 않도록 하세요.
  • 구체화된 뷰: 구체화된 뷰는 자주 사용되는 집계를 미리 계산하고 저장할 수 있으므로 이러한 뷰를 사용하는 쿼리의 READ 단계에서 기본 테이블을 읽을 필요성이 줄어들 수 있습니다.

COMPUTE 단계

COMPUTE 단계에서 BigQuery는 데이터에 대해 다음 작업을 실행합니다.

  • 계산, 비교, 논리 연산을 비롯하여 쿼리의 SELECT, WHERE, HAVING 및 기타 절의 표현식을 평가합니다.
  • 기본 제공 SQL 함수와 사용자 정의 함수를 실행합니다.
  • 쿼리의 조건을 기반으로 데이터 행을 필터링합니다.

최적화

쿼리 계획을 통해 COMPUTE 단계의 병목 현상을 확인할 수 있습니다. 계산량이 많거나 처리된 행 수가 많은 단계를 찾습니다.

  • COMPUTE 단계와 데이터 양의 연관성 비교: 한 단계에서 상당한 계산이 이루어지고 대량의 데이터가 처리되는 경우 최적화의 좋은 후보가 될 수 있습니다.
  • 데이터 왜곡: 계산 최대값이 계산 평균보다 훨씬 높은 단계는 해당 단계에서 일부 데이터 슬라이스를 처리하는 데 불균형한 시간이 소요되었음을 나타냅니다. 데이터 분포를 살펴보고 데이터가 편향되어 있는지 확인해 보세요.
  • 데이터 유형 고려: 열에 적절한 데이터 유형을 사용합니다. 예를 들어 문자열 대신 정수, 날짜 및 시간, 타임스탬프를 사용하면 성능이 향상될 수 있습니다.

WRITE 단계

WRITE 단계는 중간 데이터와 최종 출력에 적용됩니다.

  • 셔플 메모리에 쓰기: 다단계 쿼리에서 WRITE 단계는 처리된 데이터를 추가 처리를 위해 다른 단계로 전송하는 경우가 많습니다. 이는 여러 소스의 데이터를 결합하거나 집계하는 셔플 메모리의 일반적인 경우입니다. 이 단계에서 작성된 데이터는 일반적으로 최종 출력이 아닌 중간 결과입니다.
  • 최종 출력: 쿼리 결과가 대상 테이블 또는 임시 테이블에 기록됩니다.

HASH 파티션 나누기

쿼리 계획의 단계에서 해시 파티션 출력에 데이터를 쓰면 BigQuery는 출력에 포함된 열과 파티션 키로 선택된 열을 씁니다.

최적화

WRITE 단계 자체는 직접 최적화할 수 없지만 역할을 이해하면 이전 단계에서 잠재적인 병목 현상을 파악하는 데 도움이 됩니다.

  • 쓰기 데이터 최소화: 필터링 및 집계를 사용하여 이전 단계를 최적화하여 이 단계에서 쓰기되는 데이터의 양을 줄이는 데 집중합니다.
  • 파티셔닝: 테이블 파티셔닝은 쓰기에 큰 도움이 됩니다. 쓰는 데이터가 특정 파티션으로 제한되면 BigQuery에서 더 빠르게 쓸 수 있습니다.

    DML 문에 테이블 파티션 열에 대한 정적 조건이 있는 WHERE 절이 있으면 BigQuery는 관련 테이블 파티션만 수정합니다.

  • 비정규화 트레이드 오프: 비정규화로 인해 중간 WRITE 단계에서 결과 집합이 작아질 수 있습니다. 하지만 스토리지 사용량 증가 및 데이터 일관성 문제와 같은 단점도 있습니다.

JOIN 단계

JOIN 단계에서 BigQuery는 두 데이터 소스의 데이터를 결합합니다. 조인에는 조인 조건이 포함될 수 있습니다. 조인은 리소스 집약적입니다. BigQuery에서 대량 데이터를 조인할 때 조인 키는 동일한 슬롯에 정렬되도록 독립적으로 셔플되므로 각 슬롯에서 로컬로 조인이 실행됩니다.

JOIN 단계의 쿼리 계획에는 일반적으로 다음 세부정보가 표시됩니다.

  • 조인 패턴: 사용된 조인 유형을 나타냅니다. 각 유형은 조인된 테이블에서 결과 집합에 포함되는 행 수를 정의합니다.
  • 조인 열: 데이터 소스 간에 행을 일치시키는 데 사용되는 열입니다. 열 선택은 조인 성능에 매우 중요합니다.

패턴 결합

  • 브로드캐스트 조인: 일반적으로 더 작은 테이블이 단일 작업자 노드 또는 슬롯의 메모리에 맞을 수 있는 경우 BigQuery는 조인을 효율적으로 실행하기 위해 다른 모든 노드에 브로드캐스트할 수 있습니다. 단계 세부정보에서 JOIN EACH WITH ALL을 찾습니다.
  • 해시 조인: 테이블이 크거나 브로드캐스트 조인이 적합하지 않은 경우 해시 조인을 사용할 수 있습니다. BigQuery는 해시 및 셔플 작업을 사용하여 일치하는 키가 동일한 슬롯에 배치되도록 왼쪽 및 오른쪽 테이블을 셔플하여 로컬 조인을 실행합니다. 해시 조인은 데이터를 이동해야 하므로 비용이 많이 드는 작업이지만 해시 간에 행을 효율적으로 일치시킬 수 있습니다. 단계 세부정보에서 JOIN EACH WITH EACH을 찾습니다.
  • 자체 조인: 테이블이 자체에 조인되는 SQL 안티패턴입니다.
  • 교차 조인: 입력보다 큰 출력 데이터를 생성하므로 심각한 성능 문제를 일으킬 수 있는 SQL 안티패턴입니다.
  • 편향된 조인: 한 테이블의 조인 키에 걸친 데이터 분포가 매우 편향되어 성능 문제가 발생할 수 있습니다. 쿼리 계획에서 최대 컴퓨팅 시간이 평균 컴퓨팅 시간보다 훨씬 큰 사례를 찾습니다. 자세한 내용은 카디널리티가 높은 조인파티션 기울기를 참고하세요.

디버깅

  • 대량의 데이터: 쿼리 계획에 JOIN 단계에서 처리되는 데이터가 상당한 것으로 표시되면 조인 조건과 조인 키를 조사합니다. 필터링하거나 더 선택적인 조인 키를 사용하는 것이 좋습니다.
  • 데이터 분포의 왜곡: 조인 키의 데이터 분포를 분석합니다. 테이블 하나가 매우 치우쳐 있는 경우 쿼리 분할 또는 사전 필터링과 같은 전략을 살펴봅니다.
  • 카디널리티가 높은 조인: 왼쪽 및 오른쪽 입력 행 수보다 훨씬 더 많은 행을 생성하는 조인은 쿼리 성능을 크게 저하시킬 수 있습니다. 매우 많은 행을 생성하는 조인은 피하세요.
  • 잘못된 테이블 순서: INNER 또는 LEFT과 같은 적절한 조인 유형을 선택하고 쿼리의 요구사항에 따라 테이블을 가장 큰 것부터 가장 작은 것 순으로 정렬했는지 확인합니다.

최적화

  • 선택적 조인 키: 조인 키의 경우 가능하면 STRING 대신 INT64를 사용합니다. STRING 비교는 문자열의 각 문자를 비교하므로 INT64 비교보다 느립니다. 정수는 비교가 한 번만 필요합니다.
  • 조인 전 필터링: 조인 전에 개별 테이블에 WHERE 절 필터를 적용합니다. 이렇게 하면 조인 작업에 포함된 데이터 양이 줄어듭니다.
  • 조인 열에서 함수 사용하지 않기: 조인 열에서 함수를 호출하지 마세요. 대신 ELT SQL 파이프라인을 사용하여 수집 또는 수집 후 프로세스 중에 테이블 데이터를 표준화합니다. 이 접근 방식을 사용하면 조인 열을 동적으로 수정할 필요가 없으므로 데이터 무결성을 훼손하지 않고 더 효율적으로 조인할 수 있습니다.
  • 자체 조인 방지: 자체 조인은 일반적으로 행 종속 관계를 계산하는 데 사용됩니다. 하지만 자체 조인을 사용하면 출력 행 수가 4배로 늘어날 수 있어 성능 문제가 발생할 수 있습니다. 자체 조인을 사용하는 대신 윈도우 (분석) 함수를 사용하는 것이 좋습니다.
  • 큰 테이블을 먼저: SQL 쿼리 옵티마이저가 조인의 어느 쪽으로 어떤 테이블을 배치할지 판단할 수 있지만 그래도 조인된 테이블을 적절히 정렬하는 것이 좋습니다. 권장사항은 가장 큰 테이블을 가장 먼저 배치하고 가장 작은 테이블을 그 뒤에 둔 다음 점점 크기를 줄여나가는 것입니다.
  • 비정규화: 경우에 따라 전략적으로 테이블을 비정규화(중복 데이터 추가)하면 조인을 완전히 없앨 수 있습니다. 하지만 이 접근 방식에는 스토리지 및 데이터 일관성 관련 절충안이 있습니다.
  • 파티션 나누기 및 클러스터링: 조인 키를 기준으로 테이블을 파티션으로 나누고 공동 배치된 데이터를 클러스터링하면 BigQuery가 관련 데이터 파티션을 타겟팅할 수 있으므로 조인 속도가 크게 빨라집니다.
  • 편향된 조인 최적화: 편향된 조인과 관련된 성능 문제를 방지하려면 테이블의 데이터를 가능한 한 빨리 사전 필터링하거나 쿼리를 두 개 이상의 쿼리로 분할하세요.

AGGREGATE 단계

AGGREGATE 단계에서 BigQuery는 데이터를 집계하고 그룹화합니다.

디버깅

  • 단계 세부정보: 집계의 입력 행 수와 출력 행 수, 셔플 크기를 확인하여 집계 단계에서 달성한 데이터 감소량과 데이터 셔플링이 포함되었는지 확인합니다.
  • 셔플 크기: 셔플 크기가 클수록 집계 중에 워커 노드 간에 상당한 양의 데이터가 이동했음을 나타낼 수 있습니다.
  • 데이터 분포 확인: 데이터가 파티션 간에 균등하게 분산되어 있는지 확인합니다. 데이터 분포가 편향되면 집계 단계에서 워크로드가 불균형해질 수 있습니다.
  • 집계 검토: 집계 절을 분석하여 필요하고 효율적인지 확인합니다.

최적화

  • 클러스터링: GROUP BY, COUNT 또는 기타 집계 절에서 자주 사용되는 열을 기준으로 테이블을 클러스터링합니다.
  • 파티셔닝: 쿼리 패턴에 맞는 파티셔닝 전략을 선택합니다. 집계 중에 스캔되는 데이터 양을 줄이려면 수집 시간으로 파티션을 나눈 테이블을 사용하는 것이 좋습니다.
  • 일찍 집계: 가능하면 쿼리 파이프라인 초기에 집계를 실행합니다. 이렇게 하면 집계 중에 처리해야 하는 데이터 양이 줄어들 수 있습니다.
  • 셔플 최적화: 셔플이 병목 현상인 경우 이를 최소화할 방법을 모색합니다. 예를 들어 테이블을 비정규화하거나 클러스터링을 사용하여 관련 데이터를 배치합니다.

특이 사례

  • DISTINCT 집계: DISTINCT 집계가 포함된 쿼리는 특히 대규모 데이터 세트에서 컴퓨팅 비용이 많이 들 수 있습니다. 대략적인 결과를 얻으려면 APPROX_COUNT_DISTINCT와 같은 대안을 고려하세요.
  • 그룹 수 많음: 쿼리에서 많은 수의 그룹이 생성되면 상당한 양의 메모리가 사용될 수 있습니다. 이러한 경우 그룹 수를 제한하거나 다른 집계 전략을 사용하는 것이 좋습니다.

REPARTITION 단계

REPARTITIONCOALESCE는 모두 BigQuery가 쿼리에서 셔플된 데이터에 직접 적용하는 최적화 기법입니다.

  • REPARTITION: 이 작업은 작업자 노드 간 데이터 분포의 균형을 다시 맞추는 것을 목표로 합니다. 셔플링 후 한 작업자 노드에 불균형적으로 많은 양의 데이터가 할당된다고 가정해 보겠습니다. REPARTITION 단계에서는 데이터를 더 균등하게 재분배하여 단일 작업자가 병목 현상이 되는 것을 방지합니다. 이는 조인과 같은 계산 집약적인 작업에 특히 중요합니다.
  • COALESCE: 이 단계는 셔플 후 작은 데이터 버킷이 많은 경우에 발생합니다. COALESCE 단계에서는 이러한 버킷을 더 큰 버킷으로 결합하여 수많은 작은 데이터 조각을 관리하는 데 따른 오버헤드를 줄입니다. 이는 매우 작은 중간 결과 집합을 처리할 때 특히 유용합니다.

쿼리 계획에 REPARTITION 또는 COALESCE 단계가 표시된다고 해서 쿼리에 문제가 있는 것은 아닙니다. 이는 BigQuery가 성능 향상을 위해 데이터 배포를 사전 예방적으로 최적화하고 있다는 신호인 경우가 많습니다. 하지만 이러한 작업이 반복적으로 표시되면 데이터가 본질적으로 치우쳐 있거나 쿼리로 인해 데이터 셔플링이 과도하게 발생하고 있음을 나타낼 수 있습니다.

최적화

REPARTITION 단계 수를 줄이려면 다음을 시도해 보세요.

  • 데이터 배포: 테이블이 효과적으로 파티션으로 나누어지고 클러스터링되어 있는지 확인합니다. 데이터가 잘 분산되면 셔플 후 심각한 불균형이 발생할 가능성이 줄어듭니다.
  • 쿼리 구조: 쿼리를 분석하여 데이터 왜곡의 잠재적 소스를 파악합니다. 예를 들어 단일 작업자에서 처리되는 데이터의 작은 하위 집합을 생성하는 매우 선택적인 필터나 조인이 있나요?
  • 조인 전략: 다양한 조인 전략을 실험하여 데이터 분포가 더 균형을 이루는지 확인합니다.

COALESCE 단계 수를 줄이려면 다음을 시도해 보세요.

  • 집계 전략: 쿼리 파이프라인에서 더 일찍 집계를 실행하는 것이 좋습니다. 이렇게 하면 COALESCE 단계를 유발할 수 있는 작은 중간 결과 집합의 수를 줄일 수 있습니다.
  • 데이터 양: 매우 작은 데이터 세트를 처리하는 경우 COALESCE는 큰 문제가 아닐 수 있습니다.

과도하게 최적화하지 마세요. 너무 이른 시기에 최적화하면 큰 이득을 얻지 못하면서 쿼리가 더 복잡해질 수 있습니다.

통합 쿼리에 대한 설명

통합 쿼리를 사용하면 EXTERNAL_QUERY 함수를 통해 쿼리 문을 외부 데이터 소스에 보낼 수 있습니다. 통합 쿼리에는 SQL 푸시다운이라는 최적화 기법이 적용되며 쿼리 계획에는 외부 데이터 소스(있는 경우)로 푸시다운된 작업이 표시됩니다. 예를 들어 다음 쿼리를 실행하는 경우:

SELECT id, name
FROM EXTERNAL_QUERY("<connection>", "SELECT * FROM company")
WHERE country_code IN ('ee', 'hu') AND name like '%TV%'

쿼리 계획에 다음 스테이지 단계가 표시됩니다.

$1:id, $2:name, $3:country_code
FROM table_for_external_query_$_0(
  SELECT id, name, country_code
  FROM (
    /*native_query*/
    SELECT * FROM company
  )
  WHERE in(country_code, 'ee', 'hu')
)
WHERE and(in($3, 'ee', 'hu'), like($2, '%TV%'))
$1, $2
TO __stage00_output

이 계획에서 table_for_external_query_$_0(...)EXTERNAL_QUERY 함수를 나타냅니다. 괄호 안에는 외부 데이터 소스가 실행하는 쿼리가 표시됩니다. 이를 바탕으로 다음 사항을 확인할 수 있습니다.

  • 외부 데이터 소스는 선택된 3개의 열만 반환합니다.
  • 외부 데이터 소스는 country_code'ee' 또는 'hu'인 행만 반환합니다.
  • LIKE 연산자는 푸시다운되지 않으며 BigQuery에서 평가됩니다.

비교를 위해 푸시다운이 없는 경우에는 쿼리 계획에서 다음 스테이지 단계를 표시합니다.

$1:id, $2:name, $3:country_code
FROM table_for_external_query_$_0(
  SELECT id, name, description, country_code, primary_address, secondary address
  FROM (
    /*native_query*/
    SELECT * FROM company
  )
)
WHERE and(in($3, 'ee', 'hu'), like($2, '%TV%'))
$1, $2
TO __stage00_output

이번에는 외부 데이터 소스가 company 테이블의 모든 열과 모든 행을 반환하고 BigQuery에서 필터링을 수행합니다.

타임라인 메타데이터

쿼리 타임라인은 특정 시점에서 진행 상황을 보고하여 전체 쿼리 진행의 스냅샷 보기를 제공합니다. 타임라인은 다음 세부정보를 보고하는 일련의 샘플로 표시됩니다.

필드 설명
elapsedMs 쿼리 실행이 시작된 후 경과된 시간(밀리초 단위)
totalSlotMs 쿼리에서 사용된 슬롯-밀리초 누적 표시
pendingUnits 예약 작업 및 실행 대기 중인 작업의 총 단위
activeUnits 작업자가 처리 중인 총 활성 작업 단위
completedUnits 이 쿼리를 실행하는 동안 완료된 총 작업 단위

예시 쿼리

다음 쿼리는 셰익스피어 공개 데이터 세트에서 행 수를 계산하고 '햄릿'을 참조하는 행으로 결과를 제한하는 두 번째 조건부 개수를 포함합니다.

SELECT
  COUNT(1) as rowcount,
  COUNTIF(corpus = 'hamlet') as rowcount_hamlet
FROM `publicdata.samples.shakespeare`

쿼리 계획을 보려면 실행 세부정보를 클릭합니다.

햄릿 쿼리 계획입니다.

색상 표시기는 모든 스테이지에서 모든 단계에 대한 상대적 타이밍을 나타냅니다.

실행 스테이지의 단계에 대해 자세히 알아보려면 을 클릭하여 스테이지 세부정보를 확장합니다.

햄릿 쿼리 계획 단계 세부정보입니다.

이 예시에서 세그먼트에서 가장 긴 시간은 스테이지 01에 있는 단일 작업자가 스테이지 00이 완료되기를 기다리는 시간이었습니다. 그 이유는 스테이지 01이 스테이지 00 입력에 종속되어 있고 첫 번째 스테이지가 중간 셔플에 출력을 쓰기 전까지 시작될 수 없기 때문입니다.

오류 보고

쿼리 작업이 실행 중간에 실패할 수 있습니다. 계획 정보가 주기적으로 업데이트되므로, 실행 그래프에서 오류가 발생한 위치를 관찰할 수 있습니다. Google Cloud 콘솔 내에서 성공 또는 실패한 단계는 단계 이름 옆에 있는 체크표시 또는 느낌표로 표시됩니다.

오류 해석 및 해결에 대한 자세한 내용은 문제해결 가이드를 참조하세요.

API 샘플 표현

쿼리 계획 정보는 작업 응답 정보에 포함되며, jobs.get을 호출하여 검색할 수 있습니다. 예를 들어 샘플 햄릿 쿼리를 반환하는 작업에 대한 JSON 응답의 다음 발췌물에는 쿼리 계획과 타임라인 정보가 모두 표시됩니다.

"statistics": {
  "creationTime": "1576544129234",
  "startTime": "1576544129348",
  "endTime": "1576544129681",
  "totalBytesProcessed": "2464625",
  "query": {
    "queryPlan": [
      {
        "name": "S00: Input",
        "id": "0",
        "startMs": "1576544129436",
        "endMs": "1576544129465",
        "waitRatioAvg": 0.04,
        "waitMsAvg": "1",
        "waitRatioMax": 0.04,
        "waitMsMax": "1",
        "readRatioAvg": 0.32,
        "readMsAvg": "8",
        "readRatioMax": 0.32,
        "readMsMax": "8",
        "computeRatioAvg": 1,
        "computeMsAvg": "25",
        "computeRatioMax": 1,
        "computeMsMax": "25",
        "writeRatioAvg": 0.08,
        "writeMsAvg": "2",
        "writeRatioMax": 0.08,
        "writeMsMax": "2",
        "shuffleOutputBytes": "18",
        "shuffleOutputBytesSpilled": "0",
        "recordsRead": "164656",
        "recordsWritten": "1",
        "parallelInputs": "1",
        "completedParallelInputs": "1",
        "status": "COMPLETE",
        "steps": [
          {
            "kind": "READ",
            "substeps": [
              "$1:corpus",
              "FROM publicdata.samples.shakespeare"
            ]
          },
          {
            "kind": "AGGREGATE",
            "substeps": [
              "$20 := COUNT($30)",
              "$21 := COUNTIF($31)"
            ]
          },
          {
            "kind": "COMPUTE",
            "substeps": [
              "$30 := 1",
              "$31 := equal($1, 'hamlet')"
            ]
          },
          {
            "kind": "WRITE",
            "substeps": [
              "$20, $21",
              "TO __stage00_output"
            ]
          }
        ]
      },
      {
        "name": "S01: Output",
        "id": "1",
        "startMs": "1576544129465",
        "endMs": "1576544129480",
        "inputStages": [
          "0"
        ],
        "waitRatioAvg": 0.44,
        "waitMsAvg": "11",
        "waitRatioMax": 0.44,
        "waitMsMax": "11",
        "readRatioAvg": 0,
        "readMsAvg": "0",
        "readRatioMax": 0,
        "readMsMax": "0",
        "computeRatioAvg": 0.2,
        "computeMsAvg": "5",
        "computeRatioMax": 0.2,
        "computeMsMax": "5",
        "writeRatioAvg": 0.16,
        "writeMsAvg": "4",
        "writeRatioMax": 0.16,
        "writeMsMax": "4",
        "shuffleOutputBytes": "17",
        "shuffleOutputBytesSpilled": "0",
        "recordsRead": "1",
        "recordsWritten": "1",
        "parallelInputs": "1",
        "completedParallelInputs": "1",
        "status": "COMPLETE",
        "steps": [
          {
            "kind": "READ",
            "substeps": [
              "$20, $21",
              "FROM __stage00_output"
            ]
          },
          {
            "kind": "AGGREGATE",
            "substeps": [
              "$10 := SUM_OF_COUNTS($20)",
              "$11 := SUM_OF_COUNTS($21)"
            ]
          },
          {
            "kind": "WRITE",
            "substeps": [
              "$10, $11",
              "TO __stage01_output"
            ]
          }
        ]
      }
    ],
    "estimatedBytesProcessed": "2464625",
    "timeline": [
      {
        "elapsedMs": "304",
        "totalSlotMs": "50",
        "pendingUnits": "0",
        "completedUnits": "2"
      }
    ],
    "totalPartitionsProcessed": "0",
    "totalBytesProcessed": "2464625",
    "totalBytesBilled": "10485760",
    "billingTier": 1,
    "totalSlotMs": "50",
    "cacheHit": false,
    "referencedTables": [
      {
        "projectId": "publicdata",
        "datasetId": "samples",
        "tableId": "shakespeare"
      }
    ],
    "statementType": "SELECT"
  },
  "totalSlotMs": "50"
},

실행 정보 사용

BigQuery 쿼리 계획은 서비스의 쿼리 실행 방법에 대한 정보를 제공하지만, 서비스의 관리 특성에 따라 일부 세부정보의 직접적인 조치 가능 여부가 제한됩니다. 서비스를 사용하면 많은 최적화가 자동으로 수행되며, 이러한 최적화는 미세 조정, 프로비저닝 및 모니터링을 위해 전문 지식을 갖춘 전담 직원이 필요할 수 있는 다른 환경과 다를 수 있습니다.

쿼리 실행과 성능을 향상시킬 수 있는 특정 기술에 대해서는 권장사항 문서를 참조하세요. 쿼리 계획과 타임라인 통계는 어떤 스테이지에 리소스 사용이 집중되는지 이해하는 데 유용할 수 있습니다. 예를 들어 입력 행보다 훨씬 많은 출력 행을 생성하는 JOIN 스테이지는 쿼리에서 일찍 필터링하는 것이 좋습니다.

또한 타임라인 정보는 지정된 쿼리가 독립적인 상태에서 느린 것인지 또는 같은 리소스를 놓고 경합하는 다른 쿼리의 영향으로 인해 느린 것인지 식별하는 데 유용할 수 있습니다. 쿼리 전체 기간 중에 활성 단위 수가 제한된 상태로 유지되지만 큐에 추가된 작업 단위 양이 높게 유지되는 것으로 관측될 경우 동시 쿼리 수를 줄이면 특정 쿼리의 전체 실행 시간을 크게 향상시킬 수 있습니다.