Since PostgreSQL 15 the server answers that itself: shared_memory_size_in_huge_pages. And the answer is dominated by one setting — as a rule of thumb, shared memory is shared_buffers plus about 2 % of overhead for describing it, plus a per-backend tail. Everything else is a footnote until you make it extreme, which is exactly why the sizing is boring and the failure modes are not. This page computes that number for a configuration you do not run yet, and answers the question the docs skip — how much margin to put into vm.nr_hugepages, and why the honest answer is "not much".
The same arithmetic, reimplemented from the source: the per-buffer structures, the lock and predicate-lock hash tables (through dynahash's real hash_estimate_size()), the SLRU caches with their auto-tuning, WAL buffers, the per-backend arrays. The server does the same sum in CalculateShmemSize() and then reports size_b / huge_page_size + 1 — integer division, with one page added back for the remainder. Useful when the configuration you want to size for is not the one currently running — a bigger shared_buffers, or a server you have not built yet.
shared_buffers plus roughly 2 % of overhead, and everything else shifts it by a handful of pages. Which is the whole argument against padding vm.nr_hugepages by 10 % or more "just in case" — that is not caution, it is a few gigabytes taken away from the page cache to cover a number the server already knows exactly. Take the real value from the running instance with SHOW shared_memory_size_in_huge_pages, add a percent or two, and recompute when the configuration changes.—
huge_pages applies to. Memory allocated later per backend (work_mem, maintenance_work_mem, catalog and plan caches) and the DSM segments a parallel query creates on the fly are not in it, and they do not use huge pages. The one exception is min_dynamic_shared_memory: that area is carved out of the main segment, so it is both counted here and backed by huge pages.Ten configurations run through the server itself on PostgreSQL 17, sweeping the full range the sliders allow — shared_buffers from 8 GB to 1 TB, max_connections from 100 to 10 000, max_locks_per_transaction from 64 to 256 — then the same input put through the calculator. Every other setting stays at its default. The command is the one from the top of the page, and it needs no memory: it computes the number and exits.
What is compared is the requirement — the big number in the calculator, shared_memory_size_in_huge_pages. The margin slider is deliberately left out of it: postgres -C reports what the server needs, while the vm.nr_hugepages line is that number plus whatever slack you choose to add on top.
$ postgres -D /tmp/d -C shared_memory_size_in_huge_pages \
-c shared_buffers=1024GB -c max_connections=10000 -c max_locks_per_transaction=4096 …
| Configuration | calculator | postgres -C | Δ | Δ memory |
|---|---|---|---|---|
| shared_buffers 8 GB · max_connections 100 · locks 64 | 4 217 | 4 218 | −0.02 % | −2 MB |
| shared_buffers 8 GB · max_connections 10 000 · locks 256 | 4 825 | 4 826 | −0.02 % | −2 MB |
| shared_buffers 32 GB · max_connections 100 · locks 64 | 16 784 | 16 788 | −0.02 % | −8 MB |
| shared_buffers 32 GB · max_connections 1 000 · locks 128 | 16 817 | 16 821 | −0.02 % | −8 MB |
| shared_buffers 32 GB · max_connections 10 000 · locks 256 | 17 392 | 17 396 | −0.02 % | −8 MB |
| shared_buffers 128 GB · max_connections 500 · locks 64 | 67 063 | 66 975 | +0.13 % | +176 MB |
| shared_buffers 128 GB · max_connections 5 000 · locks 256 | 67 356 | 67 268 | +0.13 % | +176 MB |
| shared_buffers 512 GB · max_connections 1 000 · locks 128 | 268 159 | 267 352 | +0.30 % | +1 614 MB |
| shared_buffers 1 TB · max_connections 100 · locks 64 | 536 224 | 534 457 | +0.33 % | +3 534 MB |
| shared_buffers 1 TB · max_connections 10 000 · locks 256 | 536 832 | 535 065 | +0.33 % | +3 534 MB |