Open-Source Wikis

/

Neon

/

Background

/

Separation of compute and storage

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:

  1. 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.
  2. 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.
  3. 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:

  1. The compute downloads a basebackup from the pageserver — a tarball of just enough files to start Postgres at a chosen LSN.
  2. It opens libpq replication to one safekeeper per timeline (the walproposer inside the neon extension).
  3. It opens libpq replication to the pageserver (the page-service connection used for GetPage@LSN).
  4. It starts Postgres, which thinks it's running on local disk because the neon extension has replaced the storage manager.

On compute write:

  1. Postgres appends a WAL record.
  2. The walproposer hooks the insertion path and proposes the record to a quorum of safekeepers.
  3. Once a majority acknowledges, Postgres considers the write durable.
  4. The pageserver later pulls the WAL from any safekeeper and decodes it into per-page layer files.
  5. 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:

  1. Postgres asks for page (rel, blk).
  2. The smgr replacement sends GetPage@LSN(rel, blk, current_lsn) to the pageserver.
  3. 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/neon file 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.c replaces the smgr. (Reads.)
  • walproposer.c proposes WAL to safekeepers. (Writes.)
  • file_cache.c caches pages locally to keep latencies sane. (Performance.)
  • relsize_cache.c, neon_lwlsncache.c add per-page metadata caches. (Performance.)

See Neon Postgres extension.

See also

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Separation of compute and storage – Neon wiki | Factory