

 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-optimizing-query-performance"></a>

Amazon Redshift는 SQL 기반 쿼리를 사용하여 시스템 데이터 및 객체와 상호작용합니다. 데이터 조작 언어(DML)는 데이터를 보거나, 추가하거나, 변경하거나, 삭제하는 데 사용되는 SQL의 하위 집합입니다. 데이터 정의 언어(DDL)는 테이블 및 뷰 같은 데이터베이스 객체를 추가하거나, 변경하거나, 삭제하는 데 사용되는 SQL의 하위 집합입니다.

일단 시스템 설정을 마치면 특히 데이터를 가져오거나 확인하는 [SELECT](r_SELECT_synopsis.md) 명령에서 DML을 가장 많이 사용하게 됩니다. Amazon Redshift에서 데이터 가져오기 쿼리를 효과적으로 작성하려면 먼저 SELECT 문에 대해 이해한 후 [Amazon Redshift 테이블 설계 모범 사례](c_designing-tables-best-practices.md)에서 간략하게 소개한 팁을 적용하여 쿼리 효율성을 극대화할 수 있습니다.

Amazon Redshift의 쿼리 처리 방식에 대해 이해하려면 [쿼리 처리](c-query-processing.md) 및 [쿼리 분석 및 개선 사항](c-query-tuning.md) 섹션을 참조하세요. 그런 다음 진단 도구와 함께 이 정보를 적용하면 쿼리 성능 문제를 찾아내 제거할 수 있습니다.

Amazon Redshift 쿼리에서 발생하는 가장 공통적인 문제와 가장 심각한 문제를 찾아내 해결하는 방법은 [쿼리 문제 해결](queries-troubleshooting.md) 섹션에서 확인할 수 있습니다.

**Topics**
+ [쿼리 처리](c-query-processing.md)
+ [쿼리 분석 및 개선 사항](c-query-tuning.md)
+ [쿼리 문제 해결](queries-troubleshooting.md)

# 쿼리 처리
<a name="c-query-processing"></a>

Amazon Redshift는 제출된 SQL 쿼리를 구문 분석기와 옵티마이저를 통해 라우팅하여 쿼리 계획을 작성합니다. 그러면 실행 엔진이 쿼리 계획을 코드로 변환한 후 해당 코드를 실행할 수 있도록 컴퓨팅 노드로 전송합니다.

**Topics**
+ [쿼리 계획 및 실행 워크플로우](c-query-planning.md)
+ [쿼리 계획 생성 및 해석](c-the-query-plan.md)
+ [쿼리 계획 단계 검토](reviewing-query-plan-steps.md)
+ [쿼리 성능에 영향을 미치는 요인](c-query-performance.md)

# 쿼리 계획 및 실행 워크플로우
<a name="c-query-planning"></a>

다음 그림은 쿼리 계획 및 실행 워크플로우를 종합적으로 도식한 것입니다.

