Re: Proposal: "query_work_mem" GUC, to distribute working memory to the query's individual operators

From: Manu <manuelreyesbravo(at)gmail(dot)com>
To: Jeff Davis <pgsql(at)j-davis(dot)com>
Cc: James Hunter <james(dot)hunter(dot)pg(at)gmail(dot)com>, Álvaro Herrera <alvherre(at)kurilemu(dot)de>, pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: Re: Proposal: "query_work_mem" GUC, to distribute working memory to the query's individual operators
Date: 2026-10-03 22:46:05
Message-ID: 179106756531.274884.9866634099979671297@gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi Jeff, James,

> For a first step, I think it would be useful to create a new field for
> each Plan node, and use that to enforce the execution-time memory
> limit.

The thread has been quiet for a while, so I tried that first step.
Patch attached, building on James's work.

Each Plan node gets a workmem field. Zero, which is what the planner
leaves in it, means work_mem, so nothing changes unless a planner hook
sets it. The executor uses the node's value, and so does the code that
runs for a node: materialize-mode set-returning functions and
ordered-set aggregates. EXPLAIN VERBOSE shows it when it is set. The
test_plan_workmem module sets it from a hook and checks every kind of
node that uses working memory, with the limit below and above work_mem.

I first tried copying work_mem into the field at plan time. Then SET
LOCAL work_mem inside a PL/pgSQL function no longer reached a query
whose plan was already cached: it wrote a 13 MB temporary file that
master does not. Replanning cached plans when work_mem changes fixes
that, but cost 47% of the tps in a pgbench run that changes work_mem in
every transaction before a prepared 3-way join. With zero meaning
work_mem, behavior does not change and I could not measure a cost (all
three runs within noise, 95% CI about +/-2%).

Parallel workers get the limit with the plan. Parallel Hash uses it the
way it uses work_mem today, including the combined budget of all
participants.

Known limits:
- One limit per node: an Agg's hash table and its sorts share it.
- Limits set by a hook stay with cached plans.
- The planner still costs with work_mem.
- Not covered: GIN partial-match bitmaps, array_sort(), and the hold
store of WITH HOLD cursors, which do not run for a plan node.
- ExecChooseHashTableSize() and hash_agg_set_limits() take the limit
as a new argument.

This is not a cap on a backend's memory, only the hook point. As a
check that it is enough for a per-query budget, a small extension that
splits a budget among the nodes kept a query that used 13 MB at
work_mem = 8MB within a 4 MB budget.

Regards,
Manu

Attachment Content-Type Size
v1-0001-Add-a-per-node-working-memory-limit-to-Plan-nodes.patch text/x-patch 56.0 KB

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Andres Freund 2026-10-03 23:07:02 Re: Coverage with make coverage-html is broken on latest Debian using lcov v2
Previous Message Tom Lane 2026-10-03 22:26:45 Re: Coverage with make coverage-html is broken on latest Debian using lcov v2