쿼리 계획 - Amazon Redshift

쿼리 계획

쿼리 계획은 쿼리를 실행하는 데 필요한 개별 작업에 대한 정보를 확인하는 용도로 사용할 수 있습니다. 하지만 쿼리 계획을 사용하기 앞서서 먼저 Amazon Redshift가 쿼리를 처리하거나 쿼리 계획을 작성하는 방식부터 이해하는 것이 좋습니다. 자세한 내용은 쿼리 계획 및 실행 워크플로우 섹션을 참조하세요.

쿼리 계획을 작성하려면 실제 쿼리 텍스트와 함께 EXPLAIN 명령을 실행하세요. 쿼리 계획은 다음과 같은 정보를 제공합니다.

  • 실행 엔진이 수행한 작업(상향식 결과 확인)

  • 각 작업이 수행한 단계 유형

  • 각 작업에서 사용되는 테이블과 열

  • 행의 수와 데이터 폭(바이트)을 기준으로 한 각 작업의 데이터 처리량

  • 상대적 작업 비용. 여기에서 비용이란 계획 내 각 단계의 상대적 실행 시간을 비교하는 척도를 말합니다. 그렇다고 비용이 실제 실행 시간이나 메모리 사용량에 대해 정확한 정보를 제공하는 것은 아니며, 마찬가지로 실행 계획 간 비교가 유의미하지도 않습니다. 단지 쿼리에서 가장 많은 리소스를 사용하는 작업이 무엇인지 알려주는 역할을 합니다.

EXPLAIN 명령은 실제로 쿼리를 실행하지는 않습니다. 쿼리가 현재 작업 조건에서 실행되는 경우 Amazon Redshift의 실행 계획을 보여줄 뿐입니다. 테이블 스키마 또는 데이터를 변경한 후 ANALYZE를 다시 실행하여 통계 메타데이터를 업데이트하면 쿼리 계획도 달라질 수 있습니다.

EXPLAIN에서 출력되는 쿼리 계획은 쿼리 실행을 단순하지만 종합적으로 나타냅니다. 병렬 쿼리 처리에 대한 세부 정보까지 표시하지는 않습니다. 세부 정보를 보려면 쿼리 자체를 실행한 후 SVL_QUERY_SUMMARY 또는 SVL_QUERY_REPORT 뷰에서 쿼리 요약 정보를 가져옵니다. 이러한 뷰를 사용하는 자세한 방법은 쿼리 요약 분석 섹션을 참조하세요.

다음은 EVENT 테이블에 대한 GROUP BY 쿼리를 실행할 경우를 나타낸 EXPLAIN 출력 예입니다.

explain select eventname, count(*) from event group by eventname; QUERY PLAN ------------------------------------------------------------------- XN HashAggregate (cost=131.97..133.41 rows=576 width=17) -> XN Seq Scan on event (cost=0.00..87.98 rows=8798 width=17)

EXPLAIN은 다음과 같이 각 작업에 대한 지표를 반환합니다.

비용

계획 내 작업을 비교하는데 유용한 상대 값입니다. 비용은 2개의 마침표로 구분된 소수 값 2개로 구성됩니다. 예를 들면 cost=131.97..133.41과 같습니다. 여기에서 첫 번째 값인 131.97은 위 작업의 첫 번째 행을 반환하는 상대적 비용을 나타냅니다. 그리고, 두 번째 값인 133.41은 위 작업을 완료하는 상대적 비용을 나타냅니다. 쿼리 계획 비용은 아래에서 위 방향으로 계획을 읽어가면서 누적됩니다. 따라서 위 예에서 HashAggregate 비용(131.97..133.41)은 아래 있는 Seq Scan 비용(0.00..87.98)이 포함된 것입니다.

반환될 것으로 예상되는 행의 수입니다. 위 예에서 스캔을 통해 반환된 것으로 예상되는 행의 수는 8,798개입니다. HashAggregate 연산자 자체에서는 576개의 행이 반환될 것으로 예상됩니다(결과 집합에서 중복 이벤트 이름은 무시했을 때).

참고

예상되는 행의 수는 ANALYZE 명령으로 생성된 유효 통계를 기준으로 합니다. 최근에 ANALYZE를 실행하지 않았다면 예상되는 행의 수는 신뢰성이 떨어집니다.

너비

평균 행의 예상 폭(바이트)입니다. 위 예에서 평균 행은 폭이 17바이트가 될 것으로 보입니다.

EXPLAIN 연산자

이번 섹션에서는 EXPLAIN 출력 시 가장 자주 표시되는 연산자에 대해서 간략히 설명합니다. 전체 연산자 목록은 SQL 명령 섹션에서 EXPLAIN 섹션을 참조하세요.

