feat(remote): say why a statistics refresh was skipped - #230
Merged
Conversation
The statistics refresh runs on every 60s schema poll and has four ways to decline, all of them silent. A project that had not captured statistics for five days looked identical in the logs to one refreshing on schedule, and the logs from that window cannot tell us which guard was responsible. Each early return now reports itself: no baseline, a refresh already in flight and for how long, a backoff and how much is left, or no drift and how long until the daily floor. The no-drift line carries the near miss. detectDrift returns the table that came closest to the ratio without reaching it, because a refresh that never fires reads the same whether the database is quiet or one table is sitting just under the threshold, and those want opposite fixes. On the project that went quiet, the closest table was at 42% of a 50% ratio. Reporting every poll would bury the log, and reporting only changes would hide a steady state from any window that opens after it settled, which is the position the five-day gap left us in. Lines are emitted on change and then on a 30-minute heartbeat. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.

Query Doctor — 6 successful checks
More details via MCP → get_ci_run({ runId: "019ff79b-5dc4-7eec-8ef0-c8fc3249bf31" }) · 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #229. Same shape: observability only, no behaviour change.
Goal
The
siteproject went five days without a production-statistics capture. #229 made the relay connection explain itself; this makes the statistics refresh do the same, so the next gap is diagnosable from one poll cycle instead of from inference.What
Before:
refreshStatsIfStaleruns on every 60-second schema poll and has four ways to decline, all silent. An analyzer that had not captured statistics for days produced exactly the same log output as one refreshing on schedule.After: each early return says which guard stopped it, and carries the number that matters.
How
Read
src/remote/stats-drift.tsfirst.detectDrift's not-drifted verdict now carriesclosest, the table that came nearest the ratio without reaching it. A refresh that never fires reads the same from outside whether the database is quiet or one table is sitting just under the threshold, and those call for opposite fixes. On the project that went quiet the closest table was at 42% of a 50% ratio, which is the whole explanation and was invisible.Then
refreshStatsIfStaleinsrc/remote/remote.ts. The first guard is split so "no baseline" and "already refreshing" report separately, andrefreshingSincelets the second one say how long — a refresh that never settles wedges that flag for the life of the process, which is a real failure mode with no current symptom.noteSkippedRefreshhandles volume. Reporting every poll would add 1,440 lines a day and bury the log, sincelog.debugis not level-gated here. Reporting only changes would hide a steady state from any window that opens after it settled, which is the position the five-day gap left us in. So: on change, then on a 30-minute heartbeat.Tests
Two cases in
src/remote/stats-drift.test.tscoverclosest— the table nearest the ratio is named with how far it moved, and no table is named when none was eligible for Size Drift.The log lines themselves are not asserted. They have no callers and no return value, and a test that pins their wording would only restate the string.
Full suite passes: 442 tests across 43 files.
npm run typecheckandnpm run buildare clean.What to look for once deployed
The 2026-08-11 logs show a container that had been up four days, with the daily floor overdue by three, producing no refresh at all. Whether that was a wedged in-flight refresh, an unset baseline, or a schema poll that never fired is still open, and these lines separate the three.