![\[리더 노드의 쿼리 계획 및 실행 워크플로입니다.\]](http://docs.aws.amazon.com/ko_kr/redshift/latest/dg/images/07-QueryPlanning.png)


쿼리 계획 및 실행 워크플로우는 다음과 같은 단계로 구성됩니다.

1. 리더 노드가 쿼리를 수신하고 SQL 구문을 분석합니다.

1. 구문 분석기가 원래 쿼리에 대한 논리적 표현인 초기 쿼리 트리를 생성합니다. 그런 다음 Amazon Redshift는 이 쿼리 트리를 쿼리 옵티마이저에 입력합니다.

1. 옵티마이저가 평가를 거쳐 필요하다고 판단되면 쿼리를 재작성하여 효율성을 극대화합니다. 이 프로세스는 간혹 관련 쿼리를 다수 생성하여 단일 쿼리를 대체하기도 합니다.

1. 옵티마이저가 성능을 극대화하는 쿼리 실행 계획(이전 단계에서 다수의 쿼리를 생성한 경우에는 쿼리 실행 계획들)을 작성합니다. 쿼리 계획이 조인 유형, 조인 순서, 집계 옵션, 데이터 분산 요건 등 실행 옵션을 지정합니다.

   [EXPLAIN](r_EXPLAIN.md) 명령을 사용하면 쿼리 계획을 확인할 수 있습니다. 쿼리 계획은 복합 쿼리를 분석하여 튜닝할 수 있는 기본 도구입니다. 자세한 내용은 [쿼리 계획 생성 및 해석](c-the-query-plan.md) 섹션을 참조하세요.

1. 실행 엔진이 쿼리 계획을 *단계*, *세그먼트* 및 *스트림*으로 변환합니다.  
**단계**  
각 단계는 쿼리 실행 시 필요한 개별 작업을 의미합니다. 컴퓨팅 노드는 이러한 단계들을 조합하여 쿼리, 조인 또는 기타 데이터베이스 작업 등을 실행할 수 있습니다.  
**세그먼트**  
단일 프로세스에서 실행할 수 있는 몇몇 단계들의 조합인 동시에 컴퓨팅 노드 조각에서 실행할 수 있는 가장 작은 컴파일 유닛입니다. 여기서 *조각*이란 Amazon Redshift의 병렬 처리 유닛을 말합니다. 스트림을 구성하는 세그먼트들은 병렬로 실행됩니다.  
**Stream**  
사용 가능한 컴퓨팅 노드 조각 위로 분할되는 세그먼트 집합입니다.

   실행 엔진은 단계, 세그먼트 및 스트림을 기준으로 컴파일 코드를 작성합니다. 컴파일 코드는 인터프리터 코드보다 실행 속도가 빠르고 사용하는 컴퓨팅 용량도 적습니다. 이렇게 작성된 컴파일 코드는 컴퓨팅 노드로 브로드캐스팅됩니다.
**참고**  
쿼리를 벤치마킹할 때는 항상 두 번째 쿼리의 실행 시간을 비교해야 합니다. 첫 번째 쿼리의 실행 시간에는 코드를 컴파일하는 오버헤드가 포함되기 때문입니다. 자세한 내용은 [쿼리 성능에 영향을 미치는 요인](c-query-performance.md) 섹션을 참조하세요.

1. 컴퓨팅 노드 조각이 쿼리 세그먼트를 병렬 방식으로 실행합니다. 이때 Amazon Redshift는 최적화된 네트워크 통신, 메모리 및 디스크 관리를 활용하여 한 쿼리 계획 단계의 중간 결과를 다음 쿼리 계획 단계로 전달합니다. 이는 쿼리 실행 속도를 높이는 데도 유용합니다.

5단계와 6단계는 각 스트림마다 한 번씩 일어납니다. 엔진은 스트림마다 실행 가능한 세그먼트를 생성하여 컴퓨팅 노드로 전송합니다. 이렇게 스트림 하나의 세그먼트가 완료되면 다음 스트림을 위한 세그먼트가 엔진에서 생성됩니다. 이러한 방식으로 엔진은 이전 스트림의 작업 내용(작업의 디스크 기반 여부 등)을 분석하여 다음 스트림의 세그먼트 생성에도 영향을 미칩니다.

컴퓨팅 노드에서 작업을 마치면 최종 처리를 위해 쿼리 결과를 다시 리더 노드로 보냅니다. 리더 노드는 수신되는 데이터를 단일 결과 집합으로 병합하여 필요에 따라 정렬 또는 집계를 실행합니다. 그런 다음 결과를 클라이언트에게 돌려보냅니다.

**참고**  
컴퓨팅 노드는 쿼리 실행 도중 필요에 따라 일부 데이터를 리더 노드로 다시 보낼 수도 있습니다. 예를 들어 LIMIT 절에 하위 쿼리가 있는 경우에는 리더 노드에 먼저 제한이 적용된 후에 데이터가 추가 처리를 위해 클러스터로 재분산됩니다.

# 쿼리 계획 생성 및 해석
<a name="c-the-query-plan"></a>

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

쿼리 계획을 작성하려면 실제 쿼리 텍스트와 함께 [EXPLAIN](r_EXPLAIN.md) 명령을 실행하세요. 쿼리 계획은 다음과 같은 정보를 제공합니다.
+ 실행 엔진이 수행한 작업(상향식 결과 확인)
+ 각 작업이 수행한 단계 유형
+ 각 작업에서 사용되는 테이블과 열
+ 행의 수와 데이터 폭(바이트)을 기준으로 한 각 작업의 데이터 처리량
+ 상대적 작업 비용. 여기에서 *비용*이란 계획 내 각 단계의 상대적 실행 시간을 비교하는 척도를 말합니다. 그렇다고 비용이 실제 실행 시간이나 메모리 사용량에 대해 정확한 정보를 제공하는 것은 아니며, 마찬가지로 실행 계획 간 비교가 유의미하지도 않습니다. 단지 쿼리에서 가장 많은 리소스를 사용하는 작업이 무엇인지 알려주는 역할을 합니다.

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

EXPLAIN에서 출력되는 쿼리 계획은 쿼리 실행을 단순하지만 종합적으로 나타냅니다. 병렬 쿼리 처리에 대한 세부 정보까지 표시하지는 않습니다. 세부 정보를 보려면 쿼리 자체를 실행한 후 SVL\$1QUERY\$1SUMMARY 또는 SVL\$1QUERY\$1REPORT 뷰에서 쿼리 요약 정보를 가져옵니다. 이러한 뷰를 사용하는 자세한 방법은 [쿼리 요약 분석](c-analyzing-the-query-summary.md) 섹션을 참조하세요.

다음은 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 연산자
<a name="EXPLAIN-operators"></a>

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

### 순차적 스캔 연산자
<a name="scan-operator"></a>

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

### 조인 연산자
<a name="join-operators"></a>

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

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

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

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

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

### 집계 연산자
<a name="aggregate-operators"></a>

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

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

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

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

### 정렬 연산자
<a name="sort-operators"></a>

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

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

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

### UNION, INTERSECT 및 EXCEPT 연산자
<a name="UNION-INTERSECT-and-EXCEPT-operators"></a>

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

  UNION 쿼리를 실행하는 데 사용됩니다.
+ **Hash Intersect Distinct **

   쿼리를 실행하는 데 사용됩니다.
+ **SetOp Except**

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

### 기타 연산자
<a name="other-operators"></a>

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

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

  LIMIT 절을 처리합니다.
+ **창**

  창 함수를 실행합니다.
+ **결과**

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

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

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

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

## EXPLAIN의 조인 유형
<a name="joins-in-EXPLAIN"></a>

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

### 조인 유형 예
<a name="join-types"></a>

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

#### 예: 두 테이블의 해시 조인
<a name="hash-join-two-tables"></a>

다음은 CATID 열을 기준으로 EVENT와 CATEGORY를 조인하는 쿼리입니다. CATID는 CATEGORY에서는 분산 및 정렬 키이지만 EVENT에서는 그렇지 않습니다. 이때는 해시 조인이 EVENT를 외부 테이블로, 그리고 CATEGORY를 내부 테이블로 실행됩니다. CATEGORY가 작은 테이블이기 때문에 플래너는 쿼리를 처리하면서 DS\$1BCAST\$1INNER를 사용하여 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 스캔을 시작하려면 해시 작업이 완전히 끝날 때까지 기다려야 합니다.

#### 예: 두 테이블의 병합 조인
<a name="merge-join-two-tables"></a>

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

```
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)
```

#### 예: 조인, 집계 및 정렬
<a name="join-aggregate-and-sort-example"></a>

다음은 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)
```

### 데이터 재분산
<a name="data-redistribution"></a>

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

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

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

  전체 내부 테이블의 복사본이 모든 컴퓨팅 노드로 브로드캐스팅됩니다.
+ **DS\$1DIST\$1ALL\$1NONE**

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

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

  내부 테이블이 재분산됩니다.
+ **DS\$1DIST\$1OUTER**

  외부 테이블이 재분산됩니다.
+ **DS\$1DIST\$1ALL\$1INNER**

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

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

# 쿼리 계획 단계 검토
<a name="reviewing-query-plan-steps"></a>

EXPLAIN 명령을 실행하면 쿼리 계획의 각 단계를 볼 수 있습니다. 다음 예는 SQL 쿼리를 보여 주며 출력을 설명합니다. 쿼리 계획을 아래에서 위로 읽어보면 쿼리를 실행하는 데 사용한 로직 작업을 개별적으로 확인할 수 있습니다. 자세한 내용은 [쿼리 계획 생성 및 해석](c-the-query-plan.md) 섹션을 참조하세요.

```
explain
select eventname, sum(pricepaid) from sales, event
where sales.eventid = event.eventid
group by eventname
order by 2 desc;
```

```
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)
```

쿼리 계획 생성의 일부로 쿼리 최적화 프로그램은 계획을 스트림, 세그먼트 및 단계로 나눕니다. 쿼리 최적화 프로그램은 컴퓨팅 노드에 데이터 및 쿼리 워크로드를 분산할 준비를 위해 계획을 세분화합니다. 스트림, 세그먼트 및 단계에 대한 자세한 내용은 [쿼리 계획 및 실행 워크플로우](c-query-planning.md) 섹션을 참조하세요.

다음 그림은 위의 쿼리 및 관련 쿼리 계획을 보여줍니다. Amazon Redshift가 컴퓨팅 노드 조각에 대해 컴파일된 코드를 생성할 때 사용하는 단계에 쿼리 작업이 매핑되는 방법을 보여줍니다. 각 쿼리 계획 작업은 세그먼트 내 다수의 단계로 매핑되고, 간혹 스트림 내 다수의 세그먼트로 매핑되기도 합니다.

![\[3개의 스트림에 매핑된 쿼리 및 관련 쿼리 계획입니다.\]](http://docs.aws.amazon.com/ko_kr/redshift/latest/dg/images/map-plan-to-streams.png)


이 그림에서 쿼리 최적화 프로그램은 다음과 같이 쿼리 계획을 실행합니다.

1. `Stream 0`에서 쿼리는 순차적 스캔 작업과 함께 `Segment 0`을 실행하여 `events` 테이블을 스캔합니다. 쿼리는 해시 작업과 함께 `Segment 1`을 계속하여 조인에 내부 테이블에 대한 해시 테이블을 만듭니다.

1. `Stream 1`에서 쿼리는 순차적 스캔 작업과 함께 `Segment 2`을 실행하여 `sales` 테이블을 스캔합니다. 해시 조인과 함께 `Segment 2`를 계속하여 조인 열이 둘 다 분산 키 및 정렬 키가 아닌 테이블을 조인합니다. 해시 집계와 함께 `Segment 2`를 다시 계속하여 결과를 집계합니다. 그런 다음 쿼리는 해시 집계 작업과 함께 `Segment 3`을 실행하여 정렬되지 않은 그룹화된 집계 함수 및 정렬 작업을 수행하여 ORDER BY 절 및 다른 정렬 작업을 평가합니다.

1. `Stream 2`에서 쿼리는 `Segment 4` 및 `Segment 5`에서 네트워크 작업을 실행하여 추가 처리를 위해 중간 결과를 리더 노드로 보냅니다.

쿼리의 마지막 세그먼트는 데이터를 반환합니다. 반환 집합이 집계되거나 정렬되면 컴퓨팅 노드는 각각 중간 결과의 조각을 리더 노드로 보냅니다. 그런 다음 리더 노드는 최종 결과를 요청하는 클라이언트로 다시 전송할 수 있도록 데이터를 병합합니다.

EXPLAIN 연산자에 대한 자세한 내용은 [EXPLAIN](r_EXPLAIN.md) 섹션을 참조하세요.

# 쿼리 성능에 영향을 미치는 요인
<a name="c-query-performance"></a>

쿼리 성능에 영향을 미칠 수 있는 요인은 매우 많습니다. 아래 설명하는 데이터, 클러스터 및 데이터베이스 작업 요소들은 모두 쿼리를 빠르게 처리하는 데 일조합니다.
+ **노드, 프로세서 또는 조각 수** – 컴퓨팅 노드는 조각으로 분할됩니다. 노드가 많아질수록 프로세서와 조각의 수도 늘어난다는 것을 의미합니다. 그러면 쿼리를 다수의 조각으로 나눠 동시에 실행하기 때문에 쿼리의 처리 속도를 높일 수 있습니다. 하지만 노드 추가는 비용 증가를 의미하기 때문에 시스템에 적합한 가성비를 찾는 것이 중요합니다. Amazon Redshift 클러스터 아키텍처에 대한 자세한 내용은 [데이터 웨어하우스 시스템 아키텍처](c_high_level_system_architecture.md) 섹션을 참조하세요.
+ **노드 유형** - Amazon Redshift 클러스터는 여러 노드 유형 중 하나를 사용할 수 있습니다. 각 노드 유형마다 크기와 제한이 다르기 때문에 상황에 따라 클러스터를 확장하는 데도 좋습니다. 노드 크기는 스토리지 용량, 메모리, CPU, 그리고 클러스터의 각 노드 요금을 결정합니다. 노드 유형에 대한 자세한 내용은 *Amazon Redshift 관리 가이드*의 [Amazon Redshift 클러스터의 개요](https://docs.aws.amazon.com/redshift/latest/mgmt/working-with-clusters.html#working-with-clusters-overview) 섹션을 참조하세요.
+ **데이터 분산** - Amazon Redshift는 테이블의 배포 스타일에 따라 테이블 데이터를 컴퓨팅 노드에 저장합니다. 쿼리를 실행하면 쿼리 옵티마이저가 필요에 따라 데이터를 컴퓨팅 노드로 다시 분산시켜 조인 및 집계를 실행합니다. 테이블에 적합한 분산 스타일을 선택하면 조인에 앞서서 데이터를 필요한 것에 배치하여 재분산 단계의 영향을 최소화하는 데 효과적입니다. 자세한 내용은 [쿼리 최적화를 위한 데이터 배포](t_Distributing_data.md) 섹션을 참조하세요.
+ **데이터 정렬 순서** – Amazon Redshift는 테이블의 정렬 키에 따라 테이블 데이터를 정렬 순서대로 디스크에 저장합니다. 쿼리 옵티마이저와 쿼리 프로세서는 데이터 배치 정보를 사용하여 스캔할 블록 수를 줄이기 때문에 쿼리 속도를 높이는 데도 유용합니다. 자세한 내용은 [정렬 키](t_Sorting_data.md) 섹션을 참조하세요.
+ **데이터 집합 크기** – 클러스터의 데이터 볼륨이 높을수록 스캔 및 재분산이 필요한 행이 늘어나기 때문에 쿼리 성능이 느려질 수 있습니다. 이때는 주기적인 정리 작업(VACUUM)과 데이터 아카이빙, 그리고 조건자를 사용한 쿼리 데이터 세트 제한을 통해 이러한 성능 문제를 완화할 수 있습니다.
+ **동시 작업** – 한 번에 다수의 작업을 실행하면 쿼리 성능에 영향을 미칠 수 있습니다. 각 작업마다 유효한 쿼리 대기열에서 슬롯을 1개 이상 사용할 뿐만 아니라 각 슬롯에 연결되어 있는 메모리를 사용하기 때문입니다. 다른 작업이 실행 중이라면 사용 가능한 쿼리 대기열 슬롯 수가 부족할 수 있습니다. 이로 인해 사용 가능한 슬롯이 열릴 때까지 기다린 후에 쿼리 처리를 시작할 수 있습니다. 쿼리 대기열의 생성 및 구성에 대한 자세한 내용은 [워크로드 관리](cm-c-implementing-workload-management.md) 섹션을 참조하세요.
+ **쿼리 구조** – 쿼리 작성 방식 또한 성능에 영향을 미칩니다. 따라서 가능하다면 요건에 따라 최소한의 데이터를 처리하여 반환할 수 있도록 쿼리를 작성하는 것이 좋습니다. 자세한 내용은 [Amazon Redshift 쿼리 설계 모범 사례](c_designing-queries-best-practices.md) 섹션을 참조하세요.
+ **코드 컴파일** – Amazon Redshift는 각 쿼리 실행 계획마다 최적화된 코드를 생성하여 컴파일합니다. 컴파일 코드는 인터프리터 사용에 따른 오버헤드를 제거하기 때문에 실행 속도가 더욱 빠릅니다. Amazon Redshift는 컴파일된 코드의 성능 이점을 유지하면서 새 쿼리의 지연 시간을 최소화하기 위해 구성이라는 기술을 사용합니다. 구성은 새로운 쿼리를 즉시 처리하는 동시에 고도로 최적화된 쿼리별 코드를 백그라운드에서 컴파일하는 기존 로직의 간단한 배열을 생성합니다. 이렇게 하면 쿼리 실행의 중요한 경로에서 컴파일이 제거되므로 새 쿼리가 더 빠르게 시작되고 후속 실행과 일치하는 성능을 제공합니다.

  또한 Amazon Redshift는 서버리스 컴파일 서비스를 사용하여 Amazon Redshift 클러스터의 컴퓨팅 리소스 이상으로 쿼리 컴파일을 확장합니다. 컴파일된 코드 세그먼트는 클러스터의 로컬 캐시와 클러스터 재부팅 후에도 유지되는 사실상 무제한의 원격 캐시에 모두 저장됩니다. 동일한 쿼리의 후속 실행은 컴파일 단계를 건너뛸 수 있기 때문에 더 빠르게 실행됩니다. Amazon Redshift는 확장 가능한 컴파일 서비스를 사용하여 코드를 병렬로 컴파일하여 일관되게 빠른 성능을 제공합니다.

# 쿼리 분석 및 개선 사항
<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;
```