순차적 스캔 연산자

순차적 스캔 연산자(Seq Scan)는 테이블 스캔을 나타냅니다. Seq Scan이 처음부터 끝까지 테이블의 열을 각각 순차적으로 스캔하고 WHERE 절에서 각 행의 쿼리 제약 조건을 평가합니다.

조인 연산자

Amazon Redshift는 조인되는 테이블의 물리적 설계, 조인에 필요한 데이터의 위치, 쿼리 자체의 특정 요건을 기반으로 여러 가지 조인 연산자를 선택합니다.

  • 중첩 루프

    중첩 루프는 가장 덜 최적화된 조인으로서 주로 크로스 조인(데카르트 곱)과 일부 부등식 조인에 사용됩니다.

  • Hash Join 및 Hash

    일반적으로 중첩 루프 조인보다 빠른 해시 조인 및 해시는 내부 조인과 왼쪽 및 오른쪽 외부 조인에 사용됩니다. 이 연산자들은 조인 열이 둘 다 분산 키 정렬 키가 아닌 테이블을 조인할 때 사용됩니다. 해시 연산자는 조인의 이너 테이블에 대한 해시 테이블을 생성합니다. 해시 조인 연산자는 아우터 테이블을 읽고, 조인 열을 해시하고, 이너 해시 테이블과 일치하는 항목을 검색합니다.

  • 병합 조인

    일반적으로 가장 빠른 조인인 병합 조인은 내부 조인과 외부 조인에 사용되지만 전체 조인에는 사용되지 않습니다. 이 연산자는 조인 열이 둘 다 분산 키 정렬 키인 테이블을 조인할 때, 그리고 조인 테이블에서 정렬되지 않은 비율이 20% 미만일 때 사용됩니다. 정렬된 테이블 2개를 순서대로 읽고 나서 일치하는 행을 찾습니다. 정렬되지 않은 행의 비율을 보려면 SVV_TABLE_INFO 시스템 테이블에 대한 쿼리를 실행하세요.

  • 공간 조인

    일반적으로 공간 데이터의 근접성을 기반으로 한 고속 조인으로, GEOMETRYGEOGRAPHY 데이터 유형에 사용됩니다.

집계 연산자

쿼리 계획은 집계 함수 및 GROUP BY 작업과 관련된 쿼리에서 다음과 같은 연산자를 사용합니다.

  • 집계

    AVG, SUM 같은 스칼라 집계 함수에 사용되는 연산자입니다.

  • HashAggregate

    정렬 없이 분류된 집계 함수에 사용되는 연산자입니다.

  • GroupAggregate

    정렬과 함께 분류된 집계 함수에 사용되는 연산자입니다.

정렬 연산자

쿼리 계획은 쿼리가 결과 집합을 정렬하거나 병합해야 할 때 다음과 같은 연산자를 사용합니다.

  • 정렬

    ORDER BY 절을 비롯해 UNION 쿼리 및 조인, SELECT DISTINCT 쿼리, 창 함수에서 필요한 정렬 등 기타 정렬 작업을 평가합니다.

  • Merge

    병렬 작업을 통해 얻은 중간 정렬 결과에 따라 최종 정렬 결과를 산출합니다.

UNION, INTERSECT 및 EXCEPT 연산자

쿼리 계획은 UNION, INTERSECT 및 EXCEPT를 사용하는 집합 작업과 관련된 쿼리에서 다음과 같은 연산자를 사용합니다.

  • Subquery

    UNION 쿼리를 실행하는 데 사용됩니다.

  • Hash Intersect Distinct

    쿼리를 실행하는 데 사용됩니다.

  • SetOp Except

    EXCEPT(또는 MINUS) 쿼리를 실행하는 데 사용됩니다.

기타 연산자

그 밖에다 다음과 같은 연산자가 일반 쿼리의 EXPLAIN 출력에 자주 사용됩니다.

  • 고유

    SELECT DISTINCT 쿼리 및 UNION 쿼리에 대한 중복을 제거합니다.

  • 제한

    LIMIT 절을 처리합니다.

  • Window

    창 함수를 실행합니다.

  • 결과

    어떤 테이블 액세스에도 관련되지 않는 스칼라 함수를 실행합니다.

  • Subplan

    특정 하위 쿼리에 사용됩니다.

  • 네트워크

    추가 처리를 위해 중간 결과를 리더 노드로 전송합니다.

  • Materialize

    중첩 루프 조인과 일부 병합 조인에 대한 입력 행을 저장합니다.

