

 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/)을 참조하세요.

# 쿼리 최적화를 위한 데이터 배포
데이터 분산

테이블에 데이터를 로드하면 Amazon Redshift가 테이블의 배포 스타일에 따라 테이블 행을 각 컴퓨팅 노드로 분산시킵니다. 쿼리를 실행하면 쿼리 옵티마이저가 필요에 따라 쿼리 행을 컴퓨팅 노드로 다시 분산시켜 조인 및 집계를 실행합니다. 테이블 배포 스타일을 선택하는 목적은 쿼리 실행 이전에 데이터의 분산 위치를 지정하여 재분산 단계의 영향을 최소화하는 데 있습니다.

**참고**  
이 섹션에서는 Amazon Redshift 데이터베이스의 데이터 배포 원칙을 소개합니다. `DISTSTYLE AUTO`로 테이블을 생성하는 것이 좋습니다. 그러면 Amazon Redshift가 자동 테이블 최적화를 사용하여 데이터 배포 스타일을 선택합니다. 자세한 내용은 [자동 테이블 최적화](t_Creating_tables.md) 섹션을 참조하세요. 이 섹션의 나머지 부분에서는 배포 스타일에 대한 세부 정보를 제공합니다.

**Topics**
+ [

## 데이터 분산 개념
](#t_data_distribution_concepts)
+ [

# 분산 스타일
](c_choosing_dist_sort.md)
+ [

# 분산 스타일 보기
](viewing-distribution-styles.md)
+ [

# 쿼리 패턴 평가
](t_evaluating_query_patterns.md)
+ [

# 분산 스타일 지정
](t_designating_distribution_styles.md)
+ [

# 쿼리 계획 평가
](c_data_redistribution.md)
+ [

# 쿼리 계획 예
](t_explain_plan_example.md)
+ [

# 분산 예제
](c_Distribution_examples.md)

## 데이터 분산 개념


다음은 Amazon Redshift에 대한 몇 가지 데이터 배포 개념입니다.

 **노드 및 조각** 

 Amazon Redshift 클러스터는 노드 집합입니다. 클러스터를 구성하는 각 노드는 운영 체제, 전용 메모리 및 전용 디스크 스토리지를 자체적으로 가지고 있습니다. 여기에는 *리더 노드*라고 하는 노드가 하나 있습니다. 리더 노드는 데이터 및 쿼리 처리 태스크를 컴퓨팅 노드로 분산시킬 수 있도록 관리하는 역할을 합니다. *컴퓨팅 노드*는 이러한 태스크를 수행하기 위한 리소스를 제공합니다.

 컴퓨팅 노드의 디스크 스토리지는 다수의 *조각*으로 분할됩니다. 노드당 조각 수는 클러스터의 노드 크기에 따라 달라집니다. 각 노드는 병렬 쿼리 실행에 참여하여 조각에 대한 데이터 분산 작업을 최대한 균일하게 실행합니다. 각 노드 크기의 슬라이스 수에 대한 자세한 내용은 *Amazon Redshift 관리 가이드*의 [클러스터 및 노드 정보](https://docs.aws.amazon.com/redshift/latest/mgmt/working-with-clusters.html#rs-about-clusters-and-nodes) 섹션을 참조하세요.

 **데이터 재분산** 

 테이블에 데이터를 로드하면 Amazon Redshift가 테이블의 배포 스타일에 따라 테이블 행을 각 노드 조각으로 분산시킵니다. 옵티마이저가 쿼리 계획에 따라 최적의 쿼리 실행을 위한 데이터 블록 위치를 결정합니다. 그런 다음 쿼리가 실행되는 동안 데이터가 물리적으로 이동되거나 재배포됩니다. 재분산에는 조인을 위해 특정 행을 노드로 전송하거나, 혹은 전체 테이블을 모든 노드로 브로드캐스팅하는 작업이 포함될 수 있습니다.

 데이터 재분산은 쿼리 계획에서 차지하는 비용 부분이 매우 클 뿐만 아니라 여기에서 발생하는 네트워크 트래픽은 다른 데이터베이스 작업에도 영향을 미쳐 전반적인 시스템 성능이 느려질 수도 있습니다. 하지만 초기에 최적의 데이터 저장 위치를 예상하는 범위까지는 데이터 재분산의 영향을 최소화할 수 있습니다.

 **데이터 분산 목적** 

 데이터를 테이블에 로드할 때는 Amazon Redshift가 테이블 생성 시 선택한 배포 스타일에 따라 테이블의 행을 컴퓨팅 노드 및 조각으로 분산시킵니다. 데이터 분산은 기본적으로 다음과 같은 두 가지 목적이 있습니다.
+ 워크로드를 클러스터 노드에 균일하게 분산시킵니다. 불균일 분산, 즉 데이터 분산 스큐는 일부 노드가 상대적으로 더 많은 작업을 하게 되면서 쿼리 성능을 떨어뜨립니다.
+ 쿼리 실행 시 데이터 이동을 최소화하려면 조인 또는 집계에 참여하는 행이 다른 테이블의 조인 행과 함께 노드에 공동 배치되어 있는 경우에는 옵티마이저가 쿼리 실행 시 공동 배치되어 있는 만큼 데이터를 재분산시킬 필요가 없습니다.

데이터베이스에 선택하는 분산 전략은 쿼리 성능, 스토리지 요건, 데이터 로딩 및 유지 관리에 미치는 비중이 매우 큽니다. 따라서 각 테이블마다 최적의 분산 스타일을 선택해야만 데이터 분산 밸런스를 맞춰 전체적인 시스템 성능을 크게 높일 수 있습니다.

# 분산 스타일


테이블을 생성할 때는 AUTO, EVEN, KEY, ALL 등 네 가지 분산 스타일 중 하나를 지정할 수 있습니다.

배포 스타일을 지정하지 않으면 Amazon Redshift에서는 AUTO 배포를 사용합니다.

 **AUTO 분산** 

AUTO 배포를 사용하면 Amazon Redshift에서는 테이블 데이터의 크기를 기반으로 최적의 배포 스타일을 할당합니다. 예를 들어 AUTO 배포 스타일을 지정하면 Amazon Redshift는 처음에 작은 테이블에 ALL 배포 스타일을 할당합니다. 테이블이 커지면 Amazon Redshift는 기본 키(또는 복합 기본 키의 열)를 배포 키로 선택하여 배포 스타일을 KEY로 변경할 수 있습니다. 테이블이 커지고 배포 키로 적합한 열이 없으면 Amazon Redshift는 배포 스타일을 EVEN으로 변경합니다. 배포 스타일 변경은 사용자 쿼리에 미치는 영향을 최소화하면서 백그라운드에서 이루어집니다.

Amazon Redshift가 테이블 배포 키를 변경하기 위해 자동으로 수행한 작업을 보려면 [SVL\$1AUTO\$1WORKER\$1ACTION](r_SVL_AUTO_WORKER_ACTION.md) 섹션을 참조하세요. 테이블 배포 키 변경에 대한 현재 권장 사항을 보려면 [SVV\$1ALTER\$1TABLE\$1RECOMMENDATIONS](r_SVV_ALTER_TABLE_RECOMMENDATIONS.md) 섹션을 참조하세요.

테이블에 적용된 분산 스타일을 보려면 PG\$1CLASS\$1INFO 시스템 카탈로그 보기를 쿼리합니다. 자세한 내용은 [분산 스타일 보기](viewing-distribution-styles.md) 섹션을 참조하세요. CREATE TABLE 문을 사용해 배포 스타일을 지정하지 않으면 Amazon Redshift에서는 AUTO 배포를 적용합니다.

 **EVEN 분산** 

 리더 노드는 특정 열 값에 상관없이 행을 라운드 로빈 방식으로 조각에 분산시킵니다. EVEN 배포는 테이블이 조인에 참여하지 않는 경우 적합하며, KEY 배포와 ALL 배포 사이에 명확한 선택이 없는 경우에도 적합합니다.

 **KEY 분산** 

 행이 열 1개의 값에 따라 분산됩니다. 리더 노드는 일치하는 값을 동일한 노드 조각에 할당합니다. 조인 키를 기준으로 테이블 페어를 분산시키면 리더 노드가 조인 열의 값에 따라 행을 조각에 공동 배치하기 때문에 공통 열에서 일치하는 값은 물리적으로 함께 저장됩니다. 이렇게 하면 공통 열의 일치하는 값이 물리적으로 함께 저장됩니다.

 **ALL 분산** 

 전체 테이블의 복사본이 모든 노드로 분산됩니다. EVEN 분산이나 KEY 분산은 테이블 행의 일부를 각 노드에 할당하는 반면 ALL 분산은 테이블이 참여하는 조인마다 모든 행을 공동 배치합니다.

 ALL 분산은 필요한 스토리지를 클러스터 노드 수와 곱하기 때문에 데이터를 다수의 테이블에 로드하거나, 업데이트하거나, 삽입하는 데 더 많은 시간이 걸립니다. 따라서 비교적 느리게 이동하는 테이블, 즉 업데이트가 자주 또는 광범위하게 이루어지지 않는 테이블에 한해 적합합니다. 쿼리 중에 작은 테이블을 재분산하는 비용이 적기 때문에 작은 차원 테이블을 DISTSTYLE ALL로 정의하는 이점이 크지 않습니다.

**참고**  
 열에 대한 배포 스타일을 지정하면 Amazon Redshift가 클러스터 수준에서 데이터 배포를 처리합니다. Amazon Redshift는 데이터베이스 객체 내 데이터 분할 개념을 요구하거나 지원하지 않습니다. 따라서 테이블 공간을 생성하거나 테이블 분할 방식을 정의할 필요도 없습니다.

특정 시나리오에서는 이미 생성된 테이블의 분산 스타일을 변경할 수 있습니다. 자세한 내용은 [ALTER TABLE](r_ALTER_TABLE.md) 섹션을 참조하세요. 이미 생성된 테이블의 분산 스타일을 변경할 수 없는 시나리오에서는 테이블을 다시 생성하고 전체 복사를 통해 새 테이블을 채우십시오. 자세한 내용은 [전체 복사 수행](performing-a-deep-copy.md) 섹션을 참조하세요.

# 분산 스타일 보기


테이블의 분산 스타일을 보려면 PG\$1CLASS\$1INFO 보기 또는 SVV\$1TABLE\$1INFO 보기를 쿼리합니다.

PG\$1CLASS\$1INFO의 RELEFFECTIVEDISTSTYLE 열은 테이블의 현재 분산 스타일을 나타냅니다. 테이블에서 자동 분산을 사용하는 경우 RELEFFECTIVEDISTSTYLE은 10, 11 또는 12입니다. 즉, 효과적인 분산 스타일이 AUTO (ALL), AUTO (EVEN) 또는 AUTO (KEY)임을 나타냅니다. 테이블에서 자동 분산을 사용하는 경우 분산 스타일은 처음에 AUTO(ALL)로 표시된 다음 테이블이 커지면 AUTO(EVEN) 또는 AUTO(KEY)로 변경될 수 있습니다.

다음 표는 RELEFFECTIVEDISTSTYLE 열에서 각 값에 따른 분산 스타일을 나타낸 것입니다.


| RELEFFECTIVEDISTSTYLE | 현재 분산 스타일 | 
| --- | --- | 
| 0 | 0 | 
| 1 | 키 | 
| 8 | ALL | 
| 10 | AUTO(ALL) | 
| 11 | AUTO(EVEN) | 
| 12 | AUTO(KEY) | 

SVV\$1TABLE\$1INFO의 DISTSTYLE 열은 테이블의 현재 분산 스타일을 나타냅니다. 테이블에서 자동 분산을 사용하는 경우 DISTSTYLE은 AUTO(ALL), AUTO(EVEN) 또는 AUTO(KEY)입니다.

다음은 분산 스타일 3개와 자동 분산을 사용해 테이블 4개를 생성한 후 SVV\$1TABLE\$1INFO를 쿼리해 분산 스타일을 확인하는 예입니다.

```
create table public.dist_key (col1 int)
diststyle key distkey (col1);

insert into public.dist_key values (1);

create table public.dist_even (col1 int)
diststyle even;

insert into public.dist_even values (1);

create table public.dist_all (col1 int)
diststyle all;

insert into public.dist_all values (1);

create table public.dist_auto (col1 int);

insert into public.dist_auto values (1);

select "schema", "table", diststyle from SVV_TABLE_INFO
where "table" like 'dist%';

        schema   |    table        | diststyle
     ------------+-----------------+------------
      public     | dist_key        | KEY(col1)
      public     | dist_even       | EVEN
      public     | dist_all        | ALL
      public     | dist_auto       | AUTO(ALL)
```

# 쿼리 패턴 평가


 분산 스타일 선택은 데이터베이스 설계의 한 측면에 불과합니다. 분산 스타일은 전체 시스템의 맥락에서 클러스터 크기, 압축 인코딩 방법, 정렬 키, 테이블 제약 조건 등 다른 중요한 요인과 분산의 밸런스를 유지하면서 고려해야 합니다.

 최대한 실제 데이터에 가까운 데이터를 이용하여 시스템을 테스트하십시오.

배포 스타일을 효과적으로 선택하기 위해서는 Amazon Redshift 애플리케이션의 쿼리 패턴을 알고 있어야 합니다. 시스템에서 가장 비용이 높은 쿼리를 식별한 후 이러한 쿼리들의 수요를 기초로 초기 데이터베이스를 설계해야 합니다. 쿼리의 총 비용을 결정하는 요소에는 쿼리를 실행하는 데 걸리는 시간과 쿼리가 소비하는 컴퓨팅 리소스가 포함됩니다. 쿼리 비용을 결정하는 다른 요소는 실행 빈도와 다른 쿼리 및 데이터베이스 작업에 미치는 부정적 영향입니다.

 가장 비용이 높은 쿼리에서 사용되는 테이블을 식별한 후 쿼리 런타임 시 이러한 테이블이 어떠한 역할을 하는지 평가합니다. 그리고, 테이블의 조인 및 집계 방식을 고려합니다.

 각 테이블의 분산 스타일을 선택할 때는 이번 단원의 지침을 따르십시오. 이미 그렇게 배포 스타일을 선택했다면 테이블을 생성한 후 실제 데이터에 최대한 가까운 데이터를 테이블에 로드합니다. 그런 다음 사용할 것으로 예상되는 쿼리 유형에 대해 테이블을 테스트합니다. 쿼리 실행 계획을 평가하여 조정 필요성을 판단할 수 있습니다. 로드 시간, 스토리지 공간 및 쿼리 런타임을 서로 비교하면서 시스템의 전체 요구 사항 간의 밸런스를 유지합니다.

# 분산 스타일 지정


 이번 단원에서 분산 스타일을 지정하는 데 필요한 고려 사항과 권장 사항은 스타 스키마를 예로 사용합니다. 데이터베이스는 스타 스키마, 스타 스키마의 변형 또는 완전히 다른 스키마를 기초로 설계되기도 합니다. Amazon Redshift는 어떤 스키마를 선택하든 효과적으로 호환되도록 설계되었습니다. 이번 단원에서 언급하는 몇 가지 원칙은 어떤 설계 스키마에든 적용될 수 있습니다.

1.  **기본 키와 외래 키를 모든 테이블에 지정하십시오.**

   Amazon Redshift는 기본 키 및 외래 키 제약 조건을 강요하지 않지만 쿼리 옵티마이저는 쿼리 계획을 작성할 때 이러한 제약 조건을 사용합니다. 따라서 기본 키와 외래 키를 설정하는 경우에는 애플리케이션이 키의 유효성을 유지해야 합니다.

1.  **공통 열을 기준으로 팩트 테이블과 가장 큰 차원 테이블을 분산시키십시오.**

   테이블 크기뿐만 아니라 가장 공통적인 조인에 참여하는 데이터 세트의 크기를 기준으로 가장 큰 차원을 선택합니다. 테이블이 WHERE 절을 사용하여 공통적으로 필터링되는 경우에는 행의 일부분만 조인에 참여합니다. 이러한 테이블은 더 많은 데이터를 제공하는 작은 크기의 테이블보다 재분산에 미치는 영향이 작습니다. 차원 테이블의 기본 키와 팩트 테이블의 해당 외래 키를 모두 DISTKEY로 지정합니다. 다수의 테이블이 동일한 분산 키를 사용하는 경우에는 모두 팩트 테이블과 함께 배치됩니다. 팩트 테이블은 분산 키가 1개로 제한됩니다. 따라서 다른 키로 조인하는 테이블은 팩트 테이블과 함께 배치되지 않습니다.

1.  **나머지 차원 테이블의 분산 키를 지정하십시오.**

   다른 테이블과 가장 공통적으로 조인하는 방식에 따라 기본 키 또는 외래 키로 테이블을 분산시킵니다.

1.  **ALL 분산 사용을 위한 일부 차원 테이블의 변경 여부를 평가하십시오.**

   차원 테이블을 팩트 테이블이나 기타 중요한 조인 테이블과 함께 배치할 수 없는 경우에는 전체 테이블을 모든 노드로 분산시켜 쿼리 성능을 크게 높일 수 있습니다. ALL 분산을 사용하면 스토리지 공간 요건이 크게 늘어날 뿐만 아니라 로그 시간 및 유지 관리 작업도 증가합니다. 따라서 ALL 분산을 선택하려면 먼저 모든 인자에 가중치를 반영해야 합니다. 다음 단원에서는 EXPLAIN 계획을 평가하여 ALL 분산에 적합한 테이블 후보를 식별하는 방법에 대해서 설명하겠습니다.

1.  **나머지 테이블에 AUTO 분산을 사용합니다.**

   테이블이 대부분 비정규화되어 조인에 참여하지 않거나, 혹은 다른 분산 스타일을 명확히 구분하여 선택하지 않는 경우에는 AUTO 분산을 사용합니다.

Amazon Redshift에서 적절한 배포 스타일을 선택하도록 하려면 배포 스타일을 명시적으로 지정하지 않습니다.

# 쿼리 계획 평가


쿼리 계획을 사용하여 분산 스타일을 최적화하는 데 적합한 테이블 후보를 식별할 수 있습니다.

초기 설계 의사결정을 마친 후에는 테이블을 생성하여 데이터를 로드하고 나서 테이블을 테스트합니다. 테스트 데이터 세트는 실제 데이터에 최대한 가까운 것으로 사용합니다. 비교 기준으로 사용할 로드 시간을 측정합니다.

가장 비용이 높은 쿼리 중에서도 실행하려고 하는 대표적인 쿼리, 특히 조인 및 집계를 사용하는 쿼리를 평가합니다. 다양한 설계 옵션에 따라 런타임을 비교합니다. 런타임을 비교할 때 첫 번째로 실행하는 쿼리는 제외하세요. 첫 번째 런타임에는 컴파일 시간이 포함되어 있기 때문입니다.

**DS\$1DIST\$1NONE**  
해당하는 조각이 컴퓨팅 노드에 공동 배치되기 때문에 재분산이 필요 없습니다. 일반적으로 팩트 테이블과 차원 테이블 1개 사이의 조인을 의미하는 DS\$1DIST\$1NONE 단계는 한 번만 있습니다.

**DS\$1DIST\$1ALL\$1NONE**  
내부 조인 테이블이 DISTSTYLE ALL을 사용했기 때문에 재분산이 필요 없습니다. 전체 테이블이 모든 노드에 배치됩니다.

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

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

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

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

**DS\$1DIST\$1BOTH**  
두 테이블 모두 재분산됩니다.

**DS\$1DIST\$1ERR**  
테이블에 배포 스타일이 선택되지 않은 경우.

DS\$1DIST\$1NONE과 DS\$1DIST\$1ALL\$1NONE은 유효합니다. 두 옵션은 모든 조인이 공동 배치되기 때문에 해당 단계에서 분산이 필요 없었다는 것을 의미합니다.

DS\$1DIST\$1INNER는 내부 테이블이 노드로 재분산되기 때문에 비교적 높은 비용의 단계가 될 것이라는 것을 의미합니다. 또한 외부 테이블이 이미 조인 키를 기준으로 적절하게 분산된다는 것을 나타내기도 합니다. 이 옵션을 DS\$1DIST\$1NONE으로 변환하려면 내부 테이블의 분산 키를 조인 키로 설정하십시오. 경우에 따라 외부 테이블이 조인 키에 배포되지 않기 때문에 조인 키에 내부 테이블을 배포할 수 없습니다. 이 경우 내부 테이블에 대해 ALL 분포를 사용할지 여부를 평가합니다. 테이블 업데이트가 자주 또는 광범위하게 이루어지지 않는 동시에 높은 재배포 비용을 감당할 정도로 충분히 크다면 배포 스타일을 ALL로 변경하고 다시 테스트합니다. ALL 분산은 로드 시간의 증가를 유발합니다. 따라서 다시 테스트할 때는 평가 요인에 로드 시간도 추가해야 합니다.

DS\$1DIST\$1ALL\$1INNER는 유효하지 않습니다. 이 옵션은 외부 테이블이 DISTSTYLE ALL을 사용하기 때문에 전체 외부 테이블의 복사본이 각 노드에 배치될 수 있도록 내부 테이블 전체가 단일 조각으로 재분산된다는 의미입니다. 이렇게 되면 모든 노드를 통한 병렬 런타임을 이용하는 대신 단일 노드를 통한 조인의 직렬 런타임이라는 비효율적인 결과가 발생합니다. DISTSTYLE ALL은 오직 내부 조인 테이블에서만 사용해야 합니다. 대신에 외부 테이블에서는 분산 키를 지정하거나 EVEN 분산을 사용하십시오.

DS\$1BCAST\$1INNER와 DS\$1DIST\$1BOTH는 유효하지 않습니다. 일반적으로 테이블이 분산 키를 기준으로 조인되지 않아서 이러한 재분산이 일어납니다. 팩트 테이블에 아직 분산 키가 없는 경우에는 조인 열을 두 테이블의 분산 키로 지정하십시오. 팩트 테이블의 다른 열에 이미 재분산 키가 있는 경우에는 이 조인의 공동 배치를 위한 분산 키 변경이 전체 성능 개선에 도움이 되는지 평가합니다. 외부 테이블의 분산 키 변경이 최적의 선택이 아니라면 DISTSTYLE ALL을 내부 테이블에 지정하여 공동 배치를 사용할 수 있습니다.

 다음은 DS\$1BCAST\$1INNER 및 DS\$1DIST\$1NONE 레이블을 포함한 쿼리 계획의 일부를 나타낸 예입니다.

```
->  XN Hash Join DS_BCAST_INNER  (cost=112.50..3272334142.59 rows=170771 width=84)
        Hash Cond: ("outer".venueid = "inner".venueid)
        ->  XN Hash Join DS_BCAST_INNER  (cost=109.98..3167290276.71 rows=172456 width=47)
              Hash Cond: ("outer".eventid = "inner".eventid)
              ->  XN Merge Join DS_DIST_NONE  (cost=0.00..6286.47 rows=172456 width=30)
                    Merge Cond: ("outer".listid = "inner".listid)
                    ->  XN Seq Scan on listing  (cost=0.00..1924.97 rows=192497 width=14)
                    ->  XN Seq Scan on sales  (cost=0.00..1724.56 rows=172456 width=24)
```

DISTSTYLE ALL을 사용하도록 차원 테이블을 변경하고 나면 아래와 같이 동일한 쿼리에 대한 쿼리 계획에서 DS\$1BCAST\$1INNER가 DS\$1DIST\$1ALL\$1NONE으로 바뀐 것을 알 수 있습니다. 또한 조인 단계의 상대적 비용에도 커다란 변화가 있습니다. 총 비용은 `14142.59`이며, 이전 쿼리의 경우 `3272334142.59`였습니다.

```
->  XN Hash Join DS_DIST_ALL_NONE  (cost=112.50..14142.59 rows=170771 width=84)
        Hash Cond: ("outer".venueid = "inner".venueid)
        ->  XN Hash Join DS_DIST_ALL_NONE  (cost=109.98..10276.71 rows=172456 width=47)
              Hash Cond: ("outer".eventid = "inner".eventid)
              ->  XN Merge Join DS_DIST_NONE  (cost=0.00..6286.47 rows=172456 width=30)
                    Merge Cond: ("outer".listid = "inner".listid)
                    ->  XN Seq Scan on listing  (cost=0.00..1924.97 rows=192497 width=14)
                    ->  XN Seq Scan on sales  (cost=0.00..1724.56 rows=172456 width=24)
```

# 쿼리 계획 예


다음 예는 쿼리 계획을 평가하여 분산 최적화의 기회를 찾는 방법을 나타내고 있습니다.

EXPLAIN 명령과 함께 다음 쿼리를 실행하여 쿼리 계획을 산출합니다.

```
explain
select lastname, catname, venuename, venuecity, venuestate, eventname, 
month, sum(pricepaid) as buyercost, max(totalprice) as maxtotalprice
from category join event on category.catid = event.catid
join venue on venue.venueid = event.venueid
join sales on sales.eventid = event.eventid
join listing on sales.listid = listing.listid
join date on sales.dateid = date.dateid
join users on users.userid = sales.buyerid
group by lastname, catname, venuename, venuecity, venuestate, eventname, month
having sum(pricepaid)>9999
order by catname, buyercost desc;
```

TICKIT 데이터베이스에서는 SALES가 팩트 테이블이고, LISTING이 가장 큰 차원 테이블입니다. 두 테이블의 공동 배치를 위해 SALES는 LISTING의 외래 키인 LISTID를 기준으로 분산되고, LISTING은 기본 키인 LISTID를 기준으로 분산됩니다. 다음은 SALES 및 LISTING에 대한 CREATE TABLE 명령을 나타낸 예입니다.

```
create table sales(
	salesid integer not null,
	listid integer not null distkey,
	sellerid integer not null,
	buyerid integer not null,
	eventid integer not null encode mostly16,
	dateid smallint not null,
	qtysold smallint not null encode mostly8,
	pricepaid decimal(8,2) encode delta32k,
	commission decimal(8,2) encode delta32k,
	saletime timestamp,
	primary key(salesid),
	foreign key(listid) references listing(listid),
	foreign key(sellerid) references users(userid),
	foreign key(buyerid) references users(userid),
	foreign key(dateid) references date(dateid))
        sortkey(listid,sellerid);

create table listing(
	listid integer not null distkey sortkey,
	sellerid integer not null,
	eventid integer not null encode mostly16,
	dateid smallint not null,
	numtickets smallint not null encode mostly8,
	priceperticket decimal(8,2) encode bytedict,
	totalprice decimal(8,2) encode mostly32,
	listtime timestamp,
	primary key(listid),
	foreign key(sellerid) references users(userid),
	foreign key(eventid) references event(eventid),
	foreign key(dateid) references date(dateid));
```

다음 쿼리 계획에서는 SALES와 LISTING의 조인을 위한 Merge Join 단계가 재분산이 필요 없다는 의미의 DS\$1DIST\$1NONE을 표시하고 있습니다. 하지만 쿼리 계획 위를 보면 나머지 내부 조인은 쿼리 실행의 일부로서 내부 테이블의 브로드캐스팅을 의미하는 DS\$1BCAST\$1INNER를 표시하고 있습니다. 키 분산을 사용할 경우에는 테이블을 한 쌍만 공동 배치할 수 있기 때문에 테이블 5개를 다시 브로드캐스팅해야 합니다.

```
QUERY PLAN
XN Merge  (cost=1015345167117.54..1015345167544.46 rows=1000 width=103)
  Merge Key: category.catname, sum(sales.pricepaid)
  ->  XN Network  (cost=1015345167117.54..1015345167544.46 rows=170771 width=103)
        Send to leader
        ->  XN Sort  (cost=1015345167117.54..1015345167544.46 rows=170771 width=103)
              Sort Key: category.catname, sum(sales.pricepaid)
              ->  XN HashAggregate  (cost=15345150568.37..15345152276.08 rows=170771 width=103)
                    Filter: (sum(pricepaid) > 9999.00)
	                    ->  XN Hash Join DS_BCAST_INNER  (cost=742.08..15345146299.10 rows=170771 width=103)
	                          Hash Cond: ("outer".catid = "inner".catid)
	                          ->  XN Hash Join DS_BCAST_INNER  (cost=741.94..15342942456.61 rows=170771 width=97)
	                                Hash Cond: ("outer".dateid = "inner".dateid)
	                                ->  XN Hash Join DS_BCAST_INNER  (cost=737.38..15269938609.81 rows=170766 width=90)
	                                      Hash Cond: ("outer".buyerid = "inner".userid)
	                                      ->  XN Hash Join DS_BCAST_INNER  (cost=112.50..3272334142.59 rows=170771 width=84)
	                                            Hash Cond: ("outer".venueid = "inner".venueid)
	                                            ->  XN Hash Join DS_BCAST_INNER  (cost=109.98..3167290276.71 rows=172456 width=47)
	                                                  Hash Cond: ("outer".eventid = "inner".eventid)
	                                                  ->  XN Merge Join DS_DIST_NONE  (cost=0.00..6286.47 rows=172456 width=30)
	                                                        Merge Cond: ("outer".listid = "inner".listid)
	                                                        ->  XN Seq Scan on listing  (cost=0.00..1924.97 rows=192497 width=14)
	                                                        ->  XN Seq Scan on sales  (cost=0.00..1724.56 rows=172456 width=24)
	                                                  ->  XN Hash  (cost=87.98..87.98 rows=8798 width=25)
	                                                        ->  XN Seq Scan on event  (cost=0.00..87.98 rows=8798 width=25)
	                                            ->  XN Hash  (cost=2.02..2.02 rows=202 width=41)
	                                                  ->  XN Seq Scan on venue  (cost=0.00..2.02 rows=202 width=41)
	                                      ->  XN Hash  (cost=499.90..499.90 rows=49990 width=14)
	                                            ->  XN Seq Scan on users  (cost=0.00..499.90 rows=49990 width=14)
	                                ->  XN Hash  (cost=3.65..3.65 rows=365 width=11)
	                                      ->  XN Seq Scan on date  (cost=0.00..3.65 rows=365 width=11)
	                          ->  XN Hash  (cost=0.11..0.11 rows=11 width=10)
	                                ->  XN Seq Scan on category  (cost=0.00..0.11 rows=11 width=10)
```

한 가지 해결책으로 DISTSTYLE ALL을 포함하도록 테이블을 변경할 수 있습니다.

```
ALTER TABLE users ALTER DISTSTYLE ALL;
ALTER TABLE venue ALTER DISTSTYLE ALL;
ALTER TABLE category ALTER DISTSTYLE ALL;
ALTER TABLE date ALTER DISTSTYLE ALL;
ALTER TABLE event ALTER DISTSTYLE ALL;
```

다시 EXPLAIN으로 동일한 쿼리를 실행한 후 새로운 쿼리 계획을 검사합니다. 이제 데이터가 DISTSTYLE ALL을 사용하여 모든 노드에 분산되었기 때문에 재분산이 필요 없다는 의미의 DS\$1DIST\$1ALL\$1NONE이 조인에 표시됩니다.

```
QUERY PLAN
XN Merge  (cost=1000000047117.54..1000000047544.46 rows=1000 width=103)
  Merge Key: category.catname, sum(sales.pricepaid)
  ->  XN Network  (cost=1000000047117.54..1000000047544.46 rows=170771 width=103)
        Send to leader
        ->  XN Sort  (cost=1000000047117.54..1000000047544.46 rows=170771 width=103)
              Sort Key: category.catname, sum(sales.pricepaid)
              ->  XN HashAggregate  (cost=30568.37..32276.08 rows=170771 width=103)
                    Filter: (sum(pricepaid) > 9999.00)
                    ->  XN Hash Join DS_DIST_ALL_NONE  (cost=742.08..26299.10 rows=170771 width=103)
                          Hash Cond: ("outer".buyerid = "inner".userid)
                          ->  XN Hash Join DS_DIST_ALL_NONE  (cost=117.20..21831.99 rows=170766 width=97)
                                Hash Cond: ("outer".dateid = "inner".dateid)
                                ->  XN Hash Join DS_DIST_ALL_NONE  (cost=112.64..17985.08 rows=170771 width=90)
                                      Hash Cond: ("outer".catid = "inner".catid)
                                      ->  XN Hash Join DS_DIST_ALL_NONE  (cost=112.50..14142.59 rows=170771 width=84)
                                            Hash Cond: ("outer".venueid = "inner".venueid)
                                            ->  XN Hash Join DS_DIST_ALL_NONE  (cost=109.98..10276.71 rows=172456 width=47)
                                                  Hash Cond: ("outer".eventid = "inner".eventid)
                                                  ->  XN Merge Join DS_DIST_NONE  (cost=0.00..6286.47 rows=172456 width=30)
                                                        Merge Cond: ("outer".listid = "inner".listid)
                                                        ->  XN Seq Scan on listing  (cost=0.00..1924.97 rows=192497 width=14)
                                                        ->  XN Seq Scan on sales  (cost=0.00..1724.56 rows=172456 width=24)
                                                  ->  XN Hash  (cost=87.98..87.98 rows=8798 width=25)
                                                        ->  XN Seq Scan on event  (cost=0.00..87.98 rows=8798 width=25)
                                            ->  XN Hash  (cost=2.02..2.02 rows=202 width=41)
                                                  ->  XN Seq Scan on venue  (cost=0.00..2.02 rows=202 width=41)
                                      ->  XN Hash  (cost=0.11..0.11 rows=11 width=10)
                                            ->  XN Seq Scan on category  (cost=0.00..0.11 rows=11 width=10)
                                ->  XN Hash  (cost=3.65..3.65 rows=365 width=11)
                                      ->  XN Seq Scan on date  (cost=0.00..3.65 rows=365 width=11)
                          ->  XN Hash  (cost=499.90..499.90 rows=49990 width=14)
                                ->  XN Seq Scan on users  (cost=0.00..499.90 rows=49990 width=14)
```

# 분산 예제
분산 예제

아래 예들은 CREATE TABLE 문에서 정의하는 옵션에 따른 데이터 분산 방식을 나타내고 있습니다.

## DISTKEY 예제
DISTKEY 예제

TICKIT 데이터베이스의 USERS 테이블 스키마를 살펴봅니다. USERID가 SORTKEY 열과 DISTKEY 열로 정의되어 있습니다.

```
select "column", type, encoding, distkey, sortkey 
from pg_table_def where tablename = 'users';
    
    column     |          type          | encoding | distkey | sortkey
---------------+------------------------+----------+---------+---------
 userid        | integer                | none     | t       |       1
 username      | character(8)           | none     | f       |       0
 firstname     | character varying(30)  | text32k  | f       |       0

...
```

이 테이블에서 USERID를 분산 열로 사용하는 것은 좋은 선택입니다. SVV\$1DISKUSAGE 시스템 뷰에 대한 쿼리를 실행하면 테이블이 매우 균일하게 분산되어 있는 것을 볼 수 있습니다. 열 번호는 0부터 시작하기 때문에 USERID의 열 번호는 0입니다.

```
select slice, col, num_values as rows, minvalue, maxvalue
from svv_diskusage
where name='users' and col=0 and rows>0
order by slice, col;

slice| col | rows  | minvalue | maxvalue
-----+-----+-------+----------+----------
0    | 0   | 12496 | 4        | 49987
1    | 0   | 12498 | 1        | 49988
2    | 0   | 12497 | 2        | 49989
3    | 0   | 12499 | 3        | 49990
(4 rows)
```

테이블에 49,990개의 행이 포함되어 있습니다. rows(num\$1values) 열을 보면 각 조각마다 거의 동일한 수의 행이 포함되어 있는 것을 알 수 있습니다. minvalue 열과 maxvalue 열에는 각 조각마다 값의 범위가 표시되어 있습니다. 각 조각마다 거의 전체 범위의 값이 저장되어 있기 때문에 사용자 ID 범위를 필터링하는 쿼리에 모든 조각이 참여할 가능성이 큽니다.

이번 예에서는 작은 테스트 시스템의 분산에 대해서 설명합니다. 일반적으로 전체 조각 수는 이보다 훨씬 많습니다.

STATE 열을 사용해 공통적으로 조인 또는 분류하는 경우에는 STATE 열을 기준으로 분산시킬 수 있습니다. 다음은 USERS 테이블과 동일한 데이터로 새로운 테이블을 생성하지만 DISTKEY를 STATE 열로 설정할 경우를 보여주는 예입니다. 이 경우 배포는 균등하지 않습니다. 조각 0(13,587 행)에는 조각 3(10,150 행)보다 약 30% 많은 행이 저장되어 있습니다. 이보다 훨씬 큰 테이블에서 이러한 크기의 분산 스큐가 발생하면 쿼리 처리에 부정적인 영향을 미칠 수 있습니다.

```
create table userskey distkey(state) as select * from users;

select slice, col, num_values as rows, minvalue, maxvalue from svv_diskusage
where name = 'userskey' and col=0 and rows>0
order by slice, col;

slice | col | rows  | minvalue | maxvalue
------+-----+-------+----------+----------
    0 |   0 | 13587 |        5 |    49989
    1 |   0 | 11245 |        2 |    49990
    2 |   0 | 15008 |        1 |    49976
    3 |   0 | 10150 |        4 |    49986
(4 rows)
```

## DISTSTYLE EVEN 예제
DISTSTYLE EVEN 예제

USERS 테이블과 동일한 데이터로 새로운 테이블을 생성하더라도 DISTSTYLE을 EVEN으로 설정하면 행이 항상 균일하게 조각으로 분산됩니다.

```
create table userseven diststyle even as 
select * from users;

select slice, col, num_values as rows, minvalue, maxvalue from svv_diskusage
where name = 'userseven' and col=0 and rows>0
order by slice, col;

slice | col | rows  | minvalue | maxvalue
------+-----+-------+----------+----------
    0 |   0 | 12497 |        4 |    49990
    1 |   0 | 12498 |        8 |    49984
    2 |   0 | 12498 |        2 |    49988
    3 |   0 | 12497 |        1 |    49989  
(4 rows)
```

하지만 분산이 특정 열을 기준으로 이루어지지 않기 때문에 특히 테이블이 다른 테이블에 조인되는 경우에는 쿼리 처리 성능이 떨어질 수 있습니다. 조인 열에 따른 분산 부재는 종종 효율적으로 실행될 수 있는 유형의 조인 작업에 영향을 미치기도 합니다. 두 테이블이 각각 조인 열을 기준으로 분산 및 정렬되면 조인, 집계, 분류 등의 작업이 최적화됩니다.

## DISTSTYLE ALL 예제
DISTSTYLE ALL 예제

USERS 테이블과 동일한 데이터로 새로운 테이블을 생성하더라도 DISTSTYLE을 ALL로 설정하면 모든 행이 각 노드의 첫 번째 조각으로 분산됩니다.

```
select slice, col, num_values as rows, minvalue, maxvalue from svv_diskusage
where name = 'usersall' and col=0 and rows > 0
order by slice, col;

slice | col | rows  | minvalue | maxvalue
------+-----+-------+----------+----------
    0 |   0 | 49990 |        4 |    49990
    2 |   0 | 49990 |        2 |    49990

(4 rows)
```