max_parallel_workers

Caps total parallel query workers. Default 8. Setting to 0 disables parallelism — analytics queries silently run 3× slower without warning.

MEDIUM RISK
Safe first move

8 (default). Set to number of physical CPUs for analytics boxes.

Main risk

workers=0: GROUP BY on 2M rows takes 524ms. workers=2: 177ms — 3× slower when parallelism accidentally disabled (lab-captured).

Tier 1 — Lab-Proven — Measured behavior and safety boundaries are available below.

What We Observed

box

(PG 17.10, Intel Core Ultra 7 268V, 8 vCPU), mpw_test 2M rows, GROUP BY grp + ORDER BY sum

workers 0 sequential

exec time ms reps
  • 507.092
  • 524.378
  • 532.019
median ms

524.4

plan

Seq Scan (no parallelism)

workers 2 parallel

exec time ms reps
  • 195.927
  • 167.099
  • 177.311
median ms

177.3

speedup

2.96×

formula curve

=== max_parallel_workers Formula Curve === Table: mpw_test (2M rows), query: GROUP BY grp + ORDER BY sum

workers    plan_type                 exec_ms             
0          Seq Scan+                 501.087             
1          Gather+Parallel+          237.624             
2          Gather+Parallel+          185.252             
4          Gather+Parallel+          189.166

note

Diminishing returns visible: workers=2 → 185ms, workers=4 → 189ms (similar). More workers help until CPU-bound.

Reproduced Danger

Sizing / Safe Range

Workload Guidance

Unlock Incident Pro

The free sections tell you what to do. Pro is the measured proof — the exact failure signature, safe-ceiling formula, and per-workload defaults, each captured on real PG 14–18 runs.

  • Reproduced Danger
  • Sizing / Safe Range
  • Workload Guidance
Unlock Incident Pro

max_parallel_workers is the SERVER-WIDE POOL of parallel worker processes — the maximum number that can be active at one time across ALL sessions combined, for both parallel queries and parallel maintenance (CREATE INDEX, VACUUM). It sits in the middle of a three-level hierarchy: max_parallel_workers_per_gather caps how many workers ONE query's Gather node may use; max_parallel_workers caps how many can be busy across the WHOLE cluster at once; and max_worker_processes (set at the postmaster, RESTART-only) is the hard ceiling of background-worker slots that the pool itself draws from. So the binding relationship is per_gather ≤ max_parallel_workers ≤ max_worker_processes. When a query (or an index build) wants workers, the planner PLANS some number, then at execution PostgreSQL only LAUNCHES as many as the pool has free — if the pool is exhausted it silently runs with fewer, or with zero (fully serial), and there is no error. Default is 8.

name

max_parallel_workers

setting default

8

unit

(none — a count of concurrently active parallel workers, server-wide)

vartype

integer

category

Resource Usage / Worker Processes

context

user

min val

0

max val

1024

enumvals

(n/a — integer)

boot val

8

reset val

8

pending restart

false

short desc

Sets the maximum number of parallel workers that can be active at one time.

ground truth note

Captured literally from pg_settings on PG 14-18 (-18) on 2026-06-19. setting=8, boot_val=8, reset_val=8 on EVERY version (default_changed=false, boot_fallback_changed=false). context=user (any session/role/database can set it, no restart, no superuser). vartype=integer, min_val=0, max_val=1024, unit empty. category='Resource Usage / Worker Processes', short_desc='Sets the maximum number of parallel workers that can be active at one time.' Default and bounds are identical across 14, 15, 16, 17, 18.

Common Pitfalls

  • Setting max_parallel_workers=0 to 'limit' parallelism — it is a server-wide KILL SWITCH that disables every parallel query AND parallel maintenance, and the plan still shows a Gather, so it looks like parallelism is on.
  • Raising max_parallel_workers above max_worker_processes and expecting more workers — it is inert; the real ceiling is max_worker_processes, which needs a RESTART.
  • Forgetting that parallel CREATE INDEX and parallel VACUUM draw from this same pool — a low pool silently slows maintenance even with a high max_parallel_maintenance_workers.
  • Assuming Workers Planned = Workers Launched — under pool pressure Launched can be far lower (down to 0 = serial) with no error; always check Workers Launched in EXPLAIN ANALYZE, not just the plan shape.
  • Sizing the pool for one query while many run — concurrent parallel queries share the pool; peak_concurrency × workers_each above the pool means some queries silently run serial (unfair, first-come rationing).
  • Oversubscribing: setting the pool well above core count so launched workers + leaders exceed CPUs, causing context-switch thrash and regressions (the lab's pool=8 single query was slower than pool=4).

Evidence and References

  • PostgreSQL docs: Runtime Config — Resource Consumption → Asynchronous Behavior (max_parallel_workers, max_parallel_workers_per_gather, max_worker_processes, max_parallel_maintenance_workers).
  • PostgreSQL docs: Parallel Query — how Gather launches workers, leader participation, and Workers Planned vs Workers Launched.
  • PostgreSQL docs: Parallel Query → When Can Parallel Query Be Used (per_gather, pool, and worker-process limits).
  • PostgreSQL docs: CREATE INDEX (parallel index build, limited by max_parallel_maintenance_workers and max_parallel_workers).
  • PostgreSQL docs: ALTER TABLE . SET (parallel_workers) per-table reloption.
  • Lab capture 2026-06-19: , 20M-row par table; EXPLAIN ANALYZE Workers Planned/Launched, 4-way concurrency, parallel CREATE INDEX timing; pg_settings ground truth PG 14-18 (-18).

Technical Reference

Category
Resource Usage / Worker Processes
Type
integer ((none; a count — the maximum number of parallel worker processes that can be ACTIVE at one time across the whole server, drawn from the max_worker_processes pool))
Blast radius
query
Severity
Moderate
Context
user
Versions
14, 15, 16, 17, 18
Reviewed
2026-06-19

Severity scale: 1 Minimal · 2 Low · 3 Moderate · 4 High · 5 Critical.

Keep going

Related & next steps