EXPLAIN의 조인 유형

쿼리 옵티마이저는 쿼리 구조 및 기본 테이블에 따라 다른 조인 유형을 사용하여 테이블 데이터를 가져옵니다. 그러면 EXPLAIN 출력이 조인 유형과 사용된 테이블, 그리고 테이블 데이터의 클러스터 분산 방식을 참조하여 쿼리 처리 방법을 설명합니다.

조인 유형 예

다음 예들은 쿼리 옵티마이저가 사용할 수 있는 여러 가지 조인 유형을 보여주고 있습니다. 쿼리 계획에서 사용되는 조인 유형은 관련 테이블의 물리적 설계에 따라 다릅니다.

예: 두 테이블의 해시 조인

다음은 CATID 열을 기준으로 EVENT와 CATEGORY를 조인하는 쿼리입니다. CATID는 CATEGORY에서는 분산 및 정렬 키이지만 EVENT에서는 그렇지 않습니다. 이때는 해시 조인이 EVENT를 외부 테이블로, 그리고 CATEGORY를 내부 테이블로 실행됩니다. CATEGORY가 작은 테이블이기 때문에 플래너는 쿼리를 처리하면서 DS_BCAST_INNER를 사용하여 CATEGORY 테이블의 복사본을 컴퓨팅 노드로 브로드캐스팅합니다. 이 예에서는 조인 비용이 계획의 누적 비용 대부분을 차지합니다.

explain select * from category, event where category.catid=event.catid; QUERY PLAN ------------------------------------------------------------------------- XN Hash Join DS_BCAST_INNER (cost=0.14..6600286.07 rows=8798 width=84) Hash Cond: ("outer".catid = "inner".catid) -> XN Seq Scan on event (cost=0.00..87.98 rows=8798 width=35) -> XN Hash (cost=0.11..0.11 rows=11 width=49) -> XN Seq Scan on category (cost=0.00..0.11 rows=11 width=49)
참고

EXPLAIN 출력에서 정렬되어 있는 연산자 들여쓰기는 각 작업들이 서로 종속되지 않고 병렬 방식으로 시작될 수 있다는 것을 의미하기도 합니다. 위 예에서는 EVENT 테이블에 대한 스캔과 해시 작업이 정렬되어 있지만 EVENT 스캔을 시작하려면 해시 작업이 완전히 끝날 때까지 기다려야 합니다.

예: 두 테이블의 병합 조인

다음 쿼리 역시 SELECT *를 사용하지만 LISTID 열을 기준으로 SALES와 LISTING을 조인합니다. 이때 LISTID는 두 테이블 모두에 분산 및 정렬 키로 설정된 열입니다. 병합 조인이 선택되고, 데이터 재분산은 필요 없습니다(DS_DIST_NONE).

explain select * from sales, listing where sales.listid = listing.listid; QUERY PLAN ----------------------------------------------------------------------------- XN Merge Join DS_DIST_NONE (cost=0.00..6285.93 rows=172456 width=97) Merge Cond: ("outer".listid = "inner".listid) -> XN Seq Scan on listing (cost=0.00..1924.97 rows=192497 width=44) -> XN Seq Scan on sales (cost=0.00..1724.56 rows=172456 width=53)

다음은 동일한 쿼리 내에서 다른 유형의 조인을 설명하는 예입니다. 위 예에서 보이듯이 SALES와 LISTING이 병합 조인되지만, 세 번째 테이블인 EVENT는 병합 조인 결과와 해시 조인되어야 합니다. 그 결과, 여기에서도 해시 조인에 따른 브로드캐스팅 비용이 발생합니다.

explain select * from sales, listing, event where sales.listid = listing.listid and sales.eventid = event.eventid; QUERY PLAN ---------------------------------------------------------------------------- XN Hash Join DS_BCAST_INNER (cost=109.98..3871130276.17 rows=172456 width=132) Hash Cond: ("outer".eventid = "inner".eventid) -> XN Merge Join DS_DIST_NONE (cost=0.00..6285.93 rows=172456 width=97) Merge Cond: ("outer".listid = "inner".listid) -> XN Seq Scan on listing (cost=0.00..1924.97 rows=192497 width=44) -> XN Seq Scan on sales (cost=0.00..1724.56 rows=172456 width=53) -> XN Hash (cost=87.98..87.98 rows=8798 width=35) -> XN Seq Scan on event (cost=0.00..87.98 rows=8798 width=35)

예: 조인, 집계 및 정렬

