Repository navigation
storage: seed offset_committed per export for PostgreSQL sources - #39780
peterdukelarsen wants to merge 1 commit into
Conversation
QA LLM Review1. MEDIUM -- Old-syntax PostgreSQL sources still log
|
|
Confirmed, thanks. Under the subsource syntax the source itself is an export with a persist collection and a committed-upper feedback edge, so it reports its own committed upper, and the first revision still seeded it with the source-wide resumption point. Reproduced with the workflow's second source switched to 592c283 seeds every export in 🤖 Generated with Claude Code |
592c283 to
2c9138c
Compare
The PostgreSQL source seeded every export's `offset_committed` with the source-wide resumption point, the least resume upper across exports with the replication slot's `confirmed_flush_lsn` standing in for an export that has never committed. The slot trails the committed LSN by up to one feedback interval, so whenever a table was added to a source, or a replica restarted while a table's snapshot was pending, every existing export reported a value below the one it reported last, and kept doing so on every statistics tick until the pending snapshot finished. Worker 0 of the replica, and the storage controller after a replica restart, log each of those reports as `total gauge offset_committed erroneously regressed`. A snapshot that takes days produces that line for days. Seed each export in `source_resume_uppers` from its own resume upper instead, the source itself included: under the subsource syntax it is an export with a persist collection and a committed-upper feedback edge, so it reports its own committed upper like a table. That upper is reclocked from the export's persist upper the same way the pipeline reclocks the committed uppers the export reports from, so the seed never undercuts the last reported value. An export that has never committed keeps the slot position as its fallback. The replication start position stays the least resume upper of the table outputs, and the live path clamps each export to its own seed instead of the shared one. The new pg-cdc workflow `offset-committed-seed` adds a table to a table-syntax source and a subsource to a subsource-syntax source, each under an ACCESS EXCLUSIVE lock on its primary key index, restarts Materialize with the snapshots pending, and asserts that no regression is logged. A unit test covers the per-export seed computation. Closes: SS-450 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FTqJbQjM1u9PHMcwj3jRcR
2c9138c to
a974949
Compare
Motivation
The PostgreSQL source seeded every export's
offset_committedwith the source-wide resumption point: the least resume upper across exports, with the replication slot'sconfirmed_flush_lsnstanding in for an export that has never committed. The slot trails the committed LSN by up to one feedback interval (1s), so whenever a table was added to a source, or a replica restarted while a table's snapshot was pending, every existing export reported a value below the one it had reported last, and kept doing so on every statistics tick until the pending snapshot finished. Worker 0 of the replica logs each of those reports astotal gauge offset_committed erroneously regressed, and so does the storage controller after a replica restart, because it seeds its retained statistics from the persisted statistics collection. A snapshot that takes days produces that line for days.Closes: SS-450
Changes
Each export is now seeded from its own resume upper, the source itself included: under the subsource syntax it is an export with a persist collection and a committed-upper feedback edge, so it reports its own committed upper like a table. That upper is reclocked from the export's persist upper the same way the pipeline reclocks the committed uppers the export reports from, so the seed does not undercut the last reported value. An export that has never committed keeps the slot position as its fallback. The replication start position stays the least resume upper of the table outputs, and the live path clamps each export to its own seed instead of the shared one.
The SQL Server source has the same shape of seed and is left unchanged here; it can follow in its own PR.
Tests
offset_committed_seedsunit test insrc/storage/src/source/postgres/replication.rscovers the per-export seed computation.offset-committed-seed(test/pg-cdc/mzcompose.pyandtest/pg-cdc/override/offset-committed-seed-part-*.td) adds a table to a table-syntax source and a subsource to a subsource-syntax source, each under an ACCESS EXCLUSIVE lock on its primary key index so the snapshot stays pending, restarts Materialize with the snapshot pending, and asserts that no regression is logged. The statistics views keep the higher value, so the error line in the logs is the only observable. Before this change the workflow fails on 4 of 5 runs, with roughly 120 to 320 regression lines; the race it depends on is between the slot feedback and the DDL. After this change it passes on every run. The subsource-syntax half was added after the QA review below: without seeding the source's own export it fails with 125 lines, all for the source's own statistic.Known residual
One rare occurrence was still observed after the fix on the DDL path, on the third add after an add, release, drop cycle: the export's own resume upper came out about 21KB below the value it had last reported, so the seed undercut it. Eleven further cycles with finer sampling did not reproduce it. It is a much smaller and rarer case than the one this change removes, and the cause is still open.
Tips for reviewer
The main change is
offset_committed_seedsand its call site insrc/storage/src/source/postgres/replication.rs. The workflow follows the same shape.Checklist
$T ⇔ Proto$Tmapping (possibly in a backwards-incompatible way), then it is tagged with aT-protolabel.🤖 Generated with Claude Code
https://claude.ai/code/session_01FTqJbQjM1u9PHMcwj3jRcR