Open-Source Wikis

/

Neon

/

Primitives

/

Branches

neondatabase/neon

Branches

Branches are the user-facing name for timelines. To users, "creating a branch" is a defining Neon feature — it's instant, copy-on-write, and free of incremental disk usage. Internally a branch is just a timeline whose ancestor_timeline_id points at a parent and whose ancestor_lsn is the LSN at which the divergence happens.

Lifecycle

graph TD
    Root[main timeline]
    Root --|branch at LSN 0/16F9A00|--> Br[migration_check timeline]
    Br --|writes diverge from here|--> NewLayer[new layer files for this timeline]
    Root --|original layers stay shared|--> Shared[(shared layer files)]
    NewLayer & Shared --> ReadAt[read at LSN >= 0/16F9A00]

When you run:

cargo neon timeline branch --branch-name migration_check

neon_local creates a new timeline metadata record with ancestor_timeline_id = main_timeline_id and ancestor_lsn = current_lsn. No layer files are copied. Reads in the new branch:

  • For LSNs ≤ ancestor_lsn, traverse into the parent timeline's layer map.
  • For LSNs > ancestor_lsn, only the new branch's own layers are consulted.

Writes on the new branch go through the normal pageserver ingest path and produce new layer files owned by the branch. The parent's data is never modified.

Read path

Timeline::get_at_lsn(key, lsn) first searches the timeline's own layer map. If the search reaches the bottom of the chain (no layer covers the key at the requested LSN) and the timeline has an ancestor, it recurses into the ancestor at min(lsn, ancestor_lsn). The recursion is bounded by the ancestor chain depth — most timelines have a chain of depth 1 or 2.

Why branches are cheap

Two reasons:

  1. No data is copied at branch time. The branch shares the parent's layer files for LSNs ≤ ancestor_lsn.
  2. WAL diverges. Each branch has its own walreceiver connection; new WAL flows into the branch's own in-memory layer and L0 files. This means even heavy writes on a branch don't affect the parent's storage cost.

Branches do consume a tiny bit of metadata (a row in the timeline map and an index_part.json) but no bulk data.

Detaching the ancestor

A branch can be promoted to a fully independent timeline via:

storcon-cli tenant <id> timeline-detach-ancestor <timeline_id>

The pageserver code path is pageserver/src/tenant/timeline/detach_ancestor.rs (~48 KB). It:

  1. Identifies all layers in the ancestor that the branch needs.
  2. Materializes them into new layers owned by the branch (so they are no longer ancestor-shared).
  3. Updates the branch's metadata to drop ancestor_timeline_id.
  4. Now garbage collection can drop the ancestor without breaking the (formerly child) branch.

Detach is the operation users hit when they want to delete an old "main" timeline that has many branches.

Branch points and PITR

Branches and PITR (point-in-time recovery) are closely related but distinct:

  • PITR window is the time window over which a single timeline retains enough history to read at older LSNs. Defined per-timeline (default 24 h, see pitr_interval in pageserver/src/config.rs).
  • Branch point can be at any LSN within an ancestor's PITR window. Once the branch exists, the ancestor must keep its history at and beyond ancestor_lsn even if it would otherwise be garbage collected.

A branch effectively pins WAL retention in the ancestor at its branch point.

Why "timeline" not "branch" in code

Two reasons:

  • Postgres namespace conflict. Postgres already uses "timeline" for its own WAL forking concept (see pg_control). The Neon usage is unrelated, but the term was chosen first.
  • Hierarchical history. "Branch" suggests a tree; "timeline" makes it clearer that you're talking about a linear, append-only history that may share a prefix with another linear history.

The user-facing tooling (cargo neon timeline branch …, cargo neon endpoint create --branch-name …) uses both interchangeably.

Compute on a branch

Each branch can have one or more compute endpoints (cargo neon endpoint create --branch-name <name>). These computes:

  • Connect to the branch's pageserver shards (which may be the same as the parent's, or different — sharding is per-tenant).
  • Stream WAL through the safekeepers assigned to the branch's tenant.
  • Are independent of any compute running on the parent or sibling branch.

A common workflow: production runs on main, a developer creates a branch for a migration check, runs ALTER TABLE on the branch, and then either promotes the branch to main (production cutover) or simply deletes the branch.

See also

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

Branches – Neon wiki | Factory