Help us shape the future of Go! Take the 2026 Go Developer Survey

postgres

package
v0.31.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 28, 2026 License: Apache-2.0 Imports: 31 Imported by: 0

Documentation

Overview

Package postgres implements resumable full-snapshot reads from a PostgreSQL source.

At plan time (before the first row is read) the router asks one question of Postgres and combines the answer with a few facts from the run config:

pg_stats.correlation for the leading PK column
  ↓ (between -1 and 1; 1 = rows are physically in key order; 0 = random)

Is this a CDC run with a logical slot available?  → ModeSlot
Is the table declared append-only?                → ModeCtidAppendOnly (no xmin, just blocks)
|correlation| >= 0.8?                             → ModeKeyset   (index scan already near-sequential)
PK is mutable (rows can change their key)?        → ModeCtidXmin (physical blocks + repair pass)
Otherwise (random keys, immutable PK)             → ModeBitmap   (bitmap heap scan, same correctness as keyset)

The chosen mode is frozen into the checkpoint at plan time and reused on every resume of the same run, so the shard layout never changes mid-flight.

What each mode actually does

  • ModeKeyset — "follow the index in order" Issues ORDER BY pk LIMIT page queries. The PK index is walked in key order, one page at a time, and the last key seen becomes the cursor for the next page. Works great when the heap is already in the same order as the key (high correlation), because each page is one contiguous disk read. Resumable to the exact row.

  • ModeBitmap — "collect all the addresses first, then fetch blocks in order" Splits the key space into sub-ranges (same boundaries as keyset). Each sub-range is read with a plain range predicate and NO ORDER BY, so Postgres can collect all matching index TIDs, sort them by heap block, and read those blocks in ascending order with prefetch. This beats keyset for randomly-keyed tables because it avoids the random I/O pattern that ORDER BY forces. Resume is all-or-nothing per sub-range: when a sub-range drains fully the reader sends a Drained sentinel; the pipeline counts written rows and marks the shard Done. Interrupted sub-ranges are re-read whole on resume.

  • ModeCtidXmin — "read the heap directly, then patch up anything that moved" Divides the table into block-range shards (ctid = physical heap address). Reads blocks in order — fast sequential I/O, no index needed. The hard part is safety across a resume boundary, handled by three guards: (1) Rewrite guard: the filenode at run-start is stamped; if VACUUM FULL / CLUSTER / TRUNCATE rewrites the heap between runs, the cursor is stale → re-read from scratch. (2) Horizon-compare reconciliation: the run-start xmin horizon H1 is stamped; before the run closes, completed block ranges are re-scanned for rows with age(xmin) <= age(H1) (i.e. rows that were written or moved after run-start). The idempotent sink absorbs any over-delivery. (3) Freeze guard: if relfrozenxid advanced past H1 (vacuum froze xids), the age() comparison is unreliable → unfiltered rescan of completed ranges.

  • ModeCtidAppendOnly — like ModeCtidXmin but without the xmin apparatus; rows never move or update, so the horizon-compare pass is skipped and resume just continues past the recorded tail. (Not yet wired into the router; declared append-only sources route here explicitly.)

  • ModeSlot — a logical replication slot pins an LSN so updated/moved rows return via WAL replay. Correct for as-of-completion semantics and CDC. The CDC extractor uses this tier through a persistent pgoutput logical replication slot.

Decision table

Condition                                         Mode
─────────────────────────────────────────────────────────────────
CDC run + logical slot available                  ModeSlot
AppendOnly declared                               ModeCtidAppendOnly
|corr| >= 0.8  (heap ≈ key order)                 ModeKeyset
PK mutable                                        ModeCtidXmin
Default (random keys, immutable PK)               ModeBitmap

Routing code path

PlanResume (keyset.go)
  └─ resolveMode (bitmap.go)            ← reads s.readMode config + auto probe
       └─ Route (readmode.go)           ← queries pg_stats.correlation
            └─ chooseMode (readmode.go) ← pure decision function, unit-tested

Package postgres implements the filament.Source interface as a full-snapshot (ModeFull) reader for PostgreSQL.

Each requested resource is a table name. A table's heap is sliced into one or more shards by physical ctid block range, and each shard is read window by window: WHERE ctid >= '(lo,0)' AND ctid < '(hi,0)' over a fixed span of blocks at a time. Reading by ctid (not the primary key) means one reader handles primary-key, composite-key, and keyless tables identically. On PostgreSQL 14+ each bounded window is a TID Range Scan: sequential heap I/O, no index needed. Both bounds matter — an open-ended ctid > x re-scans to end-of-table on every page, so every window is bounded on both sides.

