Skip to content

refactor(planner): plan without deadlines over every tumbling-window width (#1185, part 2) - #1198

Merged
zzylol merged 6 commits into
mainfrom
feat/1185-no-planning-deadlines
Nov 3, 2026
Merged

zzylol merged 6 commits into
mainfrom
feat/1185-no-planning-deadlines

Conversation

@zzylol

@zzylol zzylol commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Why

Part 2 of #1185 (part 1: #1188). The planner still ran on a wall-clock budget upstream of Stage 3. The facade gave planning 30 s, split into a seed round over a few tumbling-window widths and growing intermediate rounds, and kept the best round's plan. Pass 1, Pass 2 and Stage 2 checked deadlines too. So the widths searched, and with them the selected plan, depended on machine speed and load. That is what made the wall-clock-ignored tests (markers #1067/#1175/#1180) flaky.

What

How

  • Facade (crates/planner/src/run.rs): run_planning runs Stage 0 and Pass 1, then Pass 2 → Stage 2 → Stage 3 once, then builds the selected plan with AssignmentRealizer::realize.
    • The round loop and its types are deleted: Round, RoundEnd, RoundOutcome and plan_round.
    • So are the round constants, calibration_is_frozen and has_splittable_window.
    • PlanningRoundReport is folded into PlanningReport.
  • Pass 2:
    • Deleted: the round state (Pass2RoundState), the width domain and frontier (TumblingWindowWidthDomain, TumblingWindowWidthFrontier) and check_generation_deadline.
    • Local window composition now enumerates tumbling_window_width_domain(grids), the divisor set the old Complete round used. It still skips candidates whose signature already exists.
  • Stage 2, types, executor: the deadline parameters are deleted. No caller ever set them.
    • This removes flatten_before, compile_before and the set_retention deadline, along with their *DeadlineExceeded errors.
    • It also removes CandidateGenerationTimeout, PerComputationLogicalMemoCandidatesError::Timeout and Pass2RuleError::DeadlineExceeded.

Key code interfaces added / modified

crates/planner/src/run.rs

/// Settings of one planning run (stays).
pub struct PlanSearchSettings {
    /// Keep Pass 1's output for inspection (stage_pipeline, the viewer). Default off.
    pub inspect: bool,            // unchanged
    // pub time_limit_ms: u64,    // removed: no planning budget
}

/// Why the facade selected no plan (stays).
pub enum SelectionError {
    /// Bad input: the deployment's capabilities contradict the data workload.
    InvalidDeploymentCapabilities(DeploymentCapabilitiesError), // unchanged
    /// Pass 1's memo candidates or Pass 2 failed.
    LogicalASAPCandidates(String),     // unchanged
    /// Stage 2 failed.
    PhysicalASAPCandidates(String),    // unchanged
    /// Stage 3 selected no plan.
    Selection(ErrorSelectingPlan),     // unchanged
    /// Stage 2 could not build the plan Stage 3 selected.
    Realization(PhysicalCandidateError), // unchanged
    // DeadlineReached,                // removed
}

/// One run's counts and stage times (stays; absorbs PlanningRoundReport, which is removed).
pub struct PlanningReport {
    /// Stage 0 and Pass 1, in milliseconds.
    pub pass1_ms: f64,                          // unchanged
    /// Pass 2's memo groups.
    pub memo_groups: usize,                     // was per round
    /// Pass 2's candidates.
    pub logical_candidates: usize,              // was per round
    /// Stage 2's operator nodes.
    pub operator_nodes: usize,                  // was per round
    /// Stage 2's physical candidates.
    pub physical_candidates: usize,             // was per round
    /// Stage 2's producer links over every candidate input.
    pub physical_producer_links: usize,         // was per round
    /// Stage 2's pruned candidates.
    pub stage2_pruned_candidates: usize,        // was per round
    /// Pass 2, Stage 2 and Stage 3 wall time, in milliseconds.
    pub pass2_ms: f64,                          // was per round
    pub stage2_ms: f64,                         // was per round
    pub stage3_ms: f64,                         // was per round
    /// Stage 3's search report, when it selected a plan.
    pub search_report: Option<SearchReport>,    // was per round
    // pub rounds: Vec<PlanningRoundReport>,          // removed
    // pub selected_round: Option<usize>,             // removed
    // pub tumbling_window_widths_pending: bool,      // removed
    // pub time_limit_ms: u64,                        // removed
}

pass2/logical_memo_candidates.rs, catalog/mod.rs

/// Pass 2: every tumbling-window width, in one pass.
pub fn generate_logical_asap_memo_candidates(
    pass1: PerComputationLogicalMemoCandidates,
    entries: &[QueryWorkloadEntry],
) -> Result<LogicalASAPMemoCandidates, Pass2RuleError>; // was (round state, domain, deadline) -> LogicalASAPMemoCandidatesError

/// Stage 2's catalog over Pass 2's memo.
pub fn generate_physical_asap_memo_candidates(
    logical: &LogicalASAPMemoCandidates,
    entries: &[QueryWorkloadEntry],
    data: &DataWorkload,
    query_time_results_retained: bool,
    raw_data_retained: bool,
    // deadline: Option<Instant>,   // removed
) -> Result<PhysicalASAPMemoCandidates, ErrorGeneratingPhysicalASAPMemoCandidates>;

Removed, with no replacement:

  • executor::compile_before and Error::CompilationDeadlineExceeded
  • flatten_before and FlattenDeadlineExceeded
  • PhysicalASAPCompilationError
  • TumblingWindowChainExpansionError
  • InstallableDagError::DeadlineExceeded
  • CandidateGenerationTimeout
  • ErrorSelectingPlan::EvaluationDeadlineExceeded
  • Selector::offer_before
  • CLI: stage_pipeline --milp-time-limit-s

Usage:

# Plan the two-query sum_over_time fixture. There is no time flag any more.
# Pass 2 emits every width dividing the readers' grids;
# Stage 3's MILP runs to proven optimality.
stage_pipeline --workload <workload.json> --out run.json
# run.json: planning_report has one set of counts (no "rounds");
# the selected plan costs 5.94e-6 /s, the same as before this PR.

Major algorithm changes

Tumbling-window-width search

  • Problem: the facade searched widths in rounds under a 30 s budget. On RangeHeavy T8+T5 it ended after 3 rounds with widths still unsearched, so the plan depended on how many rounds fit on the machine.
  • Before:
    1. A seed round over the seed grids' widths.
    2. Intermediate rounds over a growing frontier, each running Pass 2 → Stage 2 → Stage 3 on the delta.
    3. Stop at the deadline or when the domain is complete; keep the cheapest round's plan.
  • After: one Pass 2 over tumbling_window_width_domain(grids) (every divisor width), then Stage 2 and Stage 3 once.
  • Correctness: the catalog is a superset of every round's, and Stage 3 is exact (fix(plan-selection): run the group selection MILP to proven optimality and cost exactly (#1185, part 1) #1188). So the selected cost is never higher than before, and it is the optimum over the full domain. Corpus and sketch snapshots are byte-identical, and the measured costs match.
  • Complexity: catalog size grows with the number of divisor widths (for example, 48 for a 60 s grid) times the candidates per width. Measured (release):
Workload Before After Cost/s before → after
RangeHeavy T8+T5 1h (stage_pipeline) 117 s, widths left unsearched 2257 s (Stage 3: warm start + build ≈1507 s, HiGHS ≈749 s, 114k vars) 6.4822e-6 → 6.4822e-6
runtime_install two-query fixture < 60 s (debug) 155 s 5.94e-6 → 5.94e-6
planner-layering-1 2.1 s 0.2 s unchanged

Before this PR

Planning stopped after 30 s. On large workloads it returned the best plan over the widths it had reached, which varied with machine load. Tests that depended on the full domain were ignored as wall-clock flakes.

After this PR

Planning is deterministic: it returns the optimum over every tumbling-window width for any machine speed, and the time it takes is the cost of that search. The wall-clock markers #1067/#1175/#1180 are gone from the tests.

Verification

Limitations

Closes #1185.

🤖 Generated with Claude Code

zzylol and others added 6 commits November 3, 2026 02:45
…ling-window width (#1185, part 2)

The facade's planning budget, the seed and expanding tumbling-window-width
rounds, and the Pass 1, Pass 2 and Stage 2 generation deadlines are removed.
Planning runs Pass 2 once over the complete width domain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1185, part 2)

Types and executor exports, retention and compilation took an optional
deadline that no caller set; the devtools, snapshots, dag-viewer and scripts
lose the time-limit flag and the round report fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…; docs without planning deadlines (#1185, part 2)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r other reasons (#1185, part 2)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… part 2)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…udget (#1197)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@zzylol zzylol added optimizer Workload optimization and plan selection search Candidate-plan search space and algorithms refactor labels Nov 3, 2026
@zzylol
zzylol merged commit 1b8289a into main Nov 3, 2026
4 checks passed
@zzylol
zzylol deleted the feat/1185-no-planning-deadlines branch November 3, 2026 06:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

optimizer Workload optimization and plan selection refactor search Candidate-plan search space and algorithms

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove unnecessary deadlines and caps in the group selection MILP

1 participant