

# RDS for PostgreSQL의 병렬 쿼리 모범 사례
<a name="PostgreSQL.ParallelQueries"></a>

병렬 쿼리 실행은 PostgreSQL의 기능으로, 단일 SQL 쿼리를 더 작은 작업으로 나누어 여러 백그라운드 작업자 프로세스로 동시에 처리할 수 있습니다. PostgreSQL은 단일 백엔드 프로세스에서 쿼리를 완전히 실행하는 대신에 스캔, 조인, 집계 또는 정렬과 같은 쿼리 부분을 여러 CPU 코어에 분산할 수 있습니다. *리더 프로세스*는 이 실행을 조정하고 *병렬 작업자*로부터 결과를 수집합니다.

그러나 대부분의 프로덕션 워크로드, 특히 동시성이 높은 OLTP 시스템의 경우 자동 병렬 쿼리 실행을 비활성화하는 것이 좋습니다. 병렬 처리는 분석 또는 보고 워크로드의 대규모 데이터 세트에 대한 쿼리를 가속화할 수 있지만, 사용량이 많은 프로덕션 환경에서는 이점을 능가하는 상당한 위험을 초래하는 경우가 자주 있습니다.

병렬 실행에도 상당한 오버헤드가 발생합니다. 각 병렬 작업자는 프로세스 포크(메모리 구조 복사 및 프로세스 상태 초기화) 및 인증(`max_connections` 제한에서 연결 슬롯 사용)이 필요한 전체 PostgreSQL 백엔드 프로세스입니다. 또한 각 작업자는 쿼리당 여러 작업자가 있는 정렬 및 해싱 작업의 `work_mem`을 포함하여 자체 메모리를 사용합니다. 메모리 사용량은 빠르게 증가합니다(예: 작업자 4명 × 쿼리당 64MB `work_mem` = 256MB). 따라서 병렬 쿼리는 단일 프로세스 쿼리보다 훨씬 더 많은 시스템 리소스를 사용할 수 있습니다. 제대로 조정하지 않으면 CPU 포화(여러 작업자가 사용 가능한 처리 용량을 초과함), 컨텍스트 전환 증가(운영 체제가 여러 작업자 프로세스 간에 빈번하게 전환되어 오버헤드가 발생하고 처리량이 감소함) 또는 연결 소진(각 병렬 작업자가 연결 슬롯을 사용하므로 작업자가 4명인 단일 쿼리는 총 5개의 연결(리더 1개 \$1 작업자 4개)을 사용하게 되며, 이는 높은 동시성으로 인해 연결 풀을 빠르게 소진시켜 새로운 클라이언트 연결을 차단하고 애플리케이션 오류를 유발할 수 있음)로 이어질 수 있습니다. 이러한 문제는 여러 쿼리가 동시에 병렬 실행을 시도할 수 있는 동시성이 높은 워크로드에서 특히 심각합니다.

PostgreSQL은 예상 비용을 기반으로 병렬 처리를 사용할지 여부를 결정합니다. 경우에 따라 플래너는 실제로는 이상적이지 않더라도 더 저렴한 것으로 보이는 경우 병렬 계획으로 자동 전환할 수 있습니다. 이는 인덱스 통계가 오래된 경우 또는 순차적 스캔이 인덱스 조회보다 더 매력적으로 보이도록 하는 경우에 발생할 수 있습니다. 이러한 동작 때문에 자동 병렬 실행 계획은 때때로 쿼리 성능이나 시스템 안정성에 회귀를 초래할 수 있습니다.

RDS for PostgreSQL에서 병렬 쿼리의 이점을 최대한 활용하려면 워크로드를 기반으로 병렬 쿼리를 테스트 및 조정하고, 시스템 영향을 모니터링하고, 쿼리 수준 제어를 위해 자동 병렬 계획 선택을 비활성화하는 것이 중요합니다.

## 구성 파라미터
<a name="PostgreSQL.ParallelQueries.ConfigurationParameters"></a>

PostgreSQL은 여러 파라미터를 사용하여 병렬 쿼리의 동작과 가용성을 제어합니다. 이를 이해하고 조정하는 것은 예측 가능한 성능을 달성하는 데 매우 중요합니다.


