neondatabase/neon
Separation of compute and storage
The single architectural choice that defines Neon: a Postgres compute node has no local persistent state. Everything that vanilla Postgres would put on disk is instead either:
- Sent to the safekeepers (writes, via WAL).
- Fetched from the pageserver (reads, via
GetPage@LSN).
This page is about why and what falls out of it.
Why separate
Three motivations, all of them quoted in the project's blog posts and talks:
- Independent scaling. Compute is CPU/RAM-bound; storage is I/O/disk-bound. With local disks they had to scale together. Separated, you scale them independently.
- Stateless compute. A stateless compute can be stopped and restarted in seconds, can be bin-packed onto shared hosts, can be put to sleep when idle, and can be cloned cheaply.
- Branching. Once storage is centralized and addressable by
(tenant, timeline, lsn), creating a new "branch" is a metadata operation that is instant and free of incremental data movement.
What this looks like in practice
graph TD
Client --> Compute[Compute<br/>stateless Postgres]
Compute -->|WAL| SK[Safekeepers]
Compute -->|GetPage@LSN| PS[Pageserver]
Compute -.->|on first start| BB[basebackup tarball]
BB --> PS
SK -->|stream WAL| PS
PS -->|layer files| S3[(Object storage)]On compute start:
- The compute downloads a basebackup from the pageserver — a tarball of just enough files to start Postgres at a chosen LSN.
- It opens libpq replication to one safekeeper per timeline (the
walproposerinside theneonextension). - It opens libpq replication to the pageserver (the page-service connection used for
GetPage@LSN). - It starts Postgres, which thinks it's running on local disk because the
neonextension has replaced the storage manager.
On compute write:
- Postgres appends a WAL record.
- The walproposer hooks the insertion path and proposes the record to a quorum of safekeepers.
- Once a majority acknowledges, Postgres considers the write durable.
- The pageserver later pulls the WAL from any safekeeper and decodes it into per-page layer files.
- The compute continues running; future reads of the affected page will see the write because the pageserver applies the WAL on demand.
On compute read:
- Postgres asks for page
(rel, blk). - The smgr replacement sends
GetPage@LSN(rel, blk, current_lsn)to the pageserver. - The pageserver finds the most recent image layer ≤ LSN, replays delta records up to LSN via WAL-redo, and returns the materialized page.
What you get
- Branching is metadata. Creating a branch never touches a layer file. The new timeline shares its parent's layers up to the branch LSN.
- PITR is free. Reading a page at any LSN within the retention window is the same operation as reading at the latest LSN — only the requested LSN differs.
- Compute autoscaling. A compute can be stopped or moved at will. The control plane suspends idle computes; the proxy wakes them on the next connection.
- Read replicas at any LSN. A replica can attach at any LSN within the retention window without affecting the primary.
What you pay
- Read latency. Every cache miss is a network round-trip, plus a WAL-redo if the page isn't cached locally. The compute has multiple caches (Postgres shared_buffers, the
pgxn/neonfile cache) to mitigate this. - WAL is the bottleneck. Every write must be acknowledged by a quorum of safekeepers before commit. This adds latency relative to local-disk Postgres, especially across availability zones.
- Postgres patches. Decoupling the storage manager required upstream patches; we maintain a fork (
vendor/postgres-v*). - Multi-component complexity. The system has more moving parts: pageserver, safekeeper, broker, controller, scrubber, compute_ctl. Operations is correspondingly more involved.
How this differs from existing systems
- Aurora also separates compute and storage but uses a custom storage protocol and a single-tenant storage layer per database. Neon's storage is multi-tenant from the ground up.
- Citus / pg_shard shard a single Postgres instance across many; Neon shards storage but keeps the compute intentionally single-instance (per endpoint).
- CockroachDB / Yugabyte replace Postgres's storage with a Raft-replicated KV store. Neon keeps Postgres's storage manager interface but replaces the implementation with a remote one.
The neon extension is the linchpin
Almost every consequence above is mediated by the pgxn/neon extension:
pagestore_smgr.creplaces the smgr. (Reads.)walproposer.cproposes WAL to safekeepers. (Writes.)file_cache.ccaches pages locally to keep latencies sane. (Performance.)relsize_cache.c,neon_lwlsncache.cadd per-page metadata caches. (Performance.)
See also
docs/separation-compute-storage.md— short note in repo.SELECT 'Hello, World'— Nikita Shamgunov's blog post.Architecture decisions in Neon— Heikki Linnakangas on the rationale.- Pageserver and Safekeeper — the implementations.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.