Skip to content

fix(remote): publish production statistics from the sync that dumped them - #235

Merged
veksen merged 3 commits into
mainfrom
fix-publish-stats-on-sync
Aug 14, 2026
Merged

fix(remote): publish production statistics from the sync that dumped them#235
veksen merged 3 commits into
mainfrom
fix-publish-stats-on-sync

Conversation

@veksen

@veksen veksen commented Aug 14, 2026

Copy link
Copy Markdown
Member

Goal

An analyzer running against a database publishes that database's statistics on its own. Today it never does, so every project costs its queries against the 10M-row assumption for its whole life.

What

refreshStatsIfStale is the only code path that pushes statistics to the server. It returns immediately when statsBaseline is unset. The only thing that set statsBaseline was a push, and the only other way in, seedStatsBaseline, needs a snapshot the server does not have yet. A project with no snapshot never dumps, so it never gets one.

Found on veksen-enterprises/d2armory, whose analyzer has been connected since 2026-07-20. Its latest CI run reports statisticsMode: fromAssumption and statisticsPayloadId: null, and the container logs Statistics refresh skipped: no drift baseline, so nothing can trigger a dump.

How

syncFrom already dumps the source statistics and hands them to the optimizer. It now also passes them to recordSourceStatistics, extracted from applyStatistics, which sets the baseline and emits statsApplied. The first sync at boot publishes, and drift keeps the snapshot current after that.

decideStatsStrategy used to skip the dump below STATS_ROWS_THRESHOLD. It now dumps either way and returns the dump alongside the mode. The threshold still decides what the optimizer costs against; it no longer decides whether production's numbers reach the server. StatsResult.dump carries only statistics measured from the source, so a static mode loaded from a file still publishes nothing.

Republishing on every boot costs nothing on the server: setFromAnalyzer hashes the tables and a match only moves confirmed_at.

Tests

src/remote/publish-stats-on-sync.test.ts syncs from a container holding 200 rows, which is under the threshold, and asserts statsApplied carries reltuples: 200. It fails on main with zero emissions.

Full suite: 455 tests across 44 files, all passing. tsc --noEmit clean.

…them

Statistics reached the server only from refreshStatsIfStale, which returns
immediately when there is no drift baseline. Only a push set that baseline, and
only refreshStatsIfStale pushed. A project with no snapshot on the server had
nothing to seed a baseline from, so it never dumped, so it never got a snapshot.
Every project stayed on synthetic statistics unless someone pushed a dump by
hand.

The sync already dumps those statistics and hands them to the optimizer. Record
them there as well. The first sync at boot now publishes, and arms drift to keep
the snapshot current.

Below STATS_ROWS_THRESHOLD the sync skipped the dump. That threshold decides
what the optimizer costs against, where a sequential scan on 200 rows is the
right plan. It should not decide whether production's numbers get published, so
the sync now dumps either way and publishes what it dumped. The optimizer still
plans against the mode the threshold picked.

Republishing on every boot costs nothing. The server hashes the tables, and a
match only moves confirmed_at, so the timeline stays one row per change.

Co-Authored-By: Claude <noreply@anthropic.com>

@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 — 6 successful checks

 Cost regression — No query went up more than 5%
 Untested data access — No changed data-access file without a test
 New query — No new queries
 New query with index recommendation — No new query ships an index recommendation
 Schema drift — No schema changes
 High-value nudge — No index or rewrite past the threshold


More details via MCP → get_ci_run({ runId: "019ffe21-35b9-757c-a149-05d0a71fdc77" }) · view run · docs
3 queries read against main on assumed statistics of 10,000,000 rows per table. Sync production stats for costs measured against your real data.

veksen and others added 2 commits August 13, 2026 23:36
runner.ci.test.ts imported it from remote.test.ts, which registers that file's
11 container tests into runner.ci's suite as well. They ran twice: the full run
reported 455 tests where 444 exist. test-utils.ts holds no top-level test(), so
importing from there costs nothing.

Co-Authored-By: Claude <noreply@anthropic.com>
…ed comments

decideStatsStrategy no longer needs the row count before it can dump, so the two
queries run together. The dump reads pg_class, pg_attribute and pg_statistic;
the count aggregates pg_class. Neither waits on the other.

The comments said the same thing in three places. StatsResult.dump keeps the
part the code cannot show, which is why a static mode publishes nothing. The
placement comment in syncFrom claimed an ordering constraint the position does
not buy, since the dump is already taken by then; it now says what is true. A
comment in seed-stats-baseline.test.ts described the old sync path and this
branch made it false.

Co-Authored-By: Claude <noreply@anthropic.com>
@veksen
veksen merged commit d722730 into main Aug 14, 2026
6 checks passed
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