| 파라미터 | 설명 | 기본값 | 
| --- | --- | --- | 
| max\$1parallel\$1workers | 총 실행할 수 있는 백그라운드 작업자 프로세스의 최대 수 | GREATEST(\$1DBInstanceVCPU/2,8) | 
| max\$1parallel\$1workers\$1per\$1gather | 쿼리 계획 노드당 최대 작업자 수(예: Gather당) | 2 | 
| parallel\$1setup\$1cost | 병렬 쿼리 인프라를 시작하기 위한 플래너 비용 추가 | 1000 | 
| parallel\$1tuple\$1cost | 병렬 모드로 처리된 튜플당 비용(플래너 결정에 영향을 줌) | 0.1 | 
| force\$1parallel\$1mode | 플래너가 병렬 계획(off, on, regress)을 테스트하도록 강제 | off | 

### 주요 고려 사항
<a name="PostgreSQL.ParallelQueries.ConfigurationParameters.KeyConsiderations"></a>
+ `max_parallel_workers`는 병렬 작업자의 총 풀을 제어합니다. 너무 낮게 설정하면 일부 쿼리가 직렬 실행으로 돌아갈 수 있습니다.
+ `max_parallel_workers_per_gather`는 단일 쿼리가 사용할 수 있는 작업자 수에 영향을 줍니다. 값이 높을수록 동시성이 증가하지만 리소스 사용량도 증가합니다.
+ `parallel_setup_cost` 및 `parallel_tuple_cost`는 플래너의 비용 모델에 영향을 미칩니다. 이러한 값을 낮추면 병렬 계획을 선택할 가능성이 높아질 수 있습니다.
+ `force_parallel_mode`는 테스트에 유용하지만 필요한 경우가 아니라면 프로덕션 환경에서 사용해서는 안 됩니다.

**참고**  
`max_parallel_workers` 파라미터의 기본값은 수식 `GREATEST($DBInstanceVCPU/2, 8)`를 사용하여 인스턴스 크기를 기반으로 동적으로 계산됩니다. 즉, vCPU가 많을수록 DB 인스턴스를 더 큰 컴퓨팅 크기로 확장하면 사용 가능한 최대 병렬 작업자 수가 자동으로 증가합니다. 따라서 이전에 직렬로 실행되었거나 병렬 처리가 제한된 쿼리는 스케일 업 작업 후 갑자기 더 많은 병렬 작업자를 활용하여 연결 사용량, CPU 사용률 및 메모리 소비가 예기치 않게 증가할 수 있습니다. 컴퓨팅 스케일링 이벤트 후에는 병렬 쿼리 동작을 모니터링하고 필요한 경우 `max_parallel_workers_per_gather`를 조정하여 예측 가능한 리소스 사용량을 유지하는 것이 중요합니다.

## 병렬 쿼리 사용량 식별
<a name="PostgreSQL.ParallelQueries.IdentifyUsage"></a>

쿼리는 데이터 배포 또는 통계에 따라 병렬 계획으로 전환될 수 있습니다. 예제:

```
SELECT count(*) FROM customers WHERE last_login < now() - interval '6 months';
```

이 쿼리는 최근 데이터에 인덱스를 사용하지만 기록 데이터에 대한 병렬 순차 스캔으로 전환할 수 있습니다.

