planner/optimizer — candidate_gen.rs::window_candidates only ever generates Sliding candidates with W = range_a exactly (line ~136), queried via Direct (no merge/subtract, since n_windows is always 1 for Sliding).
Missing candidate class. A Sliding accumulator with W < range_a (say W = range_a / k for integer k) refreshes every slide_interval, just like a full-width one — so you can merge/subtract k of these partial-width staggered readings to cover the full range_a, at lower ingest cost than a full-width Sliding window (fewer concurrent accumulators: ⌈W/S⌉ instead of ⌈range_a/S⌉), while still refreshing far more often than an equivalent Tumbling+Merge setup (whose freshness is bounded by its window width, not by the slide interval).
Concrete example: range_a = 10m, t_repeat = 30s.
Tumbling, w=30s: cheap ingest (1 concurrent accumulator + 20 retained), but a 20-way merge per query.
Sliding, size=10m, slide=30s: zero query cost (Direct), but ⌈600/30⌉ = 20 concurrent accumulators — the most expensive ingest option.
Sliding, size=5m, slide=30s, merging 2 staggered 5m readings: ⌈300/30⌉ = 10 concurrent accumulators (half the ingest cost of the previous option) plus 1 retained reading, with only a 2-way merge per query.
That third option is a real point in the cost/freshness tradeoff space that the current grid search never generates. Implementing it needs:
window_candidates to also emit Sliding candidates with W = range_a / k for valid k, alongside the existing W = range_a candidates.
determine_query_method to allow Merge/Subtract for Sliding with n_windows > 1 (currently guarded by debug_assert_eq!(window_type, WindowType::Tumbling) whenever n_windows > 1, since this case never arises today).
cost_model.rs::ingest_cost's mem_retain term, which is hardcoded to 0.0 for Sliding ("already counted in mem_active's concurrent windows") — that assumption breaks once a Sliding candidate needs to retain a prior completed reading to merge against the current one.
Separately, a likely-backwards guard. window_candidates only generates any Sliding candidate at all when range_a <= min_t_repeat_secs (line ~133) — i.e. it gates on the full window width W (since W = range_a today) being within the freshness bound. But the whole point of a sliding window is that fresh answers arrive every slide_interval, not every W seconds — once warmed up, freshness should be bounded by the slide interval, not by W. As written, this guard looks like it would incorrectly reject exactly the case Sliding is meant for: a wide window (W large) refreshed frequently (slide small). Worth re-deriving what the correct guard should be once the above grid-search extension is scoped, since both pieces touch the same freshness reasoning.
Found during code review of PR #407.
planner/optimizer —
candidate_gen.rs::window_candidatesonly ever generatesSlidingcandidates withW = range_aexactly (line ~136), queried viaDirect(no merge/subtract, sincen_windowsis always 1 for Sliding).Missing candidate class. A
Slidingaccumulator withW < range_a(sayW = range_a / kfor integerk) refreshes everyslide_interval, just like a full-width one — so you can merge/subtractkof these partial-width staggered readings to cover the fullrange_a, at lower ingest cost than a full-width Sliding window (fewer concurrent accumulators:⌈W/S⌉instead of⌈range_a/S⌉), while still refreshing far more often than an equivalent Tumbling+Merge setup (whose freshness is bounded by its window width, not by the slide interval).Concrete example:
range_a = 10m,t_repeat = 30s.Tumbling, w=30s: cheap ingest (1 concurrent accumulator + 20 retained), but a 20-way merge per query.Sliding, size=10m, slide=30s: zero query cost (Direct), but⌈600/30⌉ = 20concurrent accumulators — the most expensive ingest option.Sliding, size=5m, slide=30s, merging 2 staggered 5m readings:⌈300/30⌉ = 10concurrent accumulators (half the ingest cost of the previous option) plus 1 retained reading, with only a 2-way merge per query.That third option is a real point in the cost/freshness tradeoff space that the current grid search never generates. Implementing it needs:
window_candidatesto also emitSlidingcandidates withW = range_a / kfor validk, alongside the existingW = range_acandidates.determine_query_methodto allowMerge/SubtractforSlidingwithn_windows > 1(currently guarded bydebug_assert_eq!(window_type, WindowType::Tumbling)whenevern_windows > 1, since this case never arises today).cost_model.rs::ingest_cost'smem_retainterm, which is hardcoded to0.0forSliding("already counted in mem_active's concurrent windows") — that assumption breaks once aSlidingcandidate needs to retain a prior completed reading to merge against the current one.Separately, a likely-backwards guard.
window_candidatesonly generates anySlidingcandidate at all whenrange_a <= min_t_repeat_secs(line ~133) — i.e. it gates on the full window widthW(sinceW = range_atoday) being within the freshness bound. But the whole point of a sliding window is that fresh answers arrive everyslide_interval, not everyWseconds — once warmed up, freshness should be bounded by the slide interval, not byW. As written, this guard looks like it would incorrectly reject exactly the caseSlidingis meant for: a wide window (Wlarge) refreshed frequently (slidesmall). Worth re-deriving what the correct guard should be once the above grid-search extension is scoped, since both pieces touch the same freshness reasoning.Found during code review of PR #407.