> **Plain-language companion:** [v0.32.0.md](v0.32.0.md) ## v0.32.0 — Citus: Stable Naming & Per-Source Frontier Foundation **Status: Planned.** Derived from [plans/ecosystem/PLAN_CITUS.md](../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` 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-2** — `SourceIdentifier { 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-6** — `Frontier.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-8** — `sql/pg_trickle----.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) ---