Memory
Memory architecture
PostgreSQL memory comes in two kinds: one shared area allocated at start-up, and private memory that every backend process allocates as it works. Size the machine for both, and leave room for the operating system cache.
In plain words
PostgreSQL uses memory in two ways. Shared memory is one big block reserved when the server starts and used by every process: mostly the page cache (shared_buffers), plus smaller areas for the WAL, locks and bookkeeping. Its size is fixed until the next restart.
Private memory belongs to one backend process. Each connection needs a few megabytes just to exist, and a query can grab much more while it sorts or builds hash tables: up to work_mem for each such step (hash tables twice that by default). Maintenance jobs like VACUUM and CREATE INDEX use maintenance_work_mem.
Whatever is left over is not wasted: the operating system uses it to cache files, and PostgreSQL benefits from that cache too. Trouble starts when the total asked for is more than the machine has: Linux then kills a process, and PostgreSQL restarts all sessions. Use the sliders to see where the memory goes.
For DBAs and operators
What lives in shared memory
| Area | Sized by |
|---|---|
| Buffer pool + descriptors | shared_buffers (+ about 2% for descriptors, the buffer mapping table, I/O condition variables, checkpoint sort array) |
| WAL buffers | wal_buffers, default 1/32 of shared_buffers, capped at one WAL segment (16 MB by default) |
| Lock table, proc array, AIO handles | max_connections, max_locks_per_transaction, max_prepared_transactions, worker slots |
| SLRU caches | transaction_buffers and friends (PostgreSQL 17+) |
| Cumulative statistics | Fixed part plus dynamic shared memory per object (since PostgreSQL 15) |
SHOW shared_memory_size; -- total, computed at start
SHOW shared_memory_size_in_huge_pages; -- how many to reserve
SHOW huge_pages_status; -- on / off (PostgreSQL 17+)
SELECT name, pg_size_pretty(allocated_size)
FROM pg_shmem_allocations ORDER BY allocated_size DESC LIMIT 10;You can ask a stopped cluster what it would need: postgres -D $PGDATA -C shared_memory_size_in_huge_pages. On this test server 128 MB of shared_buffers gives shared_memory_size = 150 MB; with 100 extra connections it grows by about 7 MB.
Private memory
Each backend allocates through memory contexts, a tree of arenas freed in bulk (per query, per transaction, per cache). An idle backend uses a few MB; catalog and relation caches grow with the number of tables and partitions touched, and generally stay allocated until the session ends. Inspect it:
-- your own session
SELECT name, pg_size_pretty(total_bytes) FROM pg_backend_memory_contexts
ORDER BY total_bytes DESC LIMIT 10;
-- another backend: dumps its context tree into the server log
SELECT pg_log_backend_memory_contexts(4521);Huge pages and page tables
With 4 kB pages, every backend that touches shared memory builds its own page-table entries: about 2 MB per GB of shared memory, per backend. With 32 GB of shared_buffers and 500 busy connections that is up to about 31 GB of kernel memory. Huge pages (2 MB) make this negligible and speed up TLB lookups. The default huge_pages = try silently falls back to normal pages if the kernel has none reserved, so check huge_pages_status and set vm.nr_hugepages from shared_memory_size_in_huge_pages.
When memory runs out
ERROR: out of memoryDETAIL: Failed on request of size 524288 in memory context "ExecutorState".LOG: client backend (PID 4521) was terminated by signal 9: KilledThe first is PostgreSQL failing one allocation (only that query errors). The second is the Linux OOM killer, which forces a restart of all sessions. On dedicated servers use vm.overcommit_memory = 2 with a sensible vm.overcommit_ratio, so allocations fail cleanly instead.
Rules of thumb: shared_buffers about 25% of RAM, effective_cache_size about 50–75%, and work_mem chosen so that active sessions × nodes × work_mem fits in what remains. A pooler that caps active sessions makes the last term predictable.
For developers: inside the source
Where it lives
ipci.c:CalculateShmemSize()adds up every subsystem's*ShmemSize()estimate plustotal_addin_requestfrom extensions;CreateSharedMemoryAndSemaphores()creates the segment andCreateOrAttachShmemStructs()runs each subsystem's*ShmemInit().shmem.c: the bump allocatorShmemAlloc()andShmemInitStruct()/ShmemInitHash(), which register named areas ("Buffer Blocks", "Buffer Descriptors", …) in the shmem index.pg_get_shmem_allocations()(behindpg_shmem_allocations) and, new in 18,pg_get_shmem_allocations_numa()read that index.sysv_shmem.c:PGSharedMemoryCreate(). The real block is an anonymousmmap(CreateAnonymousSegment(), withMAP_HUGETLBfor huge pages); only a very small System V segment is kept as a data-directory interlock.dsm.c,dsa.c,dshash.c: dynamic shared memory created after start-up (parallel query, cumulative statistics).utils/mmgr/READMEandmcxt.c: private memory contexts; allocators inaset.c,generation.c,slab.candbump.c(17+).
Start-up order
In PostmasterMain(): process_shmem_requests() calls every library's shmem_request_hook, then InitializeShmemGUCs() publishes shared_memory_size and shared_memory_size_in_huge_pages, then CreateSharedMemoryAndSemaphores() allocates and initialises everything and finally calls shmem_startup_hook. Children inherit the mapping through fork(); EXEC_BACKEND builds (Windows) re-attach with AttachSharedMemoryStructs(). The size is fixed: nothing can be added to the main segment later.
Private memory: contexts
palloc() allocates in CurrentMemoryContext; MemoryContextSwitchTo() changes it, and MemoryContextReset() / MemoryContextDelete() free a whole subtree at once. Long-lived roots are TopMemoryContext, CacheMemoryContext (relcache/catcache), MessageContext (one client message) and TopTransactionContext; the executor's per-query context is named "ExecutorState" (CreateExecutorState()). The names shown by pg_backend_memory_contexts are the names passed to AllocSetContextCreate(). pg_log_backend_memory_contexts() signals the target, which writes its context tree to the server log from ProcessLogMemoryContextInterrupt(); in a debugger, MemoryContextStats(TopMemoryContext) prints the same to stderr.
Extension hooks
void _PG_init(void)
{
prev_shmem_request_hook = shmem_request_hook;
shmem_request_hook = my_shmem_request; /* RequestAddinShmemSpace(),
RequestNamedLWLockTranche() */
prev_shmem_startup_hook = shmem_startup_hook;
shmem_startup_hook = my_shmem_startup; /* ShmemInitStruct() under
AddinShmemInitLock */
}This only works for libraries in shared_preload_libraries; pg_stat_statements is the reference user. A library loaded later can use GetNamedDSMSegment() from dsm_registry.c (PostgreSQL 17+). Test modules worth reading: src/test/modules/test_dsm_registry, test_shm_mq, test_dsa.
Debugging memory
Assert-enabled builds (--enable-cassert) turn on MEMORY_CONTEXT_CHECKING and CLOBBER_FREED_MEMORY (see pg_config_manual.h), which catch writes past a chunk and use-after-free. Defining USE_VALGRIND adds Valgrind client requests to the allocators. postgres -D … -C shared_memory_size prints the computed size without starting the server.
Parameters that shape this
shared_buffers huge_pages huge_page_size shared_memory_type wal_buffers max_connections max_locks_per_transaction work_mem hash_mem_multiplier maintenance_work_mem autovacuum_work_mem temp_buffers effective_cache_size shared_memory_size shared_memory_size_in_huge_pages
Related topics
Shared buffers Local (per-backend) memory SLRU caches Process model Cumulative statistics Locking and deadlocks