Analysis: bound the freshness query, let window sums skip chunks, share the Overview's catalog
ci / build-test (push) Successful in 2m34s
ci / build-test (push) Successful in 2m34s
Three things the 1,000-meter x 10-year measurement found, each proved by EXPLAIN or a statement count before and after. No tally moves. Freshness had two jobs in one unbounded query. The mark -- when a meter last delivered -- is now stored on meter_rollup_state and maintained by every recompute, with a one-pass backfill in the migration, so an import-only meter keeps its years-old last activity without reading a single raw row. The rhythm that decides stale versus live is sampled inside a 90-day window and only for meters that actually have a live source; a source silent for longer than that is re-read unbounded, so it is still called stale by its own rhythm rather than by a default. The portfolio query went from 13.8 ms planning plus 36.1 ms execution across all 123 reading chunks to 0.58 plus 0.44 ms across four. Window sums took their time bounds only from the unnest join, so the planner could not exclude chunks: a 1,960-window case scanned 1.39 M rows in parallel and spilled a 45 MB sort. Repeating the overall min and max as constants makes it five chunks and nested-loop index scans, 121.5 ms to 8.7 ms. The Overview read the catalog three times, once for the quantities and once for each of its two bills. One context and one catalog snapshot now feed all three: 31 statements per load to 23. The final timings on an idle machine are in docs/ANALYSIS_REPORT.md: the brief's target request (100 meters, ten years, monthly) is 286 ms against two seconds, and a startup rebuild of 1,000 meters is 279 s.
This commit is contained in:
@@ -8,6 +8,7 @@ The note was kept current through Phase 5:
|
||||
- §11: amendments from the Phase 1 module review.
|
||||
- §12: amendments from the acceptance review.
|
||||
- §13: decisions recorded with the final documentation.
|
||||
- §14: amendments from the performance measurement.
|
||||
- §9 and §10: extended with what the implementation measured and changed.
|
||||
|
||||
The outcome is in [ANALYSIS_REPORT.md](ANALYSIS_REPORT.md), the user-facing changes in
|
||||
@@ -708,3 +709,32 @@ figure.
|
||||
keep the existing detour and its draft.
|
||||
- The theme defaults to dark when no `mv-theme` cookie is set.
|
||||
- The Calculation tab words a calculation problem as the attention list does, one wording per `VirtualProblemKind`.
|
||||
|
||||
## 14. Amendments from the performance measurement
|
||||
|
||||
The measurement of brief §9.10 / D-56 on a synthetic 1,000-meter × 10-year instance named three costs that grow with
|
||||
history rather than with what a page asks for. They are fixed here. No golden bill, reconciliation figure or displayed
|
||||
value changes; only what a request reads does.
|
||||
|
||||
- **A-40 What a request reads (D-15, D-18, D-56).**
|
||||
- **The freshness mark is stored, not searched.** `meter_rollup_state` gains `last_reading_at`: the stamp of the
|
||||
meter's latest raw reading, written by the recompute that every write path already runs. D-18 is unchanged — the
|
||||
last reading or event time is still the mark — but a request no longer queries `reading` to find it, so an
|
||||
import-only meter's mark stays exactly as old as its data without planning across a decade of raw chunks. The
|
||||
migration backfills the column in one pass, so nothing waits for a rebuild.
|
||||
- **Only a live source's rhythm is sampled, and only from the recent past.** The median of D-18 is consulted for a
|
||||
meter with a live source, so the reading times are read for those meters alone, bounded by
|
||||
`FreshnessRules.RecentWindow` (90 days) — the bound is what lets PostgreSQL exclude the older chunks at plan time.
|
||||
A live meter that delivered nothing inside the window has no rhythm there; those few meters are read again over
|
||||
their whole history, so a long-silent source is still called stale by its own rhythm. An instance without any live
|
||||
source reads `reading` not at all.
|
||||
- **The window-sum statement carries its overall bounds.** Its windows arrive through an `unnest` join, so their
|
||||
bounds are columns and exclude no chunk. The minimum start and maximum end of the window set are repeated as
|
||||
constants in the `WHERE` clause. No row outside them can match any window, so no tally changes; with enough
|
||||
windows it is the difference between an indexed probe and a parallel scan of the whole hypertable with a sort
|
||||
spilling to disk.
|
||||
- **One Overview load holds one catalog.** The page's quantities, its bill and its comparison's bill are three
|
||||
figures of one period. `DashboardService.GetOverviewAsync` loads the meters, tanks, links and rollup states once
|
||||
and passes that snapshot to all three (`CostReader.ReadAsync(db, catalog, …)`), so every figure answers from the
|
||||
same snapshot and the load is not repeated. The cards, the change table and the composition are derived from those
|
||||
results and are never priced again. `OverviewReadBudgetTests` asserts the statement count of one load.
|
||||
|
||||
Reference in New Issue
Block a user