PostgreSQL Performance & Query Optimization · 강의

병렬 집계와 해시 조인

대규모 그룹화 작업에 부분 집계와 병렬 인식 조인을 활용하는 방법을 배웁니다.

레슨 3/413개 단계

병렬 집계와 해시 조인은(는) CoddyKit의 무료 PostgreSQL Performance & Query Optimization 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 PostgreSQL Performance & Query Optimization 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. PostgreSQL Performance & Query Optimization 강의에는 총 4개의 강의가 포함되어 있습니다.

이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.

Why Parallelism for Aggregation

Heavy grouping queries such as GROUP BY over hundreds of millions of rows are usually CPU-bound: most time is spent hashing keys and combining values, not waiting on I/O.

A single backend process can only saturate one core. PostgreSQL's parallel query machinery lets the planner split the scan and the aggregation across several parallel workers, each running on its own core, then combine their results.

  • The launching backend is the leader.
  • Extra processes are parallel workers.
  • Work is divided at the table-scan level and merged at the top.

This lesson focuses on two cooperating pieces: parallel aggregation (partial aggregates) and parallel-aware hash joins.

Partial and Finalize Aggregate

Parallel aggregation works by splitting each aggregate into two phases:

  • Partial Aggregate — each worker aggregates its own slice of rows into a partial state (e.g. a running sum and count).
  • Finalize Aggregate — the leader combines those partial states into the final result.

This is possible because aggregates like count, sum, avg, min and max are combinable: a partial result from one worker can be merged with another via a combine function.

You see this in plans as a Partial Aggregate node under Gather and a Finalize Aggregate node above it.

Reading a Parallel Aggregate Plan

Run EXPLAIN on a large grouped query and look for the Finalize / Gather / Partial sandwich. The Gather node is where worker results flow back to the leader.

Note Workers Planned: the planner's intended parallelism. At execution, EXPLAIN ANALYZE also reports Workers Launched, which can be lower if the system ran out of worker slots.

EXPLAIN (COSTS OFF)
SELECT customer_id, sum(amount) AS total
FROM orders
GROUP BY customer_id;

-- Finalize HashAggregate
--   Group Key: customer_id
--   -> Gather
--        Workers Planned: 4
--        -> Partial HashAggregate
--             Group Key: customer_id
--             -> Parallel Seq Scan on orders

Knobs That Gate Parallelism

The planner only considers parallel plans when certain GUCs allow it and when the table is big enough to be worth it.

  • max_parallel_workers_per_gather — max workers a single Gather may use (0 disables parallel query for that node).
  • max_parallel_workers — cap across the whole instance.
  • max_worker_processes — hard OS-level ceiling for all background workers.
  • min_parallel_table_scan_size (default 8MB) — table must exceed this for a parallel scan to be considered.
  • parallel_setup_cost and parallel_tuple_cost — model the overhead of starting workers and shipping tuples.
SET max_parallel_workers_per_gather = 4;
SET max_parallel_workers = 8;

SHOW min_parallel_table_scan_size;   -- 8MB default
SHOW parallel_setup_cost;            -- 1000 default

Forcing Parallelism to Experiment

On small test tables the planner may decide parallelism is not worth the setup cost. To study plans you can bias it heavily, then measure on realistic data.

Setting parallel_setup_cost and parallel_tuple_cost to 0 makes the planner ignore worker startup overhead, so it picks parallel plans even on modest inputs. This is a diagnostic trick, not a production setting.

SET parallel_setup_cost = 0;
SET parallel_tuple_cost = 0;
SET min_parallel_table_scan_size = '0';
SET max_parallel_workers_per_gather = 4;

EXPLAIN (ANALYZE, COSTS OFF)
SELECT region, count(*)
FROM sales
GROUP BY region;

Parallel-Aware Hash Join

A Hash Join builds an in-memory hash table from the smaller (build) side, then probes it with rows from the larger side. In a Parallel Hash Join, the join itself is parallel-aware.

  • The plan node is Parallel Hash Join with an inner Parallel Hash node.
  • All workers cooperate to build one shared hash table in dynamic shared memory.
  • Each worker then probes that shared table with its slice of the outer relation.

This avoids every worker re-building its own private copy of the hash table, saving both CPU and memory.

EXPLAIN (COSTS OFF)
SELECT o.customer_id, sum(o.amount)
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE c.country = 'DE'
GROUP BY o.customer_id;

-- Finalize GroupAggregate
--   -> Gather
--        -> Partial HashAggregate
--             -> Parallel Hash Join
--                  Hash Cond: (o.customer_id = c.id)
--                  -> Parallel Seq Scan on orders o
--                  -> Parallel Hash
--                       -> Parallel Seq Scan on customers c

Shared Hash vs Per-Worker Hash

Be careful distinguishing two superficially similar plans:

  • Parallel Hash Join (with Parallel Hash): workers jointly build one shared hash table. Build cost and memory are shared.
  • Hash Join under Gather (plain Hash): each worker builds its own complete copy of the hash table. The build work and memory are multiplied by the number of workers.

For a large build side, the parallel-aware variant is dramatically cheaper. The planner chooses it when both sides can be scanned in parallel and the join is parallel-safe.

work_mem and the Hash Table

Hash joins and hash aggregates live inside work_mem. If the hash table does not fit, PostgreSQL spills to disk in batches, which is far slower.

