Sorts and hashes do not have the same relationship with memory, and this parameter exists because PostgreSQL spent most of its history pretending they did.

hash_mem_multiplier is a floating-point value, its default is 2.0, its context is user, and it ranges from 1.0 to 1000. What it does is simple to state: hash-based operations are allowed to use work_mem multiplied by this value, while sort-based operations get plain work_mem. With the defaults, a sort may use 4MB before it spills to disk and a hash table may use 8MB. Hash tables here means the ones behind hash joins, hash aggregation, memoize nodes, and the hash-based handling of IN subqueries.

Why hashes get their own budget

The reason for treating them differently is an asymmetry that is worth knowning, because it explains not just this parameter but a good deal of how to think about work_mem generally.

An external sort barely cares how much memory it has. Give a sort enough to perform a single merge pass and it runs at very close to full speed; give it far more and it runs at very close to the same speed. The performance curve is nearly flat once you clear a low bar. Peter Geoghegan, who drove the changes discussed here, has made the point bluntly on the mailing lists: an external sort gains essentially nothing from extra memory until you hand it everything it could possibly use, and that flatness holds regardless of the data.

A hash operation is the opposite. A hash aggregate in which only a fraction of the groups fit in memory is dramatically slower than one where they all fit, because the overflow has to be partitioned and reprocessed. Memory availability moves hash performance a great deal, and it moves it unpredictably, because how much a hash table needs depends on cardinalities the planner only estimated.

Put those two facts together and a conclusion falls out. If you have a fixed amount of memory to hand to a query, it is worth far more given to the hash operations than to the sorts. hash_mem_multiplier is the mechanism that lets you do exactly that: feed the operations that benefit without also feeding the ones that do not.

Where it came from

The parameter arrived in PostgreSQL 13, and it arrived to clean up after another change in the same release.

Before 13, hash aggregation did not spill to disk. If the planner expected a hash aggregate to exceed work_mem, it avoided the plan; if it chose the plan and then underestimated, the hash table simply grew past work_mem and kept going, fast but with a latent risk of running the server out of memory. PostgreSQL 13 fixed the risk: Jeff Davis’s disk-based hash aggregation made a HashAggregate respect its memory limit and spill when it exceeded it, so the plan could be chosen safely even for large inputs.

Safer, but slower for a specific population of queries, the ones that in 12 had quietly run their hash aggregate in memory while over budget. In 13 those spilled to disk and got noticeably slower, with nothing in the query having changed. hash_mem_multiplier, added by Peter Geoghegan and backpatched to 13, was the release valve: raising it gives hash operations room above work_mem, so they spill less and the regression eases, without going all the way back to unbounded memory.

Why the default is 2.0

It did not start at 2.0. From 13 through 14 the default was 1.0, meaning hash and sort nodes got identical budgets, which preserved the historic behavior. PostgreSQL 15 raised it to 2.0, and the reasoning is the asymmetry above, plus a second motive that is easy to miss.

Geoghegan’s argument for the increase was not primarily about making hash nodes faster. It was about letting people avoid cranking work_mem itself. The trouble with raising work_mem to help hash operations is that it raises the ceiling for sort operations at the same time, and since sorts gain almost nothing from the extra memory, all you have really bought is a higher risk of an out-of-memory event from a sort using far more than could ever help it. Raising the hash multiplier instead directs the additional memory only where it does good. The move from 1.0 to 2.0 encodes a modest, safe amount of that, out of the box.

How to use it

This is one of the memory parameters you should reach for deliberately, and the situation that calls for it is specific: hash operations are spilling, and raising work_mem to stop them is causing memory pressure elsewhere.

You confirm the first half in EXPLAIN (ANALYZE). On a Hash node, Batches greater than 1 means the hash table did not fit and was partitioned to disk; on a HashAggregate, a reported disk usage means the same. If you see that, and if your work_mem is already as high as you dare push it given how many sorts and connections share the machine, hash_mem_multiplier is the more surgical lever. The documentation suggests values from 2.0 up to 8.0 or beyond once work_mem is already at 40MB or more.

The catch is the one that governs work_mem too, and it stacks on top of it. This is a per-node, per-connection allowance, not a global pool. A query with three hash operations can use three times work_mem times the multiplier, and a hundred such queries at once multiply that again. Doubling the multiplier doubles the hash contribution to that total. So the parameter is genuinely useful and also genuinely capable of helping you run out of memory if you treat it as free.

The summary

Leave hash_mem_multiplier at 2.0 for a general workload; it is a sensible default and the reasoning behind it is sound. Raise it, per session or per role first, when you can see hash operations spilling and you have a reason not to simply raise work_mem, which is usually that the sorts and the connection count make a global work_mem increase too risky. Treat it as what it is: the knob that acts on PostgreSQL’s hard-won observation that hash operations are hungry and sorts are not, and that the two should not be fed from the same spoon.