Plain-language companion: v0.32.0.md

v0.32.0 — Citus: Stable Naming & Per-Source Frontier Foundation

Status: Planned. Derived from plans/ecosystem/PLAN_CITUS.md §6 Phase 1 and Phase 2.

Release Theme v0.32.0 is the first of two releases delivering world-class Citus support. It ships two additive, non-breaking layers: (1) a src/citus.rs module with Citus detection and placement helpers; and (2) a stable-hash naming scheme that replaces OID-keyed internal objects (changes_{oid}, pg_trickle_cdc_fn_{oid}, slot names, frontier keys) with names derived from stable_hash(database_oid || '/' || schema || '.' || table). Both layers produce immediate quality benefits for all users — stable names survive pg_dump/restore and major-version upgrades — and they are mandatory prerequisites for v0.35.0’s per-worker slot CDC. There is zero behaviour change on a single-node deployment.


Correctness

ID Title Effort Priority
CITUS-1 src/citus.rs: detection helpers and placement query M P0
CITUS-2 SourceIdentifier struct carrying (oid, stable_name) S P0
CITUS-3 Catalog migration: add source_stable_name, source_placement, st_placement columns S P0
CITUS-4 Rename all OID-keyed objects to stable_name form M P0
CITUS-5 Frontier rekey: Frontier.sources from OID string to stable_name S P1
CITUS-6 Audit and remove cross-source global LSN comparisons M P1
CITUS-7 Add frontier_per_node JSONB column for distributed sources S P1
CITUS-8 SQL migration: rename existing changes_{oid} tables and trigger functions in a single transaction M P0

CITUS-1 — New module src/citus.rs. Detection helpers: - is_citus_loaded()SELECT 1 FROM pg_extension WHERE extname='citus' - placement(oid)Local | Reference | Distributed { dist_column } via pg_dist_partition - worker_nodes()Vec<NodeAddr> from pg_dist_node - shard_placements(table_oid) — which workers host shards

None of these panic if Citus is absent; all return sensible defaults (Local, empty vecs). Citus-specific code paths are always guarded by is_citus_loaded().

CITUS-2SourceIdentifier { oid: pg_sys::Oid, stable_name: String }. stable_name = lower_hex(FNV-1a-64(database_oid || "/" || schema || "." || table))[..16]. Deterministic, short, URL-safe, identical on every Citus node. Serialised in pgt_change_tracking.source_stable_name and used for all object names.

CITUS-3 — Schema changes (nullable, backward-compatible): sql ALTER TABLE pgtrickle.pgt_change_tracking ADD COLUMN source_stable_name TEXT, ADD COLUMN source_placement TEXT NOT NULL DEFAULT 'local'; ALTER TABLE pgtrickle.pgt_dependencies ADD COLUMN source_stable_name TEXT, ADD COLUMN source_placement TEXT NOT NULL DEFAULT 'local'; ALTER TABLE pgtrickle.pgt_stream_tables ADD COLUMN st_placement TEXT NOT NULL DEFAULT 'local'; Old rows receive a synthetic stable name (hash of existing OID-based name) and continue to function unchanged.

CITUS-4 — Replace changes_{oid}, pg_trickle_cdc_ins_{oid}, pg_trickle_cdc_fn_{oid}, idx_changes_{oid}_*, WAL slot names (pgtrickle_{oid}), and the __PGS_PREV_LSN_{oid}__ placeholder tokens in src/dvm/diff.rs with {stable_name} forms. Affected files: src/cdc.rs, src/wal_decoder.rs, src/dvm/diff.rs, src/refresh/codegen.rs, src/dvm/operators/*.

CITUS-5 / CITUS-6Frontier.sources is already a HashMap; rekey from OID string to stable_name. Audit src/refresh/codegen.rs and src/scheduler.rs for reads of pg_current_wal_lsn() used in cross-source comparisons; replace with per-source watermark logic from src/wal_decoder.rs.

CITUS-7 — Add frontier_per_node JSONB to pgt_change_tracking for distributed sources. When the source is Local this column is NULL; for Distributed sources it records { "worker_id": lsn, ... } tuples.

CITUS-8sql/pg_trickle--<prev>--<next>.sql performs column adds then backfills source_stable_name from pg_class + pg_namespace, renames existing buffer tables and trigger functions to the stable-hash form, updates the WAL slot name in pg_replication_slots (via pg_rename_logical_replication_slot where available), and updates pgt_change_tracking. Provides a downgrade path in the same file.


Stability

ID Title Effort Priority
STAB-1 Keep OID-based lookup as fallback for rows without source_stable_name S P0
STAB-2 Detect and surface a clear error if stable hash collides (astronomically unlikely; check at create time) S P1

Test Coverage

ID Title Effort Priority
TEST-1 Unit tests for stable_hash() — known vectors, cross-platform determinism S P0
TEST-2 Unit tests for SourceIdentifier round-trip serialisation S P0
TEST-3 Integration: pg_dump / pg_restore cycle preserves stream table with stable-named objects M P0
TEST-4 Integration: major-version upgrade simulation — OID changes, stable name unchanged M P1
TEST-5 Integration: frontier rekey — multi-source ST with different write rates converges correctly M P0
TEST-6 Integration: existing installation upgrade (v0.31.0 → v0.32.0) renames objects correctly M P0

Documentation

ID Title Effort Priority
DOCS-1 Changelog entry explaining stable naming motivation S P0
DOCS-2 Upgrade guide: what changes, what to expect, rollback instructions S P0

Exit Criteria

  • [ ] CITUS-1: is_citus_loaded(), placement(), worker_nodes() compile and return sensible defaults when Citus is absent
  • [ ] CITUS-2: stable_hash("public.orders") produces identical output on two fresh PG instances with different OIDs
  • [ ] CITUS-4: no {oid} tokens remain in object names created by setup_cdc_for_source()
  • [ ] CITUS-8: migration script renames all existing objects; just test-upgrade-all passes
  • [ ] TEST-3: pg_dump / pg_restore round-trip test passes
  • [ ] TEST-6: v0.31.0 → v0.32.0 upgrade integration test passes
  • [ ] Zero performance regression on the single-node benchmark suite (Citus detection check adds ≤ 1 µs per create_stream_table() call when Citus is absent)
  • [ ] just check-version-sync passes
  • [ ] just lint passes (zero warnings)