In EXPLAIN (ANALYZE, BUFFERS) watch for Batches: N where N > 1 and Disk Usage on hash nodes — signs that work_mem is too small for the build side.

For Parallel Hash, the shared table can use a larger effective budget: the per-worker work_mem allotments are pooled for the one shared hash table, which is another reason the parallel-aware join scales well.

SET work_mem = '256MB';

EXPLAIN (ANALYZE, BUFFERS, COSTS OFF)
SELECT o.product_id, count(*)
FROM orders o
JOIN products p ON p.id = o.product_id
GROUP BY o.product_id;

-- Look for: Parallel Hash  Batches: 1  Memory Usage: ...kB

What Disables Parallelism

The planner refuses parallel plans when the query contains parallel-unsafe elements. Common blockers:

  • Calling a function marked PARALLEL UNSAFE (the default for user-defined functions unless you label them).
  • Writing data: INSERT, UPDATE, DELETE targets (the modifying part runs serially).
  • Cursors / FOR UPDATE row locking in many cases.
  • max_parallel_workers_per_gather = 0.

Mark pure, side-effect-free functions as PARALLEL SAFE so they don't block parallel plans.

CREATE FUNCTION norm_region(txt text)
RETURNS text
LANGUAGE sql
IMMUTABLE
PARALLEL SAFE
AS $fn$ SELECT lower(trim(txt)) $fn$;

Leader Participation

By default the leader process does double duty: it both gathers worker output and helps execute the parallel plan. This is controlled by parallel_leader_participation (default on).

For a query with N planned workers, effective parallelism is roughly N+1 when the leader participates. But if the leader gets bottlenecked gathering a flood of tuples, turning leader participation off can sometimes help workers run unimpeded — measure both ways.

SET parallel_leader_participation = off;

EXPLAIN (ANALYZE, COSTS OFF)
SELECT category_id, avg(price)
FROM products
GROUP BY category_id;

Tuning a Heavy Grouping Workload

Putting it together for a CPU-bound grouped join:

  • Raise max_parallel_workers_per_gather so the planner can split the scan (start with the number of spare cores).
  • Ensure max_parallel_workers and max_worker_processes are high enough that workers are actually launched, not throttled.
  • Raise work_mem until hash nodes show Batches: 1 (no spill).
  • Confirm the plan shows Parallel Hash Join + Partial/Finalize Aggregate, and that Workers Launched equals Workers Planned.

Always validate with EXPLAIN (ANALYZE, BUFFERS) on production-sized data — costs at small scale lie.

SET max_parallel_workers_per_gather = 6;
SET work_mem = '512MB';

EXPLAIN (ANALYZE, BUFFERS, COSTS OFF)
SELECT c.region, count(*) AS n, sum(o.amount) AS revenue
FROM orders o
JOIN customers c ON c.id = o.customer_id
GROUP BY c.region;

Quick Check

Test your understanding of parallel-aware joins.

Recap

You learned how PostgreSQL accelerates CPU-bound grouping workloads:

  • Parallel aggregation splits work into Partial Aggregate per worker and Finalize Aggregate at the leader, possible because aggregates are combinable.
  • Parallel Hash Join builds one shared hash table across workers, avoiding per-worker duplication of build cost and memory.
  • The Finalize / Gather / Partial sandwich and Parallel Hash nodes are how you recognize these plans in EXPLAIN.
  • Gate parallelism with max_parallel_workers_per_gather, max_parallel_workers, and table-size thresholds; size hash tables with work_mem to avoid spilling to disk.
  • Parallel-unsafe functions and data-modifying statements disable parallel plans; mark pure functions PARALLEL SAFE.

Always confirm Workers Launched matches Workers Planned and verify with EXPLAIN (ANALYZE, BUFFERS) on real data.

무료로 시작

AI 튜터와 함께 SQL을(를) 배우세요 — 무료

브라우저에서 실제 코드를 작성하고 실행하며, 24/7 AI 튜터로부터 즉각적인 도움을 받고, 웹이나 앱에서 중단한 부분부터 계속 학습하세요.

코스
22
레슨
88

자주 묻는 질문

“병렬 집계와 해시 조인” 강의는 무료인가요?

네 — “병렬 집계와 해시 조인” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 PostgreSQL Performance & Query Optimization 강의 전체를 잠금 해제할 수 있습니다. PostgreSQL Performance & Query Optimization 강의에는 총 4개의 강의가 포함되어 있습니다.

“병렬 집계와 해시 조인”에서 뭘 배우나요?

대규모 그룹화 작업에 부분 집계와 병렬 인식 조인을 활용하는 방법을 배웁니다. 브라우저에서 직접 실행하는 실습 코드로 PostgreSQL Performance & Query Optimization을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

PostgreSQL Performance & Query Optimization을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 PostgreSQL Performance & Query Optimization은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.

“병렬 집계와 해시 조인” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 PostgreSQL Performance & Query Optimization 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 PostgreSQL Performance & Query Optimization 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 플래너가 병렬 계획을 선택하는 경우
  2. 작업자 수와 수집 비용 조정
  3. 병렬 집계와 해시 조인
  4. 병렬 처리가 비활성화된 이유 진단
← PostgreSQL Performance & Query Optimization(으)로 돌아가기