다음은 SALES 및 EVENT 테이블에 대한 해시 조인과, 그 뒤를 이어 분류된 SUM 함수와 ORDER BY 절을 설명하기 위한 집계 및 정렬 작업이 실행되는 쿼리입니다. 초기 Sort 연산자는 컴퓨팅 노드에서 병렬로 실행됩니다. 그런 다음 Network 연산자가 결과를 리더 노드에게 보내고 이어서 Merge 연산자가 최종 정렬 결과를 리더 노드에 산출합니다.

explain select eventname, sum(pricepaid) from sales, event where sales.eventid=event.eventid group by eventname order by 2 desc; QUERY PLAN --------------------------------------------------------------------------------- XN Merge (cost=1002815366604.92..1002815366606.36 rows=576 width=27) Merge Key: sum(sales.pricepaid) -> XN Network (cost=1002815366604.92..1002815366606.36 rows=576 width=27) Send to leader -> XN Sort (cost=1002815366604.92..1002815366606.36 rows=576 width=27) Sort Key: sum(sales.pricepaid) -> XN HashAggregate (cost=2815366577.07..2815366578.51 rows=576 width=27) -> XN Hash Join DS_BCAST_INNER (cost=109.98..2815365714.80 rows=172456 width=27) Hash Cond: ("outer".eventid = "inner".eventid) -> XN Seq Scan on sales (cost=0.00..1724.56 rows=172456 width=14) -> XN Hash (cost=87.98..87.98 rows=8798 width=21) -> XN Seq Scan on event (cost=0.00..87.98 rows=8798 width=21)

데이터 재분산

조인에 대한 EXPLAIN 출력은 조인을 용이하게 하기 위해 데이터의 클러스터 이동 방식에 대한 메서드를 지정합니다. 이 데이터 이동은 브로드캐스팅 또는 재분산으로 수행될 수 있습니다. 브로드캐스팅에서는 한쪽 조인의 데이터 값이 각 컴퓨팅 노드에서 나머지 모든 컴퓨팅 노드로 복사되어 모든 컴퓨팅 노드에 대한 데이터 복사가 완료됩니다. 재분산에서는 데이터 값이 현재 조각에서 새로운 조각(다른 노드의 조각일 가능성이 높음)으로 전송됩니다. 조인 열 중 하나가 분산 키인 경우에는 조인에 참여하는 나머지 테이블의 분산 키와 일치하도록 데이터가 재분산됩니다. 두 테이블 모두 조인 열 중 하나에 분산 키가 없는 경우에는 두 테이블 모두 분산되거나, 혹은 내부 테이블이 모든 노드로 브로드캐스팅됩니다.

EXPLAIN 출력 또한 내부 테이블과 외부 테이블을 참조합니다. 내부 테이블이 먼저 스캔되어 쿼리 계획 하단 가까운 곳에 표시됩니다. 내부 테이블은 일치하는 조건을 탐색하는 대상 테이블입니다. 보통은 메모리에 저장되며, 일반적으로 해시 처리를 위한 원본 테이블인 동시에 가능하다면 조인하는 두 테이블 중 더 작은 용량의 테이블이기도 합니다. 외부 테이블은 내부 테이블을 대상으로 일치하는 행의 원본입니다. 일반적으로 디스크에서 읽어옵니다. 쿼리 옵티마이저는 최근 실행한 ANALYZE 명령의 데이터베이스 통계를 기준으로 내부 테이블과 외부 테이블을 선택합니다. 쿼리의 FROM 절에서 테이블 순서로는 내부 테이블과 외부 테이블을 구분할 수 없습니다.

쿼리를 용이하게 하기 위해 데이터의 이동 방식은 쿼리 계획에서 다음 속성을 사용하여 구분합니다.

  • DS_BCAST_INNER

    전체 내부 테이블의 복사본이 모든 컴퓨팅 노드로 브로드캐스팅됩니다.

  • DS_DIST_ALL_NONE

    이미 내부 테이블이 DISTSTYLE ALL을 사용하여 모든 노드에 분산되었기 때문에 재분산이 필요 없습니다.

  • DS_DIST_NONE

    두 테이블 모두 재분산되지 않습니다. 해당 조각이 노드 간 데이터 이동 없이 조인되기 때문에 공동 배치되는 조인도 가능합니다.

  • DS_DIST_INNER

    내부 테이블이 재분산됩니다.

  • DS_DIST_OUTER

    외부 테이블이 재분산됩니다.

  • DS_DIST_ALL_INNER

    외부 테이블이 DISTSTYLE ALL을 사용하기 때문에 내부 테이블 전체가 단일 조각으로 재분산됩니다.

  • DS_DIST_BOTH

    두 테이블 모두 재분산됩니다.