Open-Source Wikis

/

Neon

/

Primitives

/

LSN and WAL

neondatabase/neon

LSN and WAL

The Log Sequence Number is the byte-offset clock that drives every Neon component. The Write-Ahead Log is the durable journal of changes whose offsets are LSNs. Almost everything in the system is parameterized by an LSN.

LSN

A Lsn (libs/utils/src/lsn.rs) is a 64-bit unsigned integer representing a byte position in the WAL stream. It increases monotonically as new WAL is appended. It's printed as two hex numbers separated by a slash:

0/16B5A50      = 0x16B5A50 = 23,829,072 bytes
000005A2/00000000  = bytes-into-WAL

This is the same format Postgres uses for the pg_lsn data type and what shows up in pg_current_wal_lsn().

A few convenient operations:

  • Lsn::from_str("0/16B5A50") — parse the on-the-wire format.
  • lsn.checked_sub(other) — compute byte distances (used as a measure of replication lag).
  • lsn.is_aligned() — true if 8-byte aligned (WAL records are always 8-byte aligned).
  • lsn.segment_number(segsize) — which WAL segment file this LSN falls in.

Operations on Lsn are checked-arithmetic by default; the bare + operator is implemented but most code uses checked_add to catch overflow.

How LSNs are used

  • In GetPage@LSN(key, lsn) — "give me page key as of LSN lsn".
  • In timeline state — last_record_lsn, disk_consistent_lsn, remote_consistent_lsn, ancestor_lsn.
  • In layer file naming — <key_lo>-<key_hi>__<lsn_lo>-<lsn_hi> is the on-disk filename for a delta layer.
  • In the safekeeper's consensus — flush_lsn, commit_lsn, restart_lsn, VCL (volume-consistent LSN).
  • In compute backpressure — when the gap between the compute's pg_current_wal_lsn() and the safekeepers' flush_lsn exceeds the configured limit, the compute pauses.

Multiple LSN species

Different components track LSNs with slightly different meanings. The glossary entry Lsn in docs/glossary.md enumerates them:

Compute-side (Postgres):

  • pg_current_wal_lsn() — current WAL write position.
  • pg_current_wal_flush_lsn() — latest WAL flushed to disk on the primary.
  • pg_last_wal_receive_lsn() — on a replica, latest received from primary.

Safekeeper-side:

  • flush_lsn — latest LSN durable on this safekeeper's disk.
  • commit_lsn — LSN confirmed by quorum of safekeepers (durable).
  • VCL (volume-consistent LSN) — guaranteed to be persistent and have all prior WAL.

Pageserver-side:

  • last_record_lsn — end of last applied WAL record (in-memory layer head).
  • disk_consistent_lsn — LSN ≤ which all data is on local disk.
  • remote_consistent_lsn — LSN ≤ which all data is in remote storage.
  • ancestor_lsn — LSN at which a branch was forked from its parent.

A timeline maintains the invariant: remote_consistent_lsn ≤ disk_consistent_lsn ≤ last_record_lsn.

WAL: the underlying stream

The WAL itself is the same thing it is in vanilla Postgres: a stream of fixed-size records. Each record has:

  • A 4-byte total length and 4-byte previous-record length (for backwards traversal).
  • A resource manager id (rmid) — heap, btree, transaction commit, custom Neon rmgrs, …
  • An info byte (record subtype).
  • Payload: per-record-type data.

Postgres breaks the stream into WAL segments, fixed-size files (16 MB by default) named 00000001000000000000000A (timeline-id, segment-number-high, segment-number-low). The safekeeper stores WAL in this exact format on disk, which means standard tools like pg_waldump work directly on a safekeeper's disk.

How records are decoded

libs/wal_decoder/ is the crate that decodes raw WAL records into the per-page tuples the pageserver's storage layer wants. For each record:

  1. The decoder identifies the rmid and info.
  2. It extracts the list of (RelFileNode, BlockNumber) pages the record affects.
  3. For each affected page it produces a (key, lsn, value) tuple, where key is the encoded (rel, block), lsn is the record's LSN, and value is either the full WAL record (delta) or a directly produced page image (for full-page-image records).

This decoding can happen on either side: traditionally the pageserver does it (pageserver/src/walingest.rs), but newer code paths let the safekeeper do it and ship pre-decoded tuples to the pageserver (safekeeper/src/send_interpreted_wal.rs).

WAL produced by Neon vs. vanilla

Neon's WAL is not bit-for-bit compatible with upstream Postgres. The t_cid patch (docs/core_changes.md) widens heap WAL records by 4 bytes so that INSERT/UPDATE/DELETE records carry the inserter's command id. Custom Neon rmgrs (pgxn/neon_rmgr/) introduce additional record types. As a result, you can't take a Neon WAL stream and replay it on vanilla Postgres, and vice versa.

Backpressure

pgxn/neon/ exposes three GUCs that govern compute → safekeeper / pageserver flow:

  • max_replication_write_lag — bytes the compute is allowed to be ahead of the safekeepers.
  • max_replication_flush_lag — bytes ahead of the safekeepers' flush_lsn.
  • max_replication_apply_lag — bytes ahead of the pageserver's last_record_lsn.

When any limit is exceeded, the compute's commit waits stall until the corresponding LSN catches up. This is the mechanism that prevents a runaway compute from outrunning storage.

See also

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

LSN and WAL – Neon wiki | Factory