

 Amazon Redshift는 패치 198부터 새 Python UDF 생성을 더 이상 지원하지 않습니다. 기존 Python UDF는 2026년 6월 30일까지 계속 작동합니다. 자세한 내용은 [블로그 게시물](https://aws.amazon.com/blogs/big-data/amazon-redshift-python-user-defined-functions-will-reach-end-of-support-after-june-30-2026/)을 참조하세요.

# 쿼리 분석 및 개선 사항
<a name="c-query-tuning"></a>

Amazon Redshift 데이터 웨어하우스에서 정보를 가져올 때는 엄청난 대용량 데이터에 대한 복합 쿼리를 실행하기 때문에 프로세스 시간이 길어질 수 있습니다. 이러한 쿼리 프로세스의 속도를 최대한 높이기 위해 잠재적 성능 문제를 식별하는 데 사용할 수 있는 도구가 많이 있습니다.

**Topics**
+ [쿼리 분석 워크플로우](c-query-analysis-process.md)
+ [쿼리 알림 검토](c-reviewing-query-alerts.md)
+ [쿼리 계획 분석](c-analyzing-the-query-plan.md)
+ [쿼리 요약 분석](c-analyzing-the-query-summary.md)
+ [쿼리 성능 개선](query-performance-improvement-opportunities.md)
+ [쿼리 튜닝을 위한 진단 쿼리](diagnostic-queries-for-query-tuning.md)

# 쿼리 분석 워크플로우
<a name="c-query-analysis-process"></a>

쿼리가 예상보다 오래 걸리면 다음 단계에 따라 쿼리 성능에 부정적인 영향을 미칠 수 있는 문제를 찾아 교정합니다. 시스템에서 튜닝을 통해 성능을 높일 수 있는 쿼리에 대해 확신이 없는 경우에는 먼저 [튜닝에 가장 적합한 쿼리 식별](identify-queries-that-are-top-candidates-for-tuning.md) 섹션의 진단 코드를 실행하세요.

1. 테이블이 모범 사례에 따라 설계되어 있는지 확인합니다. 자세한 내용은 [Amazon Redshift 테이블 설계 모범 사례](c_designing-tables-best-practices.md) 섹션을 참조하세요.

1. 테이블에서 불필요한 데이터를 삭제하거나 아카이빙할 수 있는지 살펴봅니다. 예를 들어 쿼리가 항상 지난 6개월 분량의 데이터를 대상으로 하지만 테이블에는 지난 18개월 분량의 데이터가 저장되어 있다고 가정하겠습니다. 이러한 경우 이전 데이터를 삭제하거나 아카이브하여 스캔 및 분산해야 하는 레코드 수를 줄일 수 있습니다.

1. 쿼리에서 테이블에 대한 [VACUUM](r_VACUUM_command.md) 명령을 실행하여 공간을 회수한 다음 행을 재정렬합니다. 정렬되지 않은 영역이 크고, 쿼리가 조인 또는 조건자에서 정렬 키를 사용하는 경우, VACUUM을 실행하는 것이 좋습니다.

1. 쿼리에서 테이블에 대한 [ANALYZE](r_ANALYZE.md) 명령을 실행하여 통계를 최신 상태로 유지합니다. 쿼리에서 테이블 하나라도 최근에 크기가 많이 변경된 경우에는 ANALYZE를 실행하는 것이 좋습니다. ANALYZE 명령 전체를 실행하는 데 시간이 너무 오래 걸린다면 단일 열에 대한 ANALYZE를 실행하여 처리 시간을 줄일 수 있습니다. 이러한 방법으로도 테이블 크기 통계가 업데이트됩니다. 테이블 크기는 쿼리 계획에서 중요한 요인입니다.

1. 각 유형별 클라이언트에 따라(클라이언트가 사용하는 연결 프로토콜 유형에 따라) 쿼리를 한 번씩 실행해야만 쿼리가 컴파일 및 캐싱됩니다. 그러면 이후 쿼리를 실행하는 속도가 빨라집니다. 자세한 내용은 [쿼리 성능에 영향을 미치는 요인](c-query-performance.md) 섹션을 참조하세요.

1. [STL\$1ALERT\$1EVENT\$1LOG](r_STL_ALERT_EVENT_LOG.md) 테이블을 확인하고 쿼리의 잠재적 문제를 찾아 교정합니다. 자세한 내용은 [쿼리 알림 검토](c-reviewing-query-alerts.md) 섹션을 참조하세요.

1. [EXPLAIN](r_EXPLAIN.md) 명령을 실행하여 쿼리 계획을 가져와 쿼리를 최적화하는 데 사용합니다. 자세한 내용은 [쿼리 계획 분석](c-analyzing-the-query-plan.md) 섹션을 참조하세요.

1. [SVL\$1QUERY\$1SUMMARY](r_SVL_QUERY_SUMMARY.md) 및 [SVL\$1QUERY\$1REPORT](r_SVL_QUERY_REPORT.md) 뷰를 사용하여 요약 정보를 가져와 쿼리를 최적화하는 데 사용합니다. 자세한 내용은 [쿼리 요약 분석](c-analyzing-the-query-summary.md) 섹션을 참조하세요.

간혹 빠르게 실행해야 하데 다른 장기 실행 쿼리가 끝날 때까지 기다려야 하는 쿼리가 있는 경우도 있습니다. 이때는 쿼리 자체에서 개선할 방법은 없습니다. 하지만 쿼리 유형마다 다른 쿼리 대기열을 생성 및 사용하여 전체 시스템 성능을 높일 수는 있습니다. 쿼리 대기열의 대기 시간에 대한 자세한 내용은 [쿼리의 대기열 대기 시간 검토](review-queue-wait-times-for-queries.md) 섹션을 참조하세요. 상태 확인 구성에 대한 자세한 내용은 [워크로드 관리](cm-c-implementing-workload-management.md) 섹션을 참조하세요.

# 쿼리 알림 검토
<a name="c-reviewing-query-alerts"></a>

[STL\$1ALERT\$1EVENT\$1LOG](r_STL_ALERT_EVENT_LOG.md) 시스템 테이블을 사용하여 쿼리의 잠재적 성능 문제를 찾아 교정하려면 다음 단계를 따릅니다.

1. 다음과 같이 실행하여 쿼리 ID를 확인합니다.

   ```
   select query, elapsed, substring
   from svl_qlog
   order by query
   desc limit 5;
   ```

   `substring` 필드에서 잘려있는 쿼리 텍스트를 검사하여 선택할 `query` 값을 결정합니다. 쿼리를 한 번 넘게 실행한 경우에는 `query` 값이 더 낮은 행의 `elapsed` 값을 사용하세요. 이 행이 컴파일 버전의 행입니다. 다수의 쿼리를 실행한 경우 쿼리의 포함 여부를 확인하는 데 사용하는 LIMIT 절에서 사용되는 값을 높일 수도 있습니다.

1. STL\$1ALERT\$1EVENT\$1LOG에서 쿼리에 사용할 행을 선택합니다.

   ```
   Select * from stl_alert_event_log where query = MyQueryID;               
   ```  
![\[STL_ALERT_EVENT_LOG의 샘플 쿼리 결과입니다.\]](http://docs.aws.amazon.com/ko_kr/redshift/latest/dg/images/stl_alert_event_log_results.png)

1. 쿼리 결과를 평가합니다. 다음 표를 사용하여 식별된 문제를 해결할 수 있는 솔루션을 찾으세요.
**참고**  
모든 쿼리에 STL\$1ALERT\$1EVENT\$1LOG의 행이 있지는 않습니다. 식별된 문제가 있는 쿼리에만 행이 있습니다.    
[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/redshift/latest/dg/c-reviewing-query-alerts.html)

# 쿼리 계획 분석
<a name="c-analyzing-the-query-plan"></a>

[EXPLAIN](r_EXPLAIN.md) 명령을 실행하여 쿼리 계획을 가져온 후

쿼리 계획을 분석하려면 먼저 쿼리 계획을 읽는 방법부터 알아야 합니다. 쿼리 계획을 읽는 방법에 대해서 잘 모르는 경우에는 본 섹션을 진행하기 전에 [쿼리 계획 생성 및 해석](c-the-query-plan.md)부터 읽는 것이 좋습니다.

다음 단계에 따라 쿼리 계획에서 출력되는 데이터를 분석합니다.

1. 비용이 가장 높은 단계를 식별합니다. 나머지 단계를 진행하면서 식별된 단계를 최적화하는 데 집중해야 합니다.

1. 조인 유형을 살펴봅니다.
   + **중첩 루프**: 이 조인이 주로 조인 조건이 생략되었을 때 발생합니다. 권장 솔루션은 [중첩 루프](query-performance-improvement-opportunities.md#nested-loop) 섹션을 참조하세요.
   + **해시 및 해시 조인**: 해시 조인은 조인 열이 분산 키와 정렬 키가 아닌 테이블을 조인할 때 사용됩니다. 권장 솔루션은 [해시 조인](query-performance-improvement-opportunities.md#hash-join) 섹션을 참조하세요.
   + **병합 조인**: 변경할 필요 없습니다.

1. 내부 조인과 외부 조인에 어떤 테이블이 사용되는지 확인합니다. 쿼리 엔진은 일반적으로 작은 테이블을 내부 조인 용도로, 그리고 큰 테이블을 외부 조인 용도로 선택합니다. 이러한 선택과 다르다면 통계가 오랜 시간이 지났을 가능성이 높습니다. 권장 솔루션은 [테이블 통계 누락 또는 만료](query-performance-improvement-opportunities.md#table-statistics-missing-or-out-of-date) 섹션을 참조하세요.

1. 비용이 높은 정렬 작업 유무를 확인합니다. 있을 경우 권장 솔루션은 [정렬되지 않았거나 잘못 정렬된 행](query-performance-improvement-opportunities.md#unsorted-or-mis-sorted-rows) 섹션을 참조하세요.

1. 비용이 높은 작업이 있을 경우 다음과 같은 브로드캐스팅 연산자를 찾습니다.
   + **DS\$1BCAST\$1INNER**: 테이블이 모든 컴퓨팅 노드에 브로드캐스트되는지 나타냅니다. 이는 작은 테이블에는 좋지만 더 큰 테이블에는 이상적이지 않습니다.
   + **DS\$1DIST\$1ALL\$1INNER**: 모든 워크로드가 단일 조각으로 재분산되는 것을 의미합니다.
   + **DS\$1DIST\$1BOTH**: 과다한 재분산을 의미합니다.

   각 상황에 대한 권장 솔루션은 [최적이 아닌 데이터 분산](query-performance-improvement-opportunities.md#suboptimal-data-distribution) 섹션을 참조하세요.

# 쿼리 요약 분석
<a name="c-analyzing-the-query-summary"></a>

[EXPLAIN](r_EXPLAIN.md)에서 출력되는 쿼리 계획보다 더욱 자세하게 실행 단계 및 통계 정보를 가져오려면 [SVL\$1QUERY\$1SUMMARY](r_SVL_QUERY_SUMMARY.md) 및 [SVL\$1QUERY\$1REPORT](r_SVL_QUERY_REPORT.md) 시스템 뷰를 사용하세요.

SVL\$1QUERY\$1SUMMARY는 스트림 단위로 쿼리 통계 정보를 제공합니다. 이 정보를 사용하여 비용이 높은 단계, 장기 실행 단계, 그리고 디스크에 작성되는 단계의 문제를 식별할 수 있습니다.

SVL\$1QUERY\$1REPORT 시스템 뷰도 SVL\$1QUERY\$1SUMMARY와 유사한 정보를 제공하지만 스트림 단위가 아닌 컴퓨팅 노드 조각 단위로 이루어집니다. 이러한 조각 수준의 정보는 클러스터 간 고르지 않은 데이터 분산(데이터 분산 스큐라고도 함)을 감지하는 데 사용할 수 있습니다. 데이터 분산이 고르지 않으면 일부 노드가 다른 노드에 비해 작업량이 많아지면서 쿼리 성능이 떨어집니다.

**Topics**
+ [SVL\$1QUERY\$1SUMMARY 뷰 사용](using-SVL-Query-Summary.md)
+ [SVL\$1QUERY\$1REPORT 뷰 사용](using-SVL-Query-Report.md)
+ [쿼리 계획을 쿼리 요약에 매핑](query-plan-summary-map.md)

# SVL\$1QUERY\$1SUMMARY 뷰 사용
<a name="using-SVL-Query-Summary"></a>

[SVL\$1QUERY\$1SUMMARY](r_SVL_QUERY_SUMMARY.md)를 사용한 스트림의 쿼리 요약 정보를 분석하려면 다음과 같이 수행합니다.

1. 다음 쿼리를 실행하여 쿼리 ID를 확인합니다.

   ```
   select query, elapsed, substring
   from svl_qlog
   order by query
   desc limit 5;
   ```

   `substring` 필드에서 잘려있는 쿼리 텍스트를 검사하여 쿼리를 표현할 `query` 값을 결정합니다. 쿼리를 한 번 넘게 실행한 경우에는 `query` 값이 더 낮은 행의 `elapsed` 값을 사용하세요. 이 행이 컴파일 버전의 행입니다. 다수의 쿼리를 실행한 경우 쿼리의 포함 여부를 확인하는 데 사용하는 LIMIT 절에서 사용되는 값을 높일 수도 있습니다.

1. SVL\$1QUERY\$1SUMMARY에서 쿼리에 사용할 행을 선택합니다. 결과 순서는 stream, segment 및 step의 순으로 정하세요.

   ```
   select * from svl_query_summary where query = MyQueryID order by stm, seg, step;
   ```

   다음은 예 결과입니다.  
![\[지정된 쿼리와 일치하는 SVL_QUERY_SUMMARY의 행에 대한 샘플 결과입니다.\]](http://docs.aws.amazon.com/ko_kr/redshift/latest/dg/images/svl_query_summary_results.png)

1. [쿼리 계획을 쿼리 요약에 매핑](query-plan-summary-map.md) 섹션의 정보를 사용하여 단계를 쿼리 계획의 작업으로 매핑합니다. 이때 각 단계는 행 값과 바이트 값이 대략적으로 동일해야 합니다(쿼리 계획의 행 \$1 폭). 그렇지 않은 경우 권장 솔루션은 [테이블 통계 누락 또는 만료](query-performance-improvement-opportunities.md#table-statistics-missing-or-out-of-date) 섹션을 참조하세요.

1. 어떤 단계든지 `is_diskbased` 필드에 `t`(true) 값이 있는지 확인합니다. 쿼리 처리를 위해 시스템에 할당된 메모리가 충분하지 않을 경우 해시, 집계 및 정렬 연산자는 데이터를 디스크에 작성할 가능성이 높습니다.

   `is_diskbased`가 true인 경우 권장 솔루션은 [쿼리에 할당되는 메모리 부족](query-performance-improvement-opportunities.md#insufficient-memory-allocated-to-the-query) 섹션을 참조하세요.

1. `label` 필드 값을 살펴보면서 여러 단계 중 어디에서든지 AGG-DIST-AGG 시퀀스가 있는지 확인합니다. 이러한 시퀀스는 2단계 집계를 의미하는 것으로 비용이 높습니다. 이 문제를 해결하려면 GROUP BY 절을 변경하여 분산 키(키가 다수인 경우 첫 번째 키)를 사용하세요.

1. 각 세그먼트마다 `maxtime` 값을 살펴봅니다(이 값은 세그먼트를 구성하는 모든 단계에서 동일합니다). `maxtime` 값이 가장 높은 세그먼트를 찾아 각 단계에서 다음 연산자를 확인합니다.
**참고**  
`maxtime` 값이 높다고 해서 반드시 세그먼트에 문제가 있다는 것을 의미하지는 않습니다. 높은 값에도 불구하고 세그먼트의 처리 시간이 오래 걸리지 않을 수도 있습니다. 스트림을 구성하는 세그먼트는 모두 시간을 동일하게 맞춥니다. 하지만 일부 다운스트림 세그먼트는 업스트림 세그먼트에서 데이터를 가져올 때까지 실행하지 못하는 경우도 있습니다. 이러한 세그먼트는 `maxtime` 값에 대기 시간과 처리 시간이 모두 포함되기 때문에 시간이 더 오래 걸리는 원인이 될 수도 있습니다.
   + **BCAST 또는 DIST**: 두 경우 `maxtime` 값은 다수의 행을 재분산한 결과가 될 수 있습니다. 권장 솔루션은 [최적이 아닌 데이터 분산](query-performance-improvement-opportunities.md#suboptimal-data-distribution) 섹션을 참조하세요.
   + **HJOIN(해시 조인)**: 해당 단계의 `rows` 필드 값이 쿼리에서 최종 RETURN 단계의 `rows` 값에 비해 매우 높은 경우 권장하는 솔루션은 [해시 조인](query-performance-improvement-opportunities.md#hash-join) 섹션을 참조하세요.
   + **SCAN/SORT**: 조인 단계 바로 앞에서 SCAN, SORT, SCAN 및 MERGE 단계를 순서대로 찾습니다. 이 패턴은 정렬되지 않은 데이터가 스캔 및 정렬을 거쳐 정렬된 테이블 영역과 병합된다는 것을 의미합니다.

     SCAN 단계의 rows 값이 쿼리에서 최종 RETURN 단계의 rows 값에 비해 매우 높은지 확인합니다. 이러한 패턴은 실행 엔진이 나중에 무시되는 행을 스캔한다는 것을 의미하지만, 이는 비효율적입니다. 권장 솔루션은 [불충분한 제한적 조건자](query-performance-improvement-opportunities.md#insufficiently-restrictive-predicate) 섹션을 참조하세요.

     SCAN 단계의 `maxtime` 값이 높은 경우 권장 솔루션은 [최상이 아닌 WHERE 절](query-performance-improvement-opportunities.md#suboptimal-WHERE-clause) 섹션을 참조하세요.

     SORT 단계의 `rows` 값이 0이 아닌 경우 권장 솔루션은 [정렬되지 않았거나 잘못 정렬된 행](query-performance-improvement-opportunities.md#unsorted-or-mis-sorted-rows) 섹션을 참조하세요.

1. 최종 RETURN 단계 앞에 있는 5\$110단계에서 `rows` 값과 `bytes` 값을 검토하여 클라이언트로 반환되는 데이터 크기를 파악합니다. 이 프로세스는 기교가 필요할 수 있습니다.

   예를 들어 다음 샘플 쿼리 요약을 보면 세 번째 PROJECT 단계에서 `bytes` 값이 아닌 `rows` 값이 제공됩니다. 이전 단계들에서 동일한 `rows` 값을 가진 단계를 살펴보며 행과 바이트 정보를 모두 제공하는 SCAN 단계를 찾습니다.

    다음은 결과 샘플입니다.  
![\[쿼리 요약 결과의 행으로, 행과 바이트 정보가 모두 포함된 SCAN 단계입니다.\]](http://docs.aws.amazon.com/ko_kr/redshift/latest/dg/images/rows_and_bytes.png)

   비정상적으로 많은 데이터 볼륨을 반환하는 경우 권장 솔루션은 [매우 큰 결과 집합](query-performance-improvement-opportunities.md#very-large-result-set) 섹션을 참조하세요.

1. 어떤 단계에서든지 다른 단계와 비교하여 `bytes` 값이 `rows` 값에 비해 높은 경우가 있는지 확인합니다. 이 패턴은 다수의 열이 선택되어 있다는 것을 의미할 수 있습니다. 권장 솔루션은 [큰 SELECT 목록](query-performance-improvement-opportunities.md#large-SELECT-list) 섹션을 참조하세요.

# SVL\$1QUERY\$1REPORT 뷰 사용
<a name="using-SVL-Query-Report"></a>

[SVL\$1QUERY\$1REPORT](r_SVL_QUERY_REPORT.md)를 사용한 조각의 쿼리 요약 정보를 분석하려면 다음과 같이 수행합니다.

1. 다음과 같이 실행하여 쿼리 ID를 확인합니다.

   ```
   select query, elapsed, substring
   from svl_qlog
   order by query
   desc limit 5;
   ```

   `substring` 필드에서 잘려있는 쿼리 텍스트를 검사하여 쿼리를 표현할 `query` 값을 결정합니다. 쿼리를 한 번 넘게 실행한 경우에는 `query` 값이 더 낮은 행의 `elapsed` 값을 사용하세요. 이 행이 컴파일 버전의 행입니다. 다수의 쿼리를 실행한 경우 쿼리의 포함 여부를 확인하는 데 사용하는 LIMIT 절에서 사용되는 값을 높일 수도 있습니다.

1. SVL\$1QUERY\$1REPORT에서 쿼리에 사용할 행을 선택합니다. 결과 순서는 segment, step, elapsed\$1time 및 rows의 순으로 정하세요.

   ```
   select * from svl_query_report where query = MyQueryID order by segment, step, elapsed_time, rows;
   ```

1. 각 단계마다 모든 조각이 대략적으로 동일한 수의 행을 처리하는지 확인합니다.  
![\[쿼리를 실행하는 데 사용되는 데이터 조각의 목록입니다. 각 조각은 거의 동일한 수의 행을 처리합니다.\]](http://docs.aws.amazon.com/ko_kr/redshift/latest/dg/images/SVL_QUERY_REPORT_rows.png)

   또한 모든 조각에서 대략적으로 동일한 시간이 걸리는지도 확인합니다.  
![\[쿼리를 실행하는 데 사용되는 데이터 조각의 목록입니다. 각 조각에는 대략 동일한 시간이 소요됩니다.\]](http://docs.aws.amazon.com/ko_kr/redshift/latest/dg/images/SVL_QUERY_REPORT_elapsed_time.png)

   두 값의 차이가 클 경우에는 위의 특정 쿼리에 최적화되지 않은 분산 스타일로 인한 데이터 분산 스큐를 나타낼 수 있습니다. 권장 솔루션은 [최적이 아닌 데이터 분산](query-performance-improvement-opportunities.md#suboptimal-data-distribution) 섹션을 참조하세요.

# 쿼리 계획을 쿼리 요약에 매핑
<a name="query-plan-summary-map"></a>

쿼리 요약을 분석하는 경우 쿼리 계획의 작업에서 쿼리 요약의 단계(레이블 필드 값으로 식별)로 매핑하여 더 많은 세부 정보를 확보할 수 있습니다. 다음 테이블은 쿼리 계획 작업을 매핑하여 요약 단계를 쿼리합니다.

[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/redshift/latest/dg/query-plan-summary-map.html)

# 쿼리 성능 개선
<a name="query-performance-improvement-opportunities"></a>

다음은 문제를 진단 및 해결하는 방법에 대한 설명과 함께 Amazon Redshift 쿼리 성능에 영향을 미치는 몇 가지 일반적인 문제입니다.

**Topics**
+ [테이블 통계 누락 또는 만료](#table-statistics-missing-or-out-of-date)
+ [중첩 루프](#nested-loop)
+ [해시 조인](#hash-join)
+ [고스트 행 또는 커밋되지 않은 행](#ghost-rows-or-uncommitted-rows)
+ [정렬되지 않았거나 잘못 정렬된 행](#unsorted-or-mis-sorted-rows)
+ [최적이 아닌 데이터 분산](#suboptimal-data-distribution)
+ [쿼리에 할당되는 메모리 부족](#insufficient-memory-allocated-to-the-query)
+ [최상이 아닌 WHERE 절](#suboptimal-WHERE-clause)
+ [불충분한 제한적 조건자](#insufficiently-restrictive-predicate)
+ [매우 큰 결과 집합](#very-large-result-set)
+ [큰 SELECT 목록](#large-SELECT-list)

## 테이블 통계 누락 또는 만료
<a name="table-statistics-missing-or-out-of-date"></a>

테이블 통계가 누락되었거나 이전 상태이면 다음과 같이 표시될 수 있습니다.
+ EXPLAIN 명령 결과의 경고 메시지
+ STL\$1ALERT\$1EVENT\$1LOG의 통계 누락 알림 이벤트. 자세한 내용은 [쿼리 알림 검토](c-reviewing-query-alerts.md) 섹션을 참조하세요.

이 문제를 해결하려면 [ANALYZE](r_ANALYZE.md)를 실행하세요.

## 중첩 루프
<a name="nested-loop"></a>

중첩 루프가 존재하는 경우 STL\$1ALERT\$1EVENT\$1LOG에 중첩 루프 알림 이벤트가 표시됩니다. 이러한 유형의 이벤트는 [중첩 루프가 포함된 쿼리 식별](identify-queries-with-nested-loops.md)에 있는 쿼리를 실행해도 식별할 수 있습니다. 자세한 내용은 [쿼리 알림 검토](c-reviewing-query-alerts.md) 섹션을 참조하세요.

이 문제를 해결하려면 쿼리에서 크로스 조인 유무를 살펴본 후 가능하다면 제거하세요. 크로스 조인은 조인 조건이 없기 때문에 두 테이블의 데카르트 곱이 발생하는 원인이 됩니다. 또한 일반적으로 중첩 루프 조인으로 실행되기 때문에 가능한 조인 유형 중에서 속도가 가장 느립니다.

## 해시 조인
<a name="hash-join"></a>

해시 조인이 존재하는 경우 다음과 같이 표시됩니다.
+ 쿼리 계획에 해시 및 해시 조인 작업이 존재합니다. 자세한 내용은 [쿼리 계획 분석](c-analyzing-the-query-plan.md) 섹션을 참조하세요.
+ SVL\$1QUERY\$1SUMMARY에 maxtime 값이 가장 높은 세그먼트의 HJOIN 단계가 존재합니다. 자세한 내용은 [SVL\$1QUERY\$1SUMMARY 뷰 사용](using-SVL-Query-Summary.md) 섹션을 참조하세요.

이 문제를 해결하려면 다음과 같이 두 가지 방법을 사용할 수 있습니다.
+ 가능하다면 쿼리를 재작성하여 병합 조인을 사용하세요. 분산 키인 동시에 정렬 키인 조인 열을 지정하면 가능합니다.
+ SVL\$1QUERY\$1SUMMARY에서 HJOIN 단계의 rows 필드 값이 쿼리에서 최종 RETURN 단계의 rows 값에 비해 매우 높은 경우에는 쿼리를 재작성하여 고유한 열을 기준으로 조인할 수 있는지 확인하세요. 쿼리가 기본 키 같이 고유한 열을 기준으로 조인되지 않으면 조인에 참여하는 행의 수가 늘어납니다.

## 고스트 행 또는 커밋되지 않은 행
<a name="ghost-rows-or-uncommitted-rows"></a>

고스트 행 또는 커밋되지 않은 행이 존재하면 고스트 행이 지나치게 많다는 것을 나타내는 알림 이벤트가 STL\$1ALERT\$1EVENT\$1LOG에 표시됩니다. 자세한 내용은 [쿼리 알림 검토](c-reviewing-query-alerts.md) 섹션을 참조하세요.

이 문제를 해결하려면 다음과 같이 두 가지 방법을 사용할 수 있습니다.
+ Amazon Redshift 콘솔의 [**로드(Loads)**] 탭에서 쿼리 테이블에 대한 활성 로드 작업을 확인합니다. 활성 로드 작업이 있으면 끝날 때까지 기다린 후 다른 작업을 시작하세요.
+ 활성 로드 작업이 없으면 쿼리 테이블에 대해 [VACUUM](r_VACUUM_command.md)을 실행하여 삭제된 행을 제거하세요.

## 정렬되지 않았거나 잘못 정렬된 행
<a name="unsorted-or-mis-sorted-rows"></a>

정렬되지 않았거나 잘못 정렬된 행이 존재하면 STL\$1ALERT\$1EVENT\$1LOG에 선택의 폭이 매우 제한적인 필터 알림 이벤트가 표시됩니다. 자세한 내용은 [쿼리 알림 검토](c-reviewing-query-alerts.md) 섹션을 참조하세요.

그 밖에 [데이터 스큐 또는 미정렬 행이 포함된 테이블 식별](identify-tables-with-data-skew-or-unsorted-rows.md)에 있는 쿼리를 실행하여 쿼리 테이블 중 정렬되지 않은 영역이 많은 테이블이 있는지 알아보는 방법도 있습니다.

이 문제를 해결하려면 다음과 같이 두 가지 방법을 사용할 수 있습니다.
+ 쿼리 테이블에 대해 [VACUUM](r_VACUUM_command.md)을 실행하여 행을 다시 정렬하세요.
+ 쿼리 테이블의 정렬 키에서 개선할 수 있는 점이 있는지 살펴보세요. 단, 무엇이든 변경하기 전에 이 쿼리의 성능과 다른 중요한 쿼리 및 전반 시스템을 비교하여 검토해야 합니다. 자세한 내용은 [정렬 키](t_Sorting_data.md) 섹션을 참조하세요.

## 최적이 아닌 데이터 분산
<a name="suboptimal-data-distribution"></a>

데이터 분산이 최적의 상태가 아니면 다음과 같이 표시됩니다.
+ 직렬 실행, 대량 브로드캐스팅 또는 대량 분산 알림 이벤트가 STL\$1ALERT\$1EVENT\$1LOG에 표시됩니다. 자세한 내용은 [쿼리 알림 검토](c-reviewing-query-alerts.md) 섹션을 참조하세요.
+ 임의 단계에서 조각이 처리하는 행의 수가 대략적으로 동일하지 않습니다. 자세한 내용은 [SVL\$1QUERY\$1REPORT 뷰 사용](using-SVL-Query-Report.md) 섹션을 참조하세요.
+ 임의 단계에서 조각이 처리하는 데 걸리는 시간이 대략적으로 동일하지 않습니다. 자세한 내용은 [SVL\$1QUERY\$1REPORT 뷰 사용](using-SVL-Query-Report.md) 섹션을 참조하세요.

위에서 설명한 것 중 하나라도 사실이 아니라면 [데이터 스큐 또는 미정렬 행이 포함된 테이블 식별](identify-tables-with-data-skew-or-unsorted-rows.md)에 있는 쿼리를 실행하여 쿼리 테이블 중 데이터 스큐가 발생하는 테이블이 있는지 살펴보는 방법도 있습니다.

이 문제를 해결하려면 쿼리의 테이블에 대한 배포 스타일을 검토하고 개선할 수 있는 점이 있는지 확인합니다. 단, 무엇이든 변경하기 전에 이 쿼리의 성능과 다른 중요한 쿼리 및 전반 시스템을 비교하여 검토해야 합니다. 자세한 내용은 [쿼리 최적화를 위한 데이터 배포](t_Distributing_data.md) 섹션을 참조하세요.

## 쿼리에 할당되는 메모리 부족
<a name="insufficient-memory-allocated-to-the-query"></a>

쿼리에 할당되는 메모리가 부족하면 SVL\$1QUERY\$1SUMMARY에 `is_diskbased` 값이 true인 단계가 표시됩니다. 자세한 내용은 [SVL\$1QUERY\$1SUMMARY 뷰 사용](using-SVL-Query-Summary.md) 섹션을 참조하세요.

이 문제를 해결하려면 사용할 쿼리 수를 일시적으로 늘려서 쿼리에 더 많은 메모리를 할당하세요. 워크로드 관리(WLM)는 대기열에 설정되어 있는 동시성 레벨과 동일하게 쿼리 대기열의 슬롯을 보유합니다. 예를 들어 동시성 레벨이 5인 대기열은 슬롯 수도 5개입니다. 대기열에 할당되는 메모리는 균일하게 각 슬롯으로 분할됩니다. 하나의 쿼리에 다수의 슬롯을 할당하면 해당 쿼리는 할당된 모든 슬롯의 메모리에 대한 액세스 권한을 갖게 됩니다. 쿼리에 사용할 슬롯 수를 일시적으로 늘리는 방법에 대한 자세한 내용은 [wlm\$1query\$1slot\$1count](r_wlm_query_slot_count.md) 섹션을 참조하세요.

## 최상이 아닌 WHERE 절
<a name="suboptimal-WHERE-clause"></a>

WHERE 절이 지나치게 많은 테이블 스캔을 초래하면 세그먼트에서 SCAN 단계가 `maxtime` 값이 가장 높은 것으로 SVL\$1QUERY\$1SUMMARY에 표시됩니다. 자세한 내용은 [SVL\$1QUERY\$1SUMMARY 뷰 사용](using-SVL-Query-Summary.md) 섹션을 참조하세요.

이 문제를 해결하려면 가장 큰 테이블의 기본 정렬 열을 기준으로 WHERE 절을 쿼리에 추가하세요. 이 방법은 스캔 시간을 최소화하는 효과가 있습니다. 자세한 내용은 [Amazon Redshift 테이블 설계 모범 사례](c_designing-tables-best-practices.md) 섹션을 참조하세요.

## 불충분한 제한적 조건자
<a name="insufficiently-restrictive-predicate"></a>

쿼리에 불충분한 제한적 조건자가 있는 경우에는 세그먼트에서 SCAN 단계가 `maxtime` 값이 가장 높은 것으로 SVL\$1QUERY\$1SUMMARY에 표시되는 동시에 SVL\$1QUERY\$1SUMMARY에서 SCAN 단계의 `rows` 값이 쿼리에서 최종 RETURN 단계의 `rows` 값에 비해 매우 높습니다. 자세한 내용은 [SVL\$1QUERY\$1SUMMARY 뷰 사용](using-SVL-Query-Summary.md) 섹션을 참조하세요.

이 문제를 해결하려면 쿼리에 조건자를 추가하거나, 혹은 기존 조건자의 제한을 높여서 출력 범위를 좁히세요.

## 매우 큰 결과 집합
<a name="very-large-result-set"></a>

쿼리가 매우 큰 결과 집합을 반환하는 경우에는 쿼리를 재작성하면서 [UNLOAD](r_UNLOAD.md)를 사용하여 결과를 Amazon S3에 작성하는 것이 좋습니다. 이 방법은 병렬 처리를 이용해 RETURN 단계의 성능을 개선하는 효과가 있습니다. 매우 큰 결과 집합의 유무를 확인하는 방법에 대한 자세한 내용은 [SVL\$1QUERY\$1SUMMARY 뷰 사용](using-SVL-Query-Summary.md) 섹션을 참조하세요.

## 큰 SELECT 목록
<a name="large-SELECT-list"></a>

쿼리의 SELECT 목록이 비정상적으로 큰 경우에는 어떤 단계에서든지(다른 단계와 비교하여) `bytes` 값이 `rows` 값에 비해 비교적 높은 것으로 SVL\$1QUERY\$1SUMMARY에 표시됩니다. 이렇게 `bytes` 값이 높으면 선택한 열이 많다는 것을 나타낼 수 있습니다. 자세한 내용은 [SVL\$1QUERY\$1SUMMARY 뷰 사용](using-SVL-Query-Summary.md) 섹션을 참조하세요.

이 문제를 해결하려면 선택한 열을 살펴보면서 제거할 수 있는 열이 있는지 확인하세요.

# 쿼리 튜닝을 위한 진단 쿼리
<a name="diagnostic-queries-for-query-tuning"></a>

아래 쿼리들은 쿼리 성능에 영향을 미칠 수 있는 쿼리 또는 쿼리 테이블의 문제를 식별하는 데 사용됩니다. 이러한 쿼리는 [쿼리 분석 및 개선 사항](c-query-tuning.md)에서 언급한 쿼리 튜닝 프로세스와 함께 사용하는 것이 좋습니다.

**참고**  
이러한 쿼리는 Amazon Redshift 프로비저닝된 클러스터를 대상으로 합니다. 이러한 쿼리는 Redshift Serverless 작업 그룹에는 사용할 수 없습니다.

**Topics**
+ [튜닝에 가장 적합한 쿼리 식별](identify-queries-that-are-top-candidates-for-tuning.md)
+ [데이터 스큐 또는 미정렬 행이 포함된 테이블 식별](identify-tables-with-data-skew-or-unsorted-rows.md)
+ [중첩 루프가 포함된 쿼리 식별](identify-queries-with-nested-loops.md)
+ [쿼리의 대기열 대기 시간 검토](review-queue-wait-times-for-queries.md)
+ [테이블별 쿼리 알림 검토](review-query-alerts-by-table.md)
+ [통계가 누락된 테이블 식별](identify-tables-with-missing-statistics.md)

# 튜닝에 가장 적합한 쿼리 식별
<a name="identify-queries-that-are-top-candidates-for-tuning"></a>

다음은 지난 7일 동안 실행한 쿼리 문 중에서 가장 많은 시간이 소요된 문 50개를 구분하는 쿼리입니다. 결과를 사용하면 비정상적으로 오래 걸리는 쿼리를 식별할 수 있습니다. 또한 자주 실행되는 쿼리(결과 집합에 두 번 이상 나타나는 쿼리)를 식별할 수 있습니다. 이러한 쿼리들은 튜닝을 통해 시스템 성능을 개선하기 좋은 후보들입니다.

이 쿼리는 식별된 각 쿼리와 연결되어 있는 알림 이벤트의 수도 반환합니다. 이러한 알림 이벤트를 통해 쿼리 성능을 개선하는 데 필요한 세부 정보를 알아낼 수 있습니다. 자세한 내용은 [쿼리 알림 검토](c-reviewing-query-alerts.md) 섹션을 참조하세요.

```
select trim(database) as db, count(query) as n_qry, 
max(substring (qrytext,1,80)) as qrytext, 
min(run_minutes) as "min" , 
max(run_minutes) as "max", 
avg(run_minutes) as "avg", sum(run_minutes) as total,  
max(query) as max_query_id, 
max(starttime)::date as last_run, 
sum(alerts) as alerts, aborted
from (select userid, label, stl_query.query, 
trim(database) as database, 
trim(querytxt) as qrytext, 
md5(trim(querytxt)) as qry_md5, 
starttime, endtime, 
(datediff(seconds, starttime,endtime)::numeric(12,2))/60 as run_minutes,     
alrt.num_events as alerts, aborted 
from stl_query 
left outer join 
(select query, 1 as num_events from stl_alert_event_log group by query ) as alrt 
on alrt.query = stl_query.query
where userid <> 1 and starttime >=  dateadd(day, -7, current_date)) 
group by database, label, qry_md5, aborted
order by total desc limit 50;
```

# 데이터 스큐 또는 미정렬 행이 포함된 테이블 식별
<a name="identify-tables-with-data-skew-or-unsorted-rows"></a>

다음은 데이터 분산이 균일하지 못하거나(데이터 스큐), 정렬되지 않은 행의 비율이 높은 테이블을 찾아내는 쿼리입니다.

`skew` 값이 낮으면 테이블 데이터가 올바로 분산된 것을 의미합니다. 테이블의 `skew` 값이 4.00 이상이면 데이터 분산 스타일을 수정하는 것이 좋습니다. 자세한 내용은 [최적이 아닌 데이터 분산](query-performance-improvement-opportunities.md#suboptimal-data-distribution) 섹션을 참조하세요.

테이블의 `pct_unsorted` 값이 20%보다 높으면 [VACUUM](r_VACUUM_command.md) 명령을 실행하는 것이 좋습니다. 자세한 내용은 [정렬되지 않았거나 잘못 정렬된 행](query-performance-improvement-opportunities.md#unsorted-or-mis-sorted-rows) 섹션을 참조하세요.

그 밖에도 각 테이블마다 `mbytes` 값과 `pct_of_total` 값을 살펴봐야 합니다. 이러한 열은 테이블 크기를 비롯해 원시 디스크에서 테이블이 사용하는 공간 비율을 나타냅니다. 원시 디스크 공간에는 Amazon Redshift가 내부 사용 목적으로 예약하는 공간도 포함되므로 사용자가 사용할 수 있는 디스크 공간 크기인 공칭 디스크 용량보다 더 커야 합니다. 이 정보를 사용하여 여유 디스크 공간이 가장 큰 테이블 크기의 2.5배 이상인지 확인합니다. 이 정도 크기의 공간을 사용할 수 있도록 유지하면 시스템이 복합 쿼리를 처리할 때도 중간 결과를 디스크에 작성할 수 있습니다.

```
select trim(pgn.nspname) as schema, 
trim(a.name) as table, id as tableid, 
decode(pgc.reldiststyle,0, 'even',1,det.distkey ,8,'all') as distkey, dist_ratio.ratio::decimal(10,4) as skew, 
det.head_sort as "sortkey", 
det.n_sortkeys as "#sks", b.mbytes,  
decode(b.mbytes,0,0,((b.mbytes/part.total::decimal)*100)::decimal(5,2)) as pct_of_total, 
decode(det.max_enc,0,'n','y') as enc, a.rows, 
decode( det.n_sortkeys, 0, null, a.unsorted_rows ) as unsorted_rows , 
decode( det.n_sortkeys, 0, null, decode( a.rows,0,0, (a.unsorted_rows::decimal(32)/a.rows)*100) )::decimal(5,2) as pct_unsorted 
from (select db_id, id, name, sum(rows) as rows, 
sum(rows)-sum(sorted_rows) as unsorted_rows 
from stv_tbl_perm a 
group by db_id, id, name) as a 
join pg_class as pgc on pgc.oid = a.id
join pg_namespace as pgn on pgn.oid = pgc.relnamespace
left outer join (select tbl, count(*) as mbytes 
from stv_blocklist group by tbl) b on a.id=b.tbl
inner join (select attrelid, 
min(case attisdistkey when 't' then attname else null end) as "distkey",
min(case attsortkeyord when 1 then attname  else null end ) as head_sort , 
max(attsortkeyord) as n_sortkeys, 
max(attencodingtype) as max_enc 
from pg_attribute group by 1) as det 
on det.attrelid = a.id
inner join ( select tbl, max(mbytes)::decimal(32)/min(mbytes) as ratio 
from (select tbl, trim(name) as name, slice, count(*) as mbytes
from svv_diskusage group by tbl, name, slice ) 
group by tbl, name ) as dist_ratio on a.id = dist_ratio.tbl
join ( select sum(capacity) as  total
from stv_partitions where part_begin=0 ) as part on 1=1
where mbytes is not null 
order by  mbytes desc;
```

# 중첩 루프가 포함된 쿼리 식별
<a name="identify-queries-with-nested-loops"></a>

다음은 중첩 루프에 대한 알림 이벤트가 기록된 쿼리를 식별하는 쿼리입니다. 중첩 루프 조건을 해결하는 방법에 대한 자세한 내용은 [중첩 루프](query-performance-improvement-opportunities.md#nested-loop) 섹션을 참조하세요.

```
select query, trim(querytxt) as SQL, starttime 
from stl_query 
where query in (
select distinct query 
from stl_alert_event_log 
where event like 'Nested Loop Join in the query plan%') 
order by starttime desc;
```

# 쿼리의 대기열 대기 시간 검토
<a name="review-queue-wait-times-for-queries"></a>

다음은 최근 쿼리가 실행에 앞서 쿼리 대기열의 슬롯이 열릴 때까지 대기한 시간을 나타내는 쿼리입니다. 대기 시간이 높은 추이가 발견되면 쿼리 대기열 구성을 수정하여 처리량을 개선하는 것이 좋습니다. 자세한 내용은 [수동 WLM 구현](cm-c-defining-query-queues.md) 섹션을 참조하세요.

```
select trim(database) as DB , w.query, 
substring(q.querytxt, 1, 100) as querytxt,  w.queue_start_time, 
w.service_class as class, w.slot_count as slots, 
w.total_queue_time/1000000 as queue_seconds, 
w.total_exec_time/1000000 exec_seconds, (w.total_queue_time+w.total_Exec_time)/1000000 as total_seconds 
from stl_wlm_query w 
left join stl_query q on q.query = w.query and q.userid = w.userid 
where w.queue_start_Time >= dateadd(day, -7, current_Date) 
and w.total_queue_Time > 0  and w.userid >1   
and q.starttime >= dateadd(day, -7, current_Date) 
order by w.total_queue_time desc, w.queue_start_time desc limit 35;
```

# 테이블별 쿼리 알림 검토
<a name="review-query-alerts-by-table"></a>

다음은 테이블 자체에 대한 알림 이벤트가 기록된 테이블을 찾아내는 동시에 가장 자주 기록되는 알림 유형을 식별하는 쿼리입니다.

식별된 테이블에서 행의 `minutes` 값이 높으면 테이블에서 해당 테이블에 대한 [ANALYZE](r_ANALYZE.md) 또는 [VACUUM](r_VACUUM_command.md) 실행과 같은 일상적인 유지 관리가 필요한지를 확인합니다.

임의의 행에서 `count` 값이 높을 때 `table` 값이 NULL이라면 STL\$1ALERT\$1EVENT\$1LOG에서 연결된 `event` 값에 대한 쿼리를 실행하여 알림이 잦은 이유를 조사하세요.

```
select trim(s.perm_table_name) as table, 
(sum(abs(datediff(seconds, s.starttime, s.endtime)))/60)::numeric(24,0) as minutes, trim(split_part(l.event,':',1)) as event,  trim(l.solution) as solution, 
max(l.query) as sample_query, count(*) 
from stl_alert_event_log as l 
left join stl_scan as s on s.query = l.query and s.slice = l.slice 
and s.segment = l.segment and s.step = l.step
where l.event_time >=  dateadd(day, -7, current_Date) 
group by 1,3,4 
order by 2 desc,6 desc;
```

# 통계가 누락된 테이블 식별
<a name="identify-tables-with-missing-statistics"></a>

다음은 통계가 누락된 테이블에 대한 쿼리 수를 제공하는 쿼리입니다. 이 쿼리가 어떤 행이든 반환하는 경우에는 `plannode` 값을 살펴보면서 해당 테이블을 확인한 후 [ANALYZE](r_ANALYZE.md)를 실행하세요.

```
select substring(trim(plannode),1,100) as plannode, count(*) 
from stl_explain 
where plannode like '%missing statistics%' 
group by plannode 
order by 2 desc;
```