# 쿼리 문제 해결
<a name="queries-troubleshooting"></a>

이 섹션은 Amazon Redshift 쿼리를 실행할 때 발생할 수 있는 가장 공통적이고 심각한 문제 몇 가지를 식별하여 해결할 수 있는 빠른 참조를 제공합니다.

**Topics**
+ [연결 실패](queries-troubleshooting-connection-fails.md)
+ [쿼리 중단](queries-troubleshooting-query-hangs.md)
+ [쿼리가 너무 오래 걸림](queries-troubleshooting-query-takes-too-long.md)
+ [로드 실패](queries-troubleshooting-load-fails.md)
+ [로드가 너무 오래 걸림](queries-troubleshooting-load-takes-too-long.md)
+ [로드 데이터가 잘못됨](queries-troubleshooting-load-data-incorrect.md)
+ [JDBC Fetch Size 파라미터 설정](set-the-JDBC-fetch-size-parameter.md)

다음은 처음에 문제를 해결할 때 시도할 수 있는 권장 방법입니다. 자세한 내용은 다음 리소스를 참조할 수도 있습니다.

애플리케이션에 영향을 미칠 수 있는 Amazon Redshift 기능의 동작 변경에 대한 자세한 내용은 [동작 변경 사항](https://docs.aws.amazon.com/redshift/latest/mgmt/behavior-changes.html)을 참조하세요.
+ [Amazon Redshift 클러스터 및 데이터베이스 액세스](https://docs.aws.amazon.com/redshift/latest/mgmt/using-rs-tools.html)
+ [자동 테이블 최적화](t_Creating_tables.md)
+ [Amazon Redshift에서 데이터 로드](t_Loading_data.md)
+ [튜토리얼: Amazon S3에서 데이터 로드](tutorial-loading-data.md)

# 연결 실패
<a name="queries-troubleshooting-connection-fails"></a>

다음과 같은 이유로 쿼리 연결이 실패할 수 있습니다. 먼저 다음 문제 해결 접근 방식을 따르는 것이 좋습니다.

**클라이언트가 서버에 연결할 수 없음**  
SSL 또는 서버 인증서를 사용할 때는 먼저 연결 문제를 해결하면서 이러한 복잡성을 제거했다가 그런 다음 해결책을 발견하였을 때 SSL 또는 서버 인증서를 다시 추가합니다. 자세한 내용은 *Amazon Redshift 관리 가이드*의 [연결에 대한 보안 옵션 구성](https://docs.aws.amazon.com/redshift/latest/mgmt/connecting-ssl-support.html) 섹션을 참조하세요.

**연결 거부**  
일반적으로 연결 구성에 실패했다는 오류 메시지가 수신되면 클러스터에 대한 액세스 권한과 관련된 문제를 의미합니다. 자세한 내용은 *Amazon Redshift 관리 가이드*의 [연결이 거부되거나 실패함](https://docs.aws.amazon.com/redshift/latest/mgmt/connecting-refusal-failure-issues.html) 섹션을 참조하세요.

# 쿼리 중단
<a name="queries-troubleshooting-query-hangs"></a>

다음과 같은 이유로 쿼리가 중단되거나 응답이 중지될 수 있습니다. 먼저 다음 문제 해결 접근 방식을 따르는 것이 좋습니다.

**데이터베이스 연결 해제**  
최대 전송 단위(MTU)의 크기를 줄이세요. 단일 이더넷 프레임으로 네트워크 연결을 통해 전송할 수 있는 패킷의 최대 크기(바이트)는 MTU의 크기에 따라 결정됩니다. 자세한 내용은 *Amazon Redshift 관리 가이드*의 [데이터베이스 연결이 끊어짐](https://docs.aws.amazon.com/redshift/latest/mgmt/connecting-drop-issues.html) 섹션을 참조하세요.

**데이터베이스 연결 시간 초과**  
COPY 명령 같은 긴 쿼리를 실행할 때는 데이터베이스에 대한 클라이언트 연결이 멈추거나 제한 시간에 걸릴 수 있습니다. 이런 경우 Amazon Redshift 콘솔에서 쿼리의 완료 여부를 관찰할 수 있지만 클라이언트 도구에는 쿼리가 여전히 실행 중인 것으로 표시됩니다. 쿼리 결과는 연결 중단 시점에 따라 누락되거나 불완전할 수도 있습니다. 이러한 문제는 중간 네트워크 구성 요소에서 유휴 상태의 연결을 종료할 때 발생합니다. 자세한 내용은 *Amazon Redshift 관리 가이드*의 [방화벽 시간 제한 문제](https://docs.aws.amazon.com/redshift/latest/mgmt/connecting-firewall-guidance.html) 섹션을 참조하세요.

**ODBC에 클라이언트 측 메모리 부족 오류 발생**  
클라이언트 애플리케이션이 ODBC 연결을 사용하고 쿼리가 메모리에 비해 너무 큰 결과 집합을 생성하는 경우, 커서를 사용하여 결과 집합을 클라이언트 애플리케이션으로 스트리밍할 수 있습니다. 자세한 내용은 [DECLARE](declare.md) 및 [커서 사용 시 성능 고려사항](declare.md#declare-performance) 섹션을 참조하세요.

**JDBC에 클라이언트 측 메모리 부족 오류 발생**  
JDBC 연결을 통해 대용량의 결과 집합을 가져오려고 하면 클라이언트 측 메모리 부족 오류가 발생할 수 있습니다. 자세한 내용은 [JDBC Fetch Size 파라미터 설정](set-the-JDBC-fetch-size-parameter.md) 섹션을 참조하세요.

**잠재적 교착 발생**  
잠재적 교착 상황이 발생하면 다음과 같이 시도하세요.
+ [STV\$1LOCKS](r_STV_LOCKS.md) 및 [STL\$1TR\$1CONFLICT](r_STL_TR_CONFLICT.md) 시스템 테이블을 살펴보면서 테이블을 1개 이상 업데이트하는 데 따른 충돌 유무를 확인하세요.
+ [PG\$1CANCEL\$1BACKEND](PG_CANCEL_BACKEND.md) 함수를 사용하여 충돌하는 쿼리를 1개 이상 취소하세요.
+ [PG\$1TERMINATE\$1BACKEND](PG_TERMINATE_BACKEND.md) 함수를 사용하여 세션을 종료하세요. 그러면 종료된 세션에서 실행 중이던 트랜잭션이 모든 잠금을 강제로 해제하여 트랜잭션을 롤백시킵니다.
+ 주의하여 동시 쓰기 작업을 예약하세요. 자세한 내용은 [동시 쓰기 작업 관리](c_Concurrent_writes.md) 섹션을 참조하세요.

# 쿼리가 너무 오래 걸림
<a name="queries-troubleshooting-query-takes-too-long"></a>

다음과 같은 이유로 쿼리가 너무 오래 걸릴 수 있습니다. 먼저 다음 문제 해결 접근 방식을 따르는 것이 좋습니다.

**테이블이 최적화되지 않음**  
테이블의 정렬 키, 분산 스타일 및 압축 인코딩을 설정하여 병렬 처리를 최대한 이용하세요. 자세한 내용은 [자동 테이블 최적화](t_Creating_tables.md) 섹션을 참조하세요.

**쿼리가 디스크에 쓰고 있음**  
쿼리는 쿼리 실행 중 일부분이라도 디스크에 작성할 수 있습니다. 자세한 내용은 [쿼리 성능 개선](query-performance-improvement-opportunities.md) 섹션을 참조하세요.

**다른 쿼리가 끝날 때까지 쿼리가 대기해야 함**  
이때는 쿼리 대기열을 생성한 후 유형에 따라 쿼리를 적합한 대기열에 할당하면 전반적인 시스템 성능을 개선할 수 있습니다. 자세한 내용은 [워크로드 관리](cm-c-implementing-workload-management.md) 섹션을 참조하세요.

**쿼리가 최적화되지 않음**  
실행 계획을 분석하여 쿼리를 재작성하거나 데이터베이스를 최적화하세요. 자세한 내용은 [쿼리 계획 생성 및 해석](c-the-query-plan.md) 섹션을 참조하세요.

**쿼리 실행에 더 많은 메모리 필요**  
특정 쿼리에 더 많은 메모리가 필요한 경우에는 [wlm\$1query\$1slot\$1count](r_wlm_query_slot_count.md) 파라미터 값을 높여서 사용 가능한 메모리를 늘릴 수 있습니다.

**데이터베이스에서 VACUUM 명령 실행 필요**  
정렬 키 순서에 따라 데이터를 로드하지 않는 경우에는 다수의 행을 추가, 삭제 또는 수정할 때마다 VACUUM 명령을 실행하세요. VACUUM 명령은 데이터를 재구성하여 정렬 순서를 유지하는 동시에 성능을 복원합니다. 자세한 내용은 [테이블 Vacuum](t_Reclaiming_storage_space202.md) 섹션을 참조하세요.

## 장기 실행 쿼리 문제 해결을 위한 추가 리소스
<a name="queries-troubleshooting-cross-refs"></a>

다음은 쿼리 튜닝에 도움이 되는 시스템 뷰 주제 및 기타 설명서 섹션입니다.
+ [STV\$1INFLIGHT](r_STV_INFLIGHT.md) 시스템 뷰는 클러스터에서 실행 중인 쿼리를 보여줍니다. 현재 실행 중이거나 최근에 완료된 쿼리를 확인하려면 [STV\$1RECENTS](r_STV_RECENTS.md)와 함께 사용하면 유용할 수 있습니다.
+ [SYS\$1QUERY\$1HISTORY](SYS_QUERY_HISTORY.md)는 문제 해결에 유용합니다. `running` 또는 `failed`와 같은 현재 상태, 각 쿼리가 실행되는 데 걸린 시간, 쿼리가 동시성 확장 클러스터에서 실행되었는지 여부와 같은 관련 속성과 함께 DDL 및 DML 쿼리를 표시합니다.
+ [STL\$1QUERYTEXT](r_STL_QUERYTEXT.md)는 SQL 명령의 쿼리 텍스트를 수집합니다. 또한 STL\$1QUERYTEXT를 STV\$1INFLIGHT에 결합하는 [SVV\$1QUERY\$1INFLIGHT](r_SVV_QUERY_INFLIGHT.md)는 더 많은 쿼리 메타데이터를 표시합니다.
+ 트랜잭션 잠금 충돌은 쿼리 성능 문제의 원인이 될 수 있습니다. 현재 테이블에 잠금을 유지하고 있는 트랜잭션에 대한 자세한 내용은 [SVV\$1TRANSACTIONS](r_SVV_TRANSACTIONS.md)를 참조하세요.
+ [조정에 가장 적합한 쿼리를 식별](https://docs.aws.amazon.com/redshift/latest/dg/diagnostic-queries-for-query-tuning.html#identify-queries-that-are-top-candidates-for-tuning)하면 최근에 실행한 쿼리 중 가장 많은 시간이 소요된 쿼리를 파악하는 데 도움이 되는 문제 해결 쿼리를 제공합니다. 이를 통해 개선이 필요한 쿼리에 노력을 집중할 수 있습니다.
+ 쿼리 관리를 더 자세히 살펴보고 쿼리 대기열을 관리하는 방법을 이해하려면 [워크로드 관리](cm-c-implementing-workload-management.md)에서 그 방법을 확인하세요.  워크로드 관리는 고급 기능이며 대부분의 경우 자동화된 워크로드 관리를 권장합니다.

# 로드 실패
<a name="queries-troubleshooting-load-fails"></a>

다음과 같은 이유로 데이터 로드가 실패할 수 있습니다. 먼저 다음 문제 해결 접근 방식을 따르는 것이 좋습니다.

**데이터 원본이 다른 AWS 리전에 있습니다**  
기본적으로 COPY 명령에서 지정하는 Amazon S3 버킷 또는 Amazon DynamoDB 테이블은 클러스터와 동일한 AWS 리전에 있어야 합니다. 데이터와 클러스터가 서로 다른 리전에 있는 경우에는 다음과 유사한 오류 메시지가 표시됩니다.

```
The bucket you are attempting to access must be addressed using the specified endpoint.
```

가능하면 클러스터와 데이터 원본이 동일한 리전에 있어야 합니다. 그렇게 해도 COPY 명령에서 [REGION](copy-parameters-data-source-s3.md#copy-region) 옵션을 사용하여 다른 리전을 지정할 수 있습니다.

**참고**  
클러스터와 데이터 원본이 서로 다른 AWS 리전에 있는 경우 데이터 전송 비용이 발생합니다. 또한 지연 시간이 더 깁니다.

**COPY 명령 실패**  
STL\$1LOAD\$1ERRORS에 대한 쿼리를 실행하여 특정 로드 도중 발생한 오류를 식별합니다. 자세한 내용은 [STL\$1LOAD\$1ERRORS](r_STL_LOAD_ERRORS.md) 섹션을 참조하세요.

# 로드가 너무 오래 걸림
<a name="queries-troubleshooting-load-takes-too-long"></a>

다음과 같은 이유로 로드 작업이 너무 오래 걸릴 수 있습니다. 먼저 다음 문제 해결 접근 방식을 따르는 것이 좋습니다.

**COPY가 단일 파일에서 데이터를 로드함**  
로드 데이터를 여러 파일로 분할합니다. 하나의 큰 파일에서 모든 데이터를 로드하면 Amazon Redshift는 훨씬 느린 직렬화된 로드를 수행해야 합니다. 이때 파일 수는 클러스터 조각 수의 배수가 되어야 하며, 파일 크기는 압축 이후 1MB\$11GB에서 대략적으로 동일해야 합니다. 자세한 내용은 [Amazon Redshift 쿼리 설계 모범 사례](c_designing-queries-best-practices.md) 섹션을 참조하세요.

**로드 작업에서 여러 개의 COPY 명령 사용**  
동시에 다수의 COPY 명령을 사용하여 여러 파일에서 테이블 하나를 로드하면 Amazon Redshift가 강제로 직렬화 로딩을 실행하여 속도가 느려집니다. 이때는 COPY 명령을 하나만 사용하세요.

# 로드 데이터가 잘못됨
<a name="queries-troubleshooting-load-data-incorrect"></a>

COPY 작업은 다음과 같은 방법으로 잘못된 데이터를 로드할 수 있습니다. 먼저 다음 문제 해결 접근 방식을 따르는 것이 좋습니다.

**잘못된 파일이 로드됨**  
객체 접두사를 사용하여 데이터 파일을 지정하면 원하지 않는 파일을 읽어올 수 있습니다. 이때는 매니페스트 파일을 사용하여 로드할 파일을 정확히 지정하세요. 자세한 내용은 COPY 명령의 [copy_from_s3_manifest_file](copy-parameters-data-source-s3.md#copy-manifest-file) 옵션과 COPY 예의 [Example: COPY from Amazon S3 using a manifest](r_COPY_command_examples.md#copy-command-examples-manifest) 섹션을 참조하세요.

# JDBC Fetch Size 파라미터 설정
<a name="set-the-JDBC-fetch-size-parameter"></a>

기본적으로 Redshift JDBC 드라이버는 링 버퍼를 사용하여 메모리를 효율적으로 관리하고 메모리 부족 오류를 방지합니다. 가져오기 크기 파라미터는 링 버퍼가 명시적으로 비활성화된 경우에만 적용됩니다. 자세한 내용은 [링크](https://docs.aws.amazon.com/redshift/latest/mgmt/jdbc20-configuration-options.html#jdbc20-enablefetchringbuffer-option)를 참조하세요. 이 구성에서는 가져오기 크기를 설정하여 각 배치에서 검색되는 행 수를 제어해야 합니다.

다음과 같은 경우 가져오기 크기 파라미터를 사용합니다.
+ 행 기반 일괄 처리를 세밀하게 제어해야 하는 경우
+ 기존 가져오기 크기 동작이 필요한 레거시 애플리케이션 작업

링 버퍼가 비활성화된 경우, JDBC 드라이버는 기본적으로 쿼리의 모든 결과를 한 번에 수집합니다. 큰 결과 집합을 반환하는 쿼리는 과도한 메모리를 소비할 수 있습니다. 결과 세트를 한 번에 모두 검색하는 대신 배치로 검색하려면 애플리케이션에서 JDBC 가져오기 크기 파라미터를 설정합니다.

**참고**  
ODBC는 Fetch Size 파라미터가 지원되지 않습니다.

성능을 최적화하려면 메모리 부족 오류가 일어나지 않는 범위 내에서 페치 크기 값을 가장 높게 설정하세요. 페치 크기 값이 더 낮아지면 서버 전송이 늘어나서 실행 시간이 장기화될 수 있습니다. 서버는 클라이언트가 전체 결과 집합을 가져오거나 쿼리가 취소될 때까지 WLM 쿼리 슬롯이나 연결 메모리를 비롯한 리소스를 예약합니다. 이때 Fetch Size 값을 적절히 조정하면 이러한 리소스가 더욱 빠르게 해제되어 다른 쿼리에서도 사용할 수 있게 됩니다.

**참고**  
대용량 데이터 집합을 추출해야 하는 경우에는 [UNLOAD](https://docs.aws.amazon.com/redshift/latest/dg/r_UNLOAD.html) 문을 사용하여 데이터를 Amazon S3로 전송하는 것이 좋습니다. UNLOAD를 사용하면 컴퓨팅 노드가 병렬로 실행되어 데이터 전송 속도가 빨라집니다.

JDBC Fetch Size 파라미터 설정에 대한 자세한 내용은 PostgreSQL 설명서의 [Getting results based on a cursor](https://jdbc.postgresql.org/documentation/query/#getting-results-based-on-a-cursor)에서 확인할 수 있습니다.