Large tables split into several shards so a single big table no longer pins one worker while the rest sit idle (see planShards / shard_pages). When more than one shard runs concurrently the reads share an exported snapshot (pg_export_snapshot), so every shard sees one consistent point-in-time even under concurrent writes — the same mechanism pg_dump -j uses.

Rows are read as typed columns over pgx's binary protocol and appended straight into the pipeline's row writers — see columns.go — so neither the source database nor this process ever renders a row as text.

Index

Constants

View Source
const PositionCodec = "postgres.wal.lsn"

PositionCodec identifies PostgreSQL transaction-end WAL positions.

Variables

This section is empty.

Functions

This section is empty.

Types

type LSNCodec added in v0.31.0

type LSNCodec struct{}

LSNCodec compares WAL addresses numerically and canonicalizes PostgreSQL hex notation.

func (LSNCodec) Canonicalize added in v0.31.0

func (c LSNCodec) Canonicalize(p filament.Position) (filament.Position, error)

Canonicalize returns the standard hexadecimal LSN representation.

func (LSNCodec) Compare added in v0.31.0

Compare orders positions numerically within the WAL domain.

func (LSNCodec) Validate added in v0.31.0

func (LSNCodec) Validate(p filament.Position) error

Validate checks the codec version and PostgreSQL LSN syntax.

type ReadMode

type ReadMode int

ReadMode is the chosen extraction strategy for a resumable resource.

const (
	// ModeKeyset reads in key order via the PK index — already sequential when the leading
	// key correlates with heap order, and resumable from a compact key cursor.
	ModeKeyset ReadMode = iota
	// ModeBitmap reads each key sub-range with no ORDER BY so the planner is free to pick a
	// bitmap heap scan (ascending block order + prefetch). Default for random immutable keys.
	ModeBitmap
	// ModeCtidAppendOnly reads physical block ranges on a declared append-only table — no
	// xmin apparatus, since rows never move; resume continues past the recorded tail.
	ModeCtidAppendOnly
	// ModeCtidXmin is the general physical tier: block-range cursor + horizon-compare repair.
	// Catches arbitrary churn including PK moves.
	ModeCtidXmin
	// ModeSlot is the premium physical tier: a logical replication slot pins an LSN and moved
	// rows return via WAL replay.
	ModeSlot
)

func (ReadMode) String

func (m ReadMode) String() string

type RouteSignals

type RouteSignals struct {
	AppendOnly    bool // source declared the table append-only (rows never move/update)
	PKMutable     bool // the primary key can change → needs the physical tier for completeness
	SlotAvailable bool // a logical replication slot is usable (wal_level=logical + privilege)
	CDC           bool // this run feeds change-data-capture (slot pins the snapshot boundary)
}

RouteSignals are the inputs the correlation probe cannot derive from catalog stats: they come from the run config, the source declaration, or the environment.

type Source

type Source struct {
	// contains filtered or unexported fields
}

Source reads tables from a PostgreSQL database as a full snapshot. One instance is created per run: Configure opens the pool, Extract pages the tables, Teardown closes the pool.

func New

func New() *Source

New returns an unconfigured source.

func (*Source) AcknowledgeChanges

func (s *Source) AcknowledgeChanges(ctx context.Context, checkpoints map[string]filament.Checkpoint) error

AcknowledgeChanges advances the logical slot to the oldest checkpoint the runner has verified durable after sink commit. ExtractChanges cannot do this itself: at extraction return time the sink and tracker may still fail. The standby replies sent while streaming only confirm a flush position; advancing decodes the retained WAL so restart_lsn moves and the server can recycle it.

func (*Source) BindReplicationStream added in v0.0.27

func (s *Source) BindReplicationStream(config map[string]any, stream filament.ReplicationStream) (map[string]any, error)

BindReplicationStream injects the resolved generation's slot into a run without mutating the stored connection or pipeline configuration.

func (*Source) CleanupReplicationStream added in v0.0.27

func (s *Source) CleanupReplicationStream(ctx context.Context, stream filament.ReplicationStream) error

CleanupReplicationStream drops a retired logical slot after its successor has committed. Missing slots are already clean and therefore succeed.

func (*Source) Configure

func (s *Source) Configure(ctx context.Context, cfg filament.Config) error

