Skip to content

fix(remote): push the source dump instead of the optimizer's own metadata - #199

Merged
veksen merged 1 commit into
mainfrom
fix-push-source-stats
Jul 27, 2026
Merged

fix(remote): push the source dump instead of the optimizer's own metadata#199
veksen merged 1 commit into
mainfrom
fix-push-source-stats

Conversation

@veksen

@veksen veksen commented Jul 27, 2026

Copy link
Copy Markdown
Member

Goal

Stop a statistics push from replacing a project's production snapshot with an empty one. Found reviewing #197 after it merged.

What

applyStatistics emitted optimizer.ownMetadata. That is a dump of the optimizing database, not the source:

  • QueryOptimizer is constructed against optimizingDbUDRL (remote.ts:113), so setStatistics dumps from that connection (query-optimizer.ts:156) and passes the result as plannerTables.
  • Statistics.ownMetadata is plannerTables. Core's own comment says snapshot carries the real numbers and passing the same array as both is "rarely what you want".
  • The optimizing database is restored with --exclude-table-data-and-children (schema-link.ts:124), and restoreStats only ever runs inside a transaction that rolls back (genalgo.ts:521).

So the pushed payload reports reltuples = -1 for every table and stats: null for every column. relay.service.ts stores it verbatim, overwriting the single project-level snapshot.

Why it matters now

Nothing reached this path before. applyStatistics is only called from updateStatistics (declared in ClientApi, never invoked by the server), resetStats, and the HTTP import.

#197 seeds the drift baseline on connect, which is what first makes the drift path reachable in production. A drifted project would then push the empty dump, baseline itself against reltuples = -1, and on the next poll see every real table as Size Drift — because Math.max(-1, 1) gives a denominator of 1, so any table over the 1,000-row floor drifts. The result is a full DUMP_STATS_SQL against the customer's production database every 60 seconds, indefinitely.

Not yet live: the deployed analyzer predates #195 and #197. Worth holding the analyzer deploy until this merges.

How

Push statsMode.stats — the dump we were handed — and baseline against the same array. Push nothing for fromAssumption, since synthetic defaults are not a project's production statistics; that also closes a smaller existing hole where resetStats pushed the empty dump for any mode, because the emit sat outside the mode check.

Tests

apply-statistics.test.ts drives applyStatistics with a stubbed optimizer whose ownMetadata reports reltuples = -1, and asserts the emitted payload is the source dump with its real row count. A second case asserts fromAssumption emits nothing. Both fail against the previous behaviour.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Query Doctor Analysis

3 queries analyzed

0 regressed · 0 improved · 0 new · 0 removed

2 pre-existing issues

Using assumed statistics (10000000 rows/table). For better results, sync production stats.

More detail → get_ci_run({ runId: "019fa561-54a1-75df-ae3c-2b4f4d46739a" }) · view run · docs

…data

`applyStatistics` emitted `optimizer.ownMetadata`, which is a dump of the
*optimizing* database, not the source. That copy is restored with
`--exclude-table-data-and-children` and its statistics are only ever written
inside a transaction that rolls back, so every table reports reltuples = -1 and
every column stats: null. The relay stores what it receives verbatim, so a push
replaces a project's real production statistics with an empty snapshot.

Nothing triggered it before: applyStatistics is only reached from
updateStatistics, resetStats, and the HTTP import, and none run on their own.
Seeding the drift baseline on connect made the drift path reachable, which
turns this into a live fault — a drifted project would push the empty dump,
baseline itself against reltuples = -1, then see every real table as size drift
and re-dump the source every 60 seconds.

Push `statsMode.stats` instead, and push nothing for fromAssumption: synthetic
defaults are not a project's production statistics.

Co-Authored-By: Claude <[email protected]>
@veksen
veksen force-pushed the fix-push-source-stats branch from 63e1cf4 to ca54187 Compare July 27, 2026 20:59
@veksen
veksen merged commit c7aa61d into main Jul 27, 2026
6 checks passed
@veksen
veksen deleted the fix-push-source-stats branch July 27, 2026 21:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant