Skip to content
dbexplore

Free tool

How much memory can these settings actually claim?

work_mem is per operation, and one connection can hold many of them at once while most connections hold none. This multiplies it out, adds the autovacuum workers, and checks what the planner has been promised.

Run it on your numbers

What the machine has, not what the container is limited to. If they differ, the container limit is the one that kills you.

Defaulted to 1.0 through PostgreSQL 14 and to 2.0 from 15. Nobody has to change it for the exposure to double.

Each one may hold its own allocation at the same time. Count the Hash, HashAggregate and Sort nodes in the worst plan you actually run.

Every worker under a Gather gets its own allocation for each node, and so does the leader.

Not max_connections. The number of genuinely heavy queries you would expect at the same moment, which is what a report hour or a retry storm decides.

-1 means each autovacuum worker uses maintenance_work_mem instead, which is the default and is usually a surprise.

Per session, allocated on first use of a temporary table and never given back for the life of the session.

A promise to the planner, not an allocation. This tool checks whether the promise can be kept.

The kernel, the pooler, the exporter, the backup agent, the log shipper. Zero here is a claim, not a default.

Per operation is doing a lot of work in that sentence

The documentation for work_mem says it is the memory allowed for each sort or hash operation before the work spills to disk. Very few people then multiply it by the number of such operations one plan runs at the same time, by the parallel workers that each get their own copy, and by the number of those plans that can be in flight at once. A query with four hash joins under two parallel workers holds twelve allocations, not one.

The figure normally quoted instead is work_mem times max_connections, and it is wrong in both directions at once. Far too large, because most of your connections are idle and hold none of it; far too small, because one connection running the plan above holds many multiples. Neither error cancels the other, and what you are left with is a number that describes nothing that happens.

What the total above is actually counting

Four things, all of which can be claimed at the same moment. The shared memory, which is taken at startup and never given back. The backends, at the multiplied-out worst case for as many heavy queries as you said could overlap. The autovacuum workers, each holding maintenance memory, which is the part people forget because it happens on its own schedule rather than in response to traffic. And whatever else lives on the box, never zero once you count the pooler and the exporter.

Then there is the part that allocates nothing at all. effective_cache_size is a promise to the planner about how much of a table it expects to find in cache, and the planner costs index scans against that promise. Take the four things above out of RAM and what remains is the page cache that can physically exist under load. When the promise is bigger than the remainder, plans that were chosen on a quiet server are being chosen for a cache that will not be there at the busy hour.

The setting that doubled without anyone touching it

hash_mem_multiplier defaulted to 1.0 through PostgreSQL 14 and defaults to 2.0 from 15. It is a user setting, so nothing about it needs a restart and nothing about it appears in a diff of postgresql.conf. A server that came through a major upgrade with an identical configuration file can therefore expose twice the hash memory it did the week before, and the first sign is a backend being killed during a report that had always been fine.

Flip the version selector across that boundary and the exposure figure moves while every input stays where it was. If you are planning the upgrade, the cheap fix is to halve work_mem in the same change, which keeps the worst case where it is and leaves the plan shapes alone. Sorts are unaffected either way: the multiplier applies to hash operations, and this tool assumes every node is one because that is what a worst case means.

Nobody wants to be the one who ran the query too late.

DBExplore watches the numbers behind these checks on every cluster it is pointed at, and asks before it changes anything. We onboard design partners in small batches.