Skip to content

CONCEPT Cited by 1 source

work_mem multiplication

Definition

work_mem multiplication is the geometric amplification that turns a modest per-allocation cap (e.g. 4 MB) into potentially gigabytes of transient RSS under load. Postgres's work_mem is not a per-query or per-connection cap — it is a per-sort-node, per-hash-node, per-query, per-backend cap, and the effective multiplier is:

max_transient_work_mem_RSS
  = work_mem × sort_and_hash_nodes_per_query × active_backends

A 4 MB work_mem with 100 active connections running queries with 3 sort/hash nodes each ⇒ up to 1.2 GB of transient RSS — a factor of 300× the nominal setting.

(Source: sources/2026-04-21-planetscale-high-memory-usage-in-postgres-is-good-actually.)

Simeon Griggs's framing

"Tuning work_mem might seem like an obvious lever — decrease it to reduce RSS, or increase it to prevent operations from spilling to disk. But the allocation is per-sort/hash-node, per-query, per-backend. A single complex query can allocate work_mem multiple times, and that multiplies across every active connection. Setting it too low forces more disk I/O; setting it too high globally can cause total memory usage to spike unpredictably under load. Neither direction is a safe default change without first understanding your workload's concurrency and query complexity."

Why this is structurally unsafe

The two failure modes of work_mem tuning are symmetric:

  • Set too low: Queries that would fit in memory at the default get pushed to on-disk sort / hash-join, multiplying I/O cost and latency. Operator sees elevated disk I/O and slow queries; CPU and memory look fine.
  • Set too high: Under peak concurrency, the multiplier unpredictably saturates RAM. Operator sees OOM-kill events. Average utilisation looked healthy because the multiplication only manifests under correlated-workload bursts.

The operator does not generally know the multiplier at configuration time because it depends on runtime concurrency + query shape.

Contrast with MySQL-style per-session caps

MySQL's sort_buffer_size is per-session, not per-node within a query. A single MySQL session with 10 sort operations allocates sort_buffer_size × 1 (reused), not × 10. Postgres's per-node semantics makes the multiplier larger for comparable queries and makes configuration correspondingly harder.

Operational discipline

Griggs's implicit prescription:

  1. Don't tune work_mem as a default RSS-reduction lever. Connection pooling (reducing active_backends) is safer.
  2. Characterise workload concurrency and query complexity first. If you don't know the multiplier, you can't set the limit.
  3. Consider per-role SET work_mem for known-heavy workloads (analytics, ETL) rather than global tuning.

Seen in

Last updated · 550 distilled / 1,221 read