Skip to content

Overview planner under-estimates section cost (SECTION_COST_VS_PROBE measured 1.9x, actual ~3.4x) #665

Description

@bgmcmullen

Summary

SECTION_COST_VS_PROBE in src/core/query/overview.js is calibrated at
1.9x, measured against a 48k-row cache. On a 91k-row cache it measures
~3.4x. The planner therefore believes a window costs roughly half what it
actually costs, and picks one it cannot finish inside OVERVIEW_TIME_BUDGET_MS.

The user-visible symptoms:

  • hyp init prints Stopped here to keep setup moving - the repos and tools sections did not finish. The wizard's deadline is a documented backstop
    ("it should only fire when the measured plan was wrong ... If it starts
    firing routinely, the fix is the planner's calibration, not a longer
    deadline" - first_look.js), and it was firing routinely.
  • hyp query overview has no deadline, so it does not truncate; it just runs
    long. Measured 8.5s against a 5s budget.

Measurement

Cache: 91,098 rows across 55 active days (macOS, warm).

statement wall time (CLI, includes ~400ms node startup)
probe 773 ms
models 1,665 ms
daily 1,633 ms
repos 1,620 ms
tools 1,049 ms

For the window the planner actually chose (18 days, 71,582 rows):

  • predicted section cost, as coded: ~2,210 ms
  • actual section cost: ~5,000 ms

The window choice is consistent with an in-process probe of ~700ms: that
yields timeCap ≈ 73,600 rows, and the day walk stops at 18 days / 71,582
rows. So the time cap is the binding constraint here, computed from a cost
model that is ~1.8x optimistic.

Why the ratio drifted

The constant's own comment records the original measurement: "Measured at 48k
rows: ~0.27s against ~0.50s, so a section is ~1.9x the probe." Two things
changed underneath it:

  1. The probe reads one narrow column (date), which columnar storage prunes
    to almost nothing, while the sections read attributes - large JSON blobs
    with json_extract/cast over them. The gap widens with payload size, so
    a ratio measured on a smaller/lighter cache under-predicts a bigger one.
  2. Three of the four sections now also run count(distinct session_id), which
    was not part of the 48k-row measurement.

Second defect: the section count is hardcoded

rowsAffordable charges for OVERVIEW_SECTIONS.length (4) sections
unconditionally:

const sectionCount = OVERVIEW_SECTIONS.length
return Math.floor(budgetMs / (perRowMs * SECTION_COST_VS_PROBE * sectionCount))

but collectOverview accepts a sections subset, and since LLP 0195
#wizard-sections the wizard runs only ['models', 'daily']. The planner now
over-charges the wizard by 2x, which partly cancels the 1.8x under-charge
above - by accident, not by design, and only for that one caller. The cost
model should take the sections it is planning for.

Suggested fix

  1. Re-measure SECTION_COST_VS_PROBE across a few cache sizes (not one), and
    update both the constant and the comment recording what it was measured
    against.
  2. Pass the requested sections into rowsAffordable so the estimate matches
    the work.
  3. Consider whether the ratio belongs as a single constant at all, given it
    varies with payload size. A per-section weight, or a floor on the estimate,
    may be more honest than one number that is right at one cache size.

Not in scope: raising OVERVIEW_TIME_BUDGET_MS or the wizard's deadline.
first_look.js rules that out explicitly, and it would make every install
wait longer to paper over a bad estimate.

Open question

With a ~700ms probe and the 18-day window, the predicted-vs-actual arithmetic
above puts the sections at ~4.7-5.7s, still inside the wizard's 8s deadline -
yet the truncation message fired. Either the sections are slower in the
wizard's process than when timed through hyp query sql, or something else
contributes to the elapsed time the deadline sees. Worth confirming with a
wizard.first_look span from a real run before settling on the fix, since it
may indicate a second cost the model does not account for at all.

References

  • src/core/query/overview.js: SECTION_COST_VS_PROBE, rowsAffordable,
    chooseOverviewWindow, OVERVIEW_TIME_BUDGET_MS
  • src/core/cli/wizard/first_look.js: FIRST_LOOK_BUDGET_MS and the
    backstop-not-mechanism rule
  • LLP 0135 #window, #overrun - the planner's design
  • LLP 0195 #wizard-sections - the trim that exposed the hardcoded section count

Metadata

Metadata

Assignees

No one assigned

    Labels

    neutral:fixDelegate this issue to neutral for an autonomous fix attempt (reproduce -> fix -> PR)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions