Hash
Appears in EXPLAIN asHash
Builds the in-memory hash table used by a Hash Join.
What it is
A Hash node materializes its child into a hash table in memory; its parent Hash Join then probes that table. The node reports buckets, batches, and peak memory usage.
When the planner picks it
Always paired with a Hash Join — it represents the build side.
Is it good or bad?
Fine when it stays in memory. 'Batches > 1' or peak memory near work_mem signals a spill; that's the lever to tune (work_mem / hash_mem_multiplier).
In depth
Always paired with a Hash Join
A bare Hash node never appears on its own — it's the build side of a Hash Join. The executor reads the Hash's child entirely, drops each row into a hash
table keyed on the join column, and then the Hash Join scans the other input
and probes that table for matches.
That asymmetry is everything: the planner picks the smaller estimated input as the Hash's child, because the whole build side has to fit in memory (or spill to disk in batches).
What the node tells you
Hash (cost=… rows=…) (actual rows=580000 loops=1)
Buckets: 65536 Batches: 32 Memory Usage: 4096kB
- Buckets — size of the hash array. Larger reduces collisions.
- Batches > 1 — the build side didn't fit in
work_mem, so PostgreSQL partitioned the join into batches and processed it in passes (spill to disk). A single batch is the goal. - Memory Usage — peak in-memory size of the table.
Batches: 32 with Memory Usage: 4096kB means work_mem was the wall: raising
it (or hash_mem_multiplier) typically restores a single-batch, fast join.
Parallel Hash
Under a Gather, the Hash node can become Parallel Hash: workers cooperatively
build one shared hash table, then probe in parallel. The same memory math
applies — the shared table still has to fit in the combined memory budget, so
work_mem matters per worker.
What the analyzer flags here
- Hash build spilled — see Hash Join
Settings that influence it
work_memhash_mem_multiplier