Plain-language companion: v0.35.0.md

v0.35.0 — Temporal IVM & Columnar Materialization

Status: Planned. Derived from plans/PLAN_OVERALL_ASSESSMENT_3.md §10.2, §10.7.

Release Theme v0.35.0 unlocks two analytic workload patterns that the current streaming engine cannot serve. Temporal IVM lets a stream table maintain a rolling history of how its rows have changed over time — providing first-class SCD-Type-2 semantics without external ETL or separate audit tables. Columnar materialization lets a stream table store its result set in Citus columnar storage (or pg_mooncake), dramatically reducing storage footprint and query I/O for analytic consumers that scan the materialised result set but never write to it. Together they close the OLTP→OLAP bridging gap and make pg_trickle viable as the materialization layer for dashboards and reporting queries.


Correctness

ID Title Effort Priority
CORR-1 Temporal IVM: two-dimensional frontier (LSN, timestamp) L P0
CORR-2 Columnar: verify differential MERGE compatibility with columnar storage M P0

CORR-1 — Temporal IVM requires extending the frontier model from a one-dimensional LSN cursor to a two-dimensional (frontier_lsn, valid_from_ts) pair. Each row carries a __pgt_valid_from TIMESTAMPTZ and an optional __pgt_valid_to TIMESTAMPTZ. Rows are never physically deleted; instead a “close” delta sets valid_to. Queries against the stream table with AS OF TIMESTAMP $1 resolve to the materialised row version valid at that timestamp. Schema change: Yes — new temporal_mode BOOLEAN column on pgtrickle.pgt_stream_tables; new __pgt_valid_from/__pgt_valid_to columns auto-added to the storage table when temporal_mode = true.

CORR-2 — Citus columnar and pg_mooncake use append-only storage models. Verify that the differential MERGE (MERGE INTO storage USING delta) is compatible with append-only semantics (likely requires DELETE + INSERT merge strategy for columnar targets). Add a storage_backend column to pgtrickle.pgt_stream_tables and route merge codegen accordingly.


Ease of Use

ID Title Effort Priority
UX-1 create_stream_table(…, temporal := true) parameter M P0
UX-2 AS OF TIMESTAMP query rewrite in DVM parser L P1
UX-3 create_stream_table(…, storage_backend := 'columnar') parameter M P1
UX-4 Automatic delete_insert strategy for columnar backends S P1
UX-5 Documentation: temporal IVM tutorial + SCD-Type-2 worked example M P1
UX-6 Documentation: columnar backend setup guide (Citus + pg_mooncake) S P1

Test Coverage

ID Title Effort Priority
TEST-1 Integration: temporal stream table AS OF TIMESTAMP round-trip L P0
TEST-2 Integration: SCD-Type-2 dimension pattern end-to-end M P1
TEST-3 Integration: columnar stream table MERGE parity with heap M P0
TEST-4 Integration: temporal + columnar combined (temporal columnar ST) M P2

Conflicts & Risks

  • CORR-1 requires a non-trivial DVM engine extension (two-dimensional frontier). Spike before committing to the milestone; do not commit until the spike proves the approach is sound.
  • CORR-2 depends on the Citus columnar or pg_mooncake extension being present; CI must add a Testcontainers image with one of these available. Gate columnar support behind pg_trickle.columnar_backend GUC (default none); the CI matrix should test at least one columnar provider.
  • The combination of temporal mode and columnar storage is a P2 stretch goal; do not block the release on it.

Exit Criteria

  • [ ] CORR-1/UX-1: create_stream_table(…, temporal := true) creates storage table with __pgt_valid_from/__pgt_valid_to; rows never physically deleted
  • [ ] UX-2: AS OF TIMESTAMP $1 query rewrites resolve against the materialised history
  • [ ] TEST-1: Temporal round-trip test passes: insert → update → delete, query at t₀/t₁/t₂ returns correct historical rows
  • [ ] TEST-2: SCD-Type-2 dimension pattern: slowly-changing customer table materialised with full history, current-version query using WHERE valid_to IS NULL
  • [ ] CORR-2/UX-3: Columnar stream table creates; delete_insert strategy used automatically; columnar MERGE E2E parity test passes
  • [ ] Extension upgrade path tested (0.32.0 → 0.33.0)
  • [ ] just check-version-sync passes