10 min read

PostgreSQL Parallel Query: Why More Workers Do Not Always Mean Faster SQL

Parallel query can speed large scans and aggregates, but it can also add coordination overhead. Use it when the plan has enough work per worker and the system has CPU to spare.

Parallel query feels like it should be simple. If one worker is slow, use four. If four are slow, use eight. Real production systems are less polite. Workers need coordination, memory, CPU, and enough useful work to justify their existence.

PostgreSQL parallelism is excellent for some large scans, joins, and aggregates. It is disappointing for tiny result sets, highly selective index lookups, overloaded CPUs, and plans where one non-parallel step dominates the runtime.

The question is not whether parallel query is enabled. The question is whether this specific plan has enough parallelizable work.

Read workers planned versus launched

EXPLAIN tells you how many workers PostgreSQL planned and how many it actually launched. If planned workers are not launched, the system may be constrained by worker availability or settings.

EXPLAIN (ANALYZE, BUFFERS)
SELECT tenant_id, count(*), sum(total_cents)
FROM orders
WHERE created_at >= now() - interval '90 days'
GROUP BY tenant_id;

Gather is not free

A Gather or Gather Merge node has to combine worker output. If each worker produces a huge stream that then needs sorting or aggregation, the coordination point can become visible in runtime.

Parallelism competes with the application

A report using four workers is not isolated from the rest of the database. Those workers consume CPU and memory that foreground requests may need. On a busy OLTP system, turning up parallelism can move pain from one query to everyone else.

SHOW max_parallel_workers;
SHOW max_parallel_workers_per_gather;
SHOW max_worker_processes;

Use session-level experiments

Test parallel settings in a session before changing cluster defaults. You want to know whether the query improves and whether the system has enough headroom for the new worker shape.

SET max_parallel_workers_per_gather = 4;
EXPLAIN (ANALYZE, BUFFERS)
SELECT date_trunc('day', created_at), count(*)
FROM orders
GROUP BY 1;

When parallel query is a good fit

  • Large table scans where many pages must be read anyway.
  • Aggregations over broad time windows.
  • Analytical queries on systems with spare CPU.
  • Maintenance or reporting workloads separated from hot OLTP traffic.

When it disappoints

  • Highly selective index lookups.
  • Queries dominated by lock waits or client waits.
  • Systems already CPU saturated.
  • Plans with a serial bottleneck after worker output is gathered.

The production habit

The teams that get good at PostgreSQL performance do not chase every knob. They turn a vague complaint into a named failure mode, collect one clean measurement, make one change, and then compare the next measurement against the first. That rhythm is slower than guessing for the first hour and much faster by the end of the incident.