
The Process Model
PostgreSQL does not use a thread-per-connection model. When the server starts, a single supervisor process called the postmaster listens for incoming connections. Each time a client connects, the postmaster forks a dedicated backend process to serve that session for its entire lifetime.
This differs from database engines that multiplex many client sessions onto a pool of threads inside one process. The process model gives PostgreSQL strong isolation: a bug or crash in one backend is less likely to corrupt another session's in-memory state directly. The trade-off is overhead. Each backend carries the memory and scheduling cost of a full OS process, so a server with thousands of idle connections can run out of memory or context-switch heavily before it runs out of actual work.
Each backend also runs the full query pipeline for its own session: parsing the SQL text, rewriting it against any applicable rules, planning it into an execution strategy, and executing that plan. There is no shared query engine thread; planning and execution cost scales with the number of active backends, not with a single central bottleneck.
Shared Memory
All backend processes attach to a shared memory segment set up by the postmaster at startup. The largest piece of this is the shared buffer cache (shared_buffers), which holds recently used data and index pages so repeated reads do not hit disk.
Shared memory also holds the WAL buffers, the lock manager's tables, and bookkeeping structures that track transaction status. This is distinct from work_mem, which is allocated per sort or hash operation inside a single backend and is not shared. A common sizing mistake is treating shared_buffers as the whole memory budget; the operating system's own page cache also holds copies of the same data files, and PostgreSQL relies on that second layer of caching rather than trying to replace it.
Each backend also keeps its own local caches of system catalog entries, such as table and type definitions it has already looked up, so repeated catalog lookups do not require shared access. These caches are private to the backend and are discarded when the connection closes.
Write-Ahead Logging (WAL)
Before any change to a data page is considered durable, PostgreSQL writes a record of that change to the write-ahead log. The log entry is flushed to disk first; the actual data page can be written later, during a checkpoint, by a background process.
This ordering is what makes crash recovery possible. If the server stops unexpectedly, PostgreSQL replays the WAL from the last checkpoint to reconstruct any changes that were committed but not yet written to the data files. The same WAL stream is also the basis for physical replication, since a replica can apply the same log records to stay in sync. A separate decoding process can read that same WAL stream and transform it into row-level logical changes instead of raw page changes; that mechanism is what logical replication is built on, as distinct from physical, block-for-block replication.
MVCC and Row Versions
PostgreSQL handles concurrency with multiversion concurrency control (MVCC) instead of blocking readers with locks. Every row carries hidden metadata recording which transaction created it and, if applicable, which transaction deleted or replaced it. A query sees a consistent snapshot of the data based on these markers, so readers never wait on writers and writers never wait on readers.
The cost of this design is that updates and deletes do not remove old row versions immediately; they leave dead tuples behind. Those tuples are reclaimed later by vacuum, which is why vacuum is not an optional maintenance chore but a core part of the architecture.
Background Processes
Beyond the postmaster and per-connection backends, PostgreSQL runs several permanent background processes inside the same instance:
- The checkpointer periodically flushes dirty pages from shared buffers to disk and marks a recovery starting point in the WAL.
- The background writer trickles dirty pages to disk between checkpoints so checkpoints have less work to do.
- The WAL writer flushes WAL buffers to disk on a regular cycle.
- Autovacuum workers reclaim dead tuples and update planner statistics automatically.
These processes exist specifically to keep the cost of durability and cleanup spread out, rather than concentrated into large pauses. When streaming replication is configured, the primary also runs a WAL sender process for each connected replica, and each replica runs a WAL receiver process to apply incoming records; unlike the four processes above, these only appear while replication is active.
Seeing the Architecture in Practice
You can observe several of these pieces directly from SQL. The following queries show per-connection backends, memory configuration, and WAL position:
-- Each connection maps to one OS-level backend process
SELECT pid, usename, state, backend_start
FROM pg_stat_activity;
-- Inspect how shared and per-operation memory are configured
SHOW shared_buffers;
SHOW work_mem;
-- Confirm WAL is active and check the current write position
SELECT pg_current_wal_lsn();On the server's host, running a process listing shows the same thing from the operating system's side: one postmaster and one additional process per open connection, plus the checkpointer, background writer, WAL writer, and autovacuum processes running alongside them.
Trade-offs and Common Mistakes
The process-per-connection model is the source of most architecture-related operational problems. Opening thousands of direct client connections, especially short-lived ones from a web application, wastes memory and CPU on process overhead rather than queries. The standard fix is connection pooling, either in the application layer or with an external pooler placed in front of PostgreSQL.
Other frequent mistakes include setting shared_buffers too high and starving the OS page cache, disabling or under-tuning autovacuum and letting table bloat grow unchecked, and assuming a long-running read transaction is harmless when it actually blocks vacuum from reclaiming dead tuples for the duration of that transaction. None of these are bugs in PostgreSQL; they are consequences of how the process model, shared memory, and MVCC interact, and tuning means working with that interaction rather than against it.
Common questions
Is PostgreSQL multi-threaded or multi-process?
PostgreSQL is multi-process. The postmaster forks a separate backend process for each client connection rather than running connections as threads inside one process. This gives strong isolation between sessions but makes each connection relatively expensive.
Why does PostgreSQL need connection pooling?
Because every connection is a full operating system process, a large number of idle or short-lived connections consumes memory and CPU scheduling time without doing useful work. Pooling, either in the application or through an external pooler, reduces the number of live backend processes the server has to manage.
What happens if a backend process crashes?
The postmaster detects the failure and, because shared memory may now be in an inconsistent state, terminates every other backend process, reinitializes shared memory, and runs crash recovery before accepting new connections again. All active sessions are dropped as a result, including ones unrelated to the original crash.
What is WAL and why does it matter for architecture?
WAL, the write-ahead log, is where every change is recorded before it is applied to the actual data files. It is what allows PostgreSQL to recover cleanly after a crash and is also the mechanism both physical and logical replication rely on to keep replicas in sync.
Does MVCC mean PostgreSQL never needs vacuum?
No. MVCC avoids read locks by keeping old row versions around instead of overwriting them immediately, but those old versions still need to be cleaned up. Vacuum, usually run automatically by autovacuum, reclaims that space and keeps table statistics current.
Sources
About this page
Last reviewed October 30, 2026 by the Nine Bits editors. Read our editorial policy or report an error.