Question: Plans and the planner
Why is my query not using parallel workers?
Answered in the first paragraph. Last updated .
Two entirely different failures wearing the same symptom. Either the planner never produced a parallel plan, which is a property of the query, or it produced one and no workers were available when it ran, which is a property of the server at that moment. The plan tells you which: a plan with no gather node is the first, and a gather node reporting fewer workers launched than planned is the second.
What prevents a parallel plan existing
The documented list is short and worth knowing by heart. A query that writes rows or locks them is excluded, though the reading half of creating a table from a select can still parallelise. Anything whose execution can be suspended is excluded, which means cursors and the loop form in stored procedures. A function marked unsafe for parallel execution poisons the whole plan, and user-defined functions are unsafe by default unless somebody said otherwise, which is the single most common cause in an application with its own function library.
Then there are the thresholds. The per-gather worker limit set to zero disables it outright. A table smaller than the minimum scan size is not considered worth splitting, which is why the behaviour appears in production and not in a test database.
What prevents the workers appearing
The worker pool is cluster-wide and shared. Parallel query competes with parallel maintenance, logical replication workers and any extension that runs background processes, all against one ceiling. When it is exhausted the plan does not fail: the leader process simply runs the whole thing itself, and the query is two or four times slower with no error anywhere.
That silence is why PostgreSQL 18 added counters for the parallel workers each database asked for and the ones it got. The gap between the two numbers is the cheapest capacity signal in the release, and before it existed this problem was effectively invisible.
One more runtime case: a client that asks for rows in batches rather than taking the whole result triggers the same fallback, so the same query is parallel from one driver and serial from another.
To see which case you have, run the plan with execution statistics and read the worker line, remembering that the query really does run when you do. If the plan is serial because the planner preferred an index, that is a different and usually correct decision, covered in sequential scan versus index scan. Query plan regression covers noticing when a plan silently stops being parallel between releases.