`auto_explain` 모듈을 로드하여 쿼리 실행 계획을 로깅할 수 있습니다. 자세히 알아보려면 AWS 지식 센터에서 [쿼리 실행 계획 로깅에 관한 문서](https://aws.amazon.com/premiumsupport/knowledge-center/rds-postgresql-tune-query-performance/#)를 참조하세요.

을 참조하세요.

병렬 쿼리 관련 대기 이벤트에 대한 [CloudWatch Database Insights](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Database-Insights-Database-Instance-Dashboard.html)를 모니터링할 수 있습니다. 병렬 쿼리 관련 대기 이벤트에 대해 자세히 알아보려면 [IPC:병렬 대기 이벤트](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/apg-ipc-parallel.html)를 살펴보세요.

PostgreSQL 버전 18에서는 [https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-DATABASE-VIEW](https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-DATABASE-VIEW) 및 [https://www.postgresql.org/docs/current/pgstatstatements.html](https://www.postgresql.org/docs/current/pgstatstatements.html)의 새 열을 사용하여 병렬 작업자 활동을 모니터링할 수 있습니다.
+ `parallel_workers_to_launch`: 시작될 계획인 병렬 작업자 수
+ `parallel_workers_launched`: 실제로 시작된 병렬 작업자 수

이러한 지표는 계획된 병렬 처리와 실제 병렬 처리 간의 불일치를 식별하는 데 도움이 되며, 이는 리소스 제약 또는 구성 문제를 나타낼 수 있습니다. 다음 쿼리를 사용하여 병렬 실행을 모니터링합니다.

데이터베이스 수준 병렬 작업자 지표의 경우:

```
SELECT datname, parallel_workers_to_launch, parallel_workers_launched
FROM pg_stat_database
WHERE datname = current_database();
```

쿼리 수준 병렬 작업자 지표의 경우:

```
SELECT query, parallel_workers_to_launch, parallel_workers_launched
FROM pg_stat_statements
ORDER BY parallel_workers_launched;
```

## 병렬 처리를 제어하는 방법
<a name="PostgreSQL.ParallelQueries.ControlParallelism"></a>

쿼리 병렬 처리를 제어하는 방법에는 여러 가지가 있으며, 각 방법은 다양한 시나리오와 요구 사항에 맞게 설계되었습니다.

전역적으로 자동 병렬 처리를 비활성화하려면 [. 파라미터 그룹을 수정](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_WorkingWithParamGroups.Modifying.html)하여 설정합니다.

```
max_parallel_workers_per_gather = 0;
```

영구 사용자별 설정의 경우 ALTER ROLE 명령은 특정 사용자의 향후 모든 세션에 적용되는 파라미터를 설정하는 방법을 제공합니다.

예제:

`ALTER ROLE username SET max_parallel_workers_per_gather = 4;`는 이 사용자가 데이터베이스에 연결할 때마다 필요한 경우 세션에서 이 병렬 작업자 설정을 사용하도록 합니다.

세션 수준 제어는 현재 데이터베이스 세션 기간 동안 파라미터를 수정하는 SET 명령을 사용하여 수행할 수 있습니다. 이는 다른 사용자나 향후 세션에 영향을 주지 않고 설정을 일시적으로 조정해야 할 때 특히 유용합니다. 일단 설정되면 이러한 파라미터는 명시적으로 재설정되거나 세션이 종료될 때까지 유효합니다. 명령은 간단합니다.

```
SET max_parallel_workers_per_gather = 4;
-- Run your queries
RESET max_parallel_workers_per_gather;
```

보다 세분화된 제어를 위해 SET LOCAL을 사용하면 단일 트랜잭션에 대한 파라미터를 수정할 수 있습니다. 이는 트랜잭션 내의 특정 쿼리 세트에 대한 설정을 조정해야 할 때 이상적이며, 그 이후에는 설정이 자동으로 이전 값으로 되돌아갑니다. 이 접근 방식은 동일한 세션 내의 다른 작업에 의도하지 않은 영향을 방지하는 데 도움이 됩니다.

## 병렬 쿼리 동작 진단
<a name="PostgreSQL.ParallelQueries.Diagnosing"></a>

`EXPLAIN (ANALYZE, VERBOSE)`를 사용하여 쿼리가 병렬 실행을 사용했는지 확인합니다.
+ `Gather`, `Gather Merge` 또는 `Parallel Seq Scan`과 같은 노드를 찾습니다.
+ 병렬 처리가 있는 계획과 없는 계획을 비교합니다.

비교를 위해 병렬 처리를 일시적으로 비활성화하려면: 다음을 수행합니다.

```
SET max_parallel_workers_per_gather = 0;
EXPLAIN ANALYZE <your_query>;
RESET max_parallel_workers_per_gather;
```