Configure reads dsn/schema/page_size/shard_pages/max_conns and opens a connection pool. Set max_conns to at least the run's parallelism + 1 (the snapshot coordinator holds one connection) so concurrent shard reads each get a connection instead of queueing; unset keeps pgx's default (max(4, NumCPU)).

func (*Source) CursorColumns

func (s *Source) CursorColumns(ctx context.Context, table string) ([]filament.CursorColumn, error)

CursorColumns reports every table column and ranks supported conventional watermark names.

func (*Source) Discover

Discover lists tables in the configured schema with their primary keys and row estimates.

func (*Source) Extract

func (s *Source) Extract(ctx context.Context, sink arrowbatch.Inlet, opts filament.ExtractOpts) error

Extract plans every requested table into shards and pages them out through the sink. With Parallelism > 1 the shards are read concurrently (bounded by Parallelism) into the shared sink under one exported snapshot; paging within a shard stays sequential. The first shard error cancels the rest and is returned.

func (*Source) ExtractChanges

func (s *Source) ExtractChanges(ctx context.Context, sink arrowbatch.Inlet, opts filament.ChangeExtractOpts) error

ExtractChanges replays pgoutput row changes for the selected tables. The publication is created/expanded by default; set manage_publication=false when database administration owns it instead. A slot is never dropped automatically: its stable identity is what makes a later catch-up lossless.

func (*Source) ExtractFrom

func (s *Source) ExtractFrom(ctx context.Context, sink arrowbatch.Inlet, opts filament.ExtractOpts, prev map[string]filament.Checkpoint) error

ExtractFrom reads each resource from its checkpoint; the checkpoint plan's mode decides how. Incremental plans read by cursor, everything else reads via keyset shards (concurrently, under one exported snapshot, like Extract); a resource with no keyset plan (no primary key, or a checkpoint-free full read) falls back to the ctid reader and is read whole.

func (*Source) Lookup added in v0.31.0

func (*Source) Lookup(name string, version int) (filament.PositionCodec, error)

Lookup resolves the PostgreSQL WAL codec without opening a connection.

func (*Source) OpenStream added in v0.31.0

func (s *Source) OpenStream(ctx context.Context, opts filament.StreamOpenOpts) (_ filament.StreamSession, result error)

OpenStream opens one persistent pgoutput session. An uncertified initial snapshot completes before the session returns its first candidate coverage.

func (*Source) PlanIncremental

func (s *Source) PlanIncremental(ctx context.Context, resources []string, prev map[string]filament.Checkpoint, cursors map[string]filament.ResourceCursorConfig) (map[string]filament.Checkpoint, error)

PlanIncremental builds a single compound watermark per table. The source cursor is followed by the complete primary key so equal cursor values are deterministic and cannot skip rows at a batch boundary.

func (*Source) PlanReplicationStream added in v0.0.27

PlanReplicationStream gives one pipeline route its own logical replication slot while keeping the publication shared at the connection level.

func (*Source) PlanResume

func (s *Source) PlanResume(ctx context.Context, resources []string, prev map[string]filament.Checkpoint) (map[string]filament.Checkpoint, error)

PlanResume builds the per-resource keyset plan: the shard layout plus any cursor carried over from prev. A resource without a primary key maps to nil (non-resumable; read whole via ctid). The engine persists this plan so a later resume reuses the same stable shard boundaries.

func (*Source) Route

func (s *Source) Route(ctx context.Context, table string, sig RouteSignals) (ReadMode, float64, bool)

Route runs the correlation probe for a table's leading key and returns the chosen mode along with the observed correlation (for an observability fact). It never fails the run: a probe error degrades to "no correlation" (random) and routes on the signals alone.

func (*Source) Schema

func (s *Source) Schema(ctx context.Context, resource string) (rowmodel.Schema, error)

Schema returns the column schema of one table: every live column in attribute order with its Postgres type (format_type, kept verbatim in Native for a same-engine round-trip and classified into a LogicalType), plus the primary key. It implements filament.SchemaProvider so a Schematized sink can build matching typed tables.

func (*Source) Spec

func (s *Source) Spec() filament.ConnectorSpec

Spec describes the source's config fields, modes, and write policies.

func (*Source) Teardown

func (s *Source) Teardown(context.Context) error

Teardown closes the pool.

func (*Source) TestConnection

func (s *Source) TestConnection(ctx context.Context, cfg filament.Config) error

TestConnection opens a short-lived pool and pings the database.

func (*Source) Validate

func (s *Source) Validate(cfg filament.Config) error

Validate rejects an invalid URL or incomplete individual connection fields.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL