병렬 쿼리가 어떻게 동작하나

병렬 쿼리가 어떻게 동작하나 (How Parallel Query Works)

느린 쿼리가 있을 때 PostgreSQL은 여러 프로세스가 함께 일해서 결과를 빨리 내는 법을 고민해요. 바로 병렬 쿼리(parallel query)예요. 옵티마이저가 어떤 쿼리에 병렬 쿼리가 가장 빠른 실행 전략이라고 판단하면, Gather 또는 Gather Merge 노드를 포함한 쿼리 계획을 만들어요. 이 장에서는 그게 어떻게 동작하는지 들여다볼게요.

출처: 공식문서

간단한 예: EXPLAIN으로 보기

간단한 예를 볼게요:

EXPLAIN SELECT * FROM pgbench_accounts WHERE filler LIKE '%x%';
                                     QUERY PLAN
-------------------------------------------------------------------​------------------
 Gather  (cost=1000.00..217018.43 rows=1 width=97)
   Workers Planned: 2
   ->  Parallel Seq Scan on pgbench_accounts  (cost=0.00..216018.33 rows=1 width=97)
         Filter: (filler ~~ '%x%'::text)
(4 rows)

Gather / Gather Merge 노드

모든 경우에서 Gather 또는 Gather Merge 노드는 정확히 하나의 자식 계획을 가져요. 그것이 병렬로 실행될 계획 부분이죠. Gather 또는 Gather Merge 노드가 계획 트리의 맨 위에 있으면 쿼리 전체가 병렬로 실행돼요. 계획 트리의 다른 곳에 있으면 그 아래의 계획 부분만 병렬로 실행돼요. 위 예제에서 쿼리는 테이블 하나만 접근하므로 Gather 노드 말고는 계획 노드가 하나뿐이에요. 그 계획 노드가 Gather 노드의 자식이라 병렬로 실행되죠.

워커(worker) 개수

EXPLAIN으로 플래너가 선택한 워커 개수를 볼 수 있어요. 쿼리 실행 중 Gather 노드에 도달하면 사용자 세션을 구현하는 프로세스가, 플래너가 선택한 워커 수만큼 **백그라운드 워커 프로세스(background worker processes)**를 요청해요.

플래너가 고려할 백그라운드 워커 수는 max_parallel_workers_per_gather로 제한돼요. 그리고 어떤 시점에 존재할 수 있는 백그라운드 워커의 총 수는 max_worker_processesmax_parallel_workers 둘 다에 의해 제한되죠. 그래서 병렬 쿼리가 계획보다 적은 워커로, 심지어 워커 없이 실행될 수도 있어요. 최적 계획은 가용한 워커 수에 따라 달라서, 이러면 쿼리 성능이 나빠질 수 있어요. 그러한 일이 잦다면 max_worker_processesmax_parallel_workers를 늘려 동시에 더 많은 워커를 실행하거나, max_parallel_workers_per_gather를 줄여 플래너가 더 적은 워커를 요청하게 하는 것을 고려해보세요.

리더(leader)의 역할

주어진 병렬 쿼리에 성공적으로 시작된 각 백그라운드 워커 프로세스는 계획의 병렬 부분을 실행해요. **리더(leader)**도 그 계획 부분을 실행하지만, 추가 책임이 하나 있어요. 워커들이 만든 모든 튜플을 읽어야 한다는 거죠.

계획의 병렬 부분이 소수의 튜플만 만들면, 리더는 종종 추가 워커처럼 행동해서 쿼리 실행을 빨라지게 해요. 반대로 병렬 부분이 많은 튜플을 만들면, 리더는 워커가 만든 튜플을 읽고 Gather 또는 Gather Merge 노드 위쪽에 있는 계획 노드가 요구하는 추가 처리 단계를 수행하는 데 거의 전속력으로 바빠져요. 그런 경우 리더는 병렬 부분 실행 작업을 거의 하지 않아요.

Gather vs Gather Merge

계획의 병렬 부분 맨 위 노드가 Gather가 아니라 Gather Merge라면, 병렬 부분을 실행하는 각 프로세스가 정렬된 순서로 튜플을 만들고 있고, 리더가 순서를 보존하는 병합을 수행한다는 뜻이에요. 반대로 Gather는 워커들로부터 편리한 순서대로 튜플을 읽어, 존재했을 수도 있는 정렬 순서를 파괴해요.

더 알아보기 (Learn more)