Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
fe5cc51
refactor: share execution demand accounting with joint candidate plan…
zzylol Oct 24, 2026
2f46513
refactor: evaluate summary materialization for selected readers
zzylol Oct 24, 2026
b1d1691
feat: select physical configurations with validated cost refinement
zzylol Oct 24, 2026
484cf4c
fix: charge request overhead for literal queries and CPU capacity
zzylol Oct 24, 2026
e200188
feat: report candidate model, solve, and validation metrics
zzylol Oct 24, 2026
de6eefa
feat: expose candidate-connection planning with scoped search reports
zzylol Oct 24, 2026
8494098
refactor: move shared candidate evaluation out of legacy search
zzylol Oct 24, 2026
b2c0349
fix: exclude unavailable query-time raw scans before candidate search
zzylol Oct 24, 2026
c81cebf
feat: reuse local pane rules in candidate planning and report generat…
zzylol Oct 24, 2026
64e4081
fix: assign visible IDs to validated joint plans
zzylol Oct 24, 2026
c9ecb52
fix: preserve ranked-row contracts when lowering fused and top-k cand…
zzylol Oct 24, 2026
16de169
feat: share concrete pane states and seed joint search with validated…
zzylol Oct 24, 2026
e3888d9
docs: record capability parity gates and ASAPQuery workload measurements
zzylol Oct 24, 2026
5f424b8
feat: share pane states across compatible scan-label projections
zzylol Oct 24, 2026
5448dc7
feat: migrate certified query accuracy pruning to candidate search
zzylol Oct 24, 2026
c196e42
feat: add certified ingestion cost bounds to candidate selection
zzylol Oct 24, 2026
4f9a6ca
docs: measure migrated sharing pruning and ingestion bounds
zzylol Oct 24, 2026
63dc0e5
docs: require scope-based module organization at planner cutover
zzylol Oct 24, 2026
16c9df9
fix: retain feasible solver proposals without overstating cost bounds
zzylol Oct 24, 2026
5badfbb
docs: identify mixed responsibilities for final module cleanup
zzylol Oct 24, 2026
632ccc2
feat: expand shared state capacities and pane horizons in logical memo
zzylol Oct 24, 2026
3521b7e
feat: restore shared non-summary query materialization
zzylol Oct 24, 2026
e25a435
feat: reuse conservative per-query pruning before memo expansion
zzylol Oct 24, 2026
3e39b20
feat: complete joint candidate sharing and objective selection
zzylol Oct 24, 2026
73476c8
feat: replace legacy planning with bounded candidate-connection search
zzylol Oct 24, 2026
99274c9
fix: reject unexecutable raw window candidates before pane expansion
zzylol Oct 24, 2026
bf03890
fix: preserve physical contracts and feasible workload plans
zzylol Oct 24, 2026
e3dfbc8
perf: refine seed materialization with bounded MILP proposals
zzylol Oct 24, 2026
0474b69
test: enforce raw-source seed legality under both accuracy policies
zzylol Oct 24, 2026
ef26d3e
fix(planner): bound compilation and validate pane edges linearly
zzylol Oct 24, 2026
7df8247
refactor(planner): remove retired Cartesian workload enumeration
zzylol Oct 24, 2026
2743efe
test(planner): verify explicit rules independently of search archives
zzylol Oct 24, 2026
bae908c
fix(planner): preserve small-cost distinctions in MILP scaling
zzylol Oct 24, 2026
bc9f58c
test: migrate planner examples to explicit rule fixtures
zzylol Oct 24, 2026
f31c4ad
fix(cost): reject overflowing physical state configurations
zzylol Oct 24, 2026
c47648f
Make disjoint pane coverage union scale with sorted intervals
zzylol Oct 24, 2026
30677d3
test(planner): validate sharing rewrites and selected pane coverage
zzylol Oct 24, 2026
a7e0b9d
fix(accuracy): gate interpolated DDS quantiles on source domain
zzylol Oct 24, 2026
1856935
test(planner): make signed DDS and compile rejection fixtures explicit
zzylol Oct 24, 2026
2fa98f0
test(planner): compare facade plans independently of search timings
zzylol Oct 24, 2026
0b504e5
Honor planning deadlines while flattening and exporting physical DAGs
zzylol Oct 24, 2026
e5cc91d
fix(planner): retain budget reports without a feasible incumbent
zzylol Oct 24, 2026
9fff8ac
fix(planner): seed composed DDS budgets before query validation
zzylol Oct 24, 2026
52781d7
test: pin valid DDS and pane fixtures independently of plan ranking
zzylol Oct 24, 2026
185275f
docs: record validated joint planner workload results and limits
zzylol Oct 24, 2026
e909928
docs(planner): describe the active candidate connection search
zzylol Oct 24, 2026
2c0b27e
test(planner): make composed summary rejection fixtures deterministic
zzylol Oct 24, 2026
c35fe3f
fix(viewer): display current candidate-connection search reports
zzylol Oct 24, 2026
761a019
docs: record complete regression and viewer coverage
zzylol Oct 24, 2026
a69ea1a
Compress pane candidate search and progressively expand legal widths …
zzylol Oct 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 4 additions & 5 deletions crates/devtools/src/bin/planner_fuzz.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// cargo run -p asap-devtools --bin planner_fuzz -- \
// --seed 1 --cases 200 --time-budget-s 600 --out /tmp/planner-fuzz
// cargo run -p asap-devtools --bin planner_fuzz -- \
// --seed 2 --cases 50 --relations a,b,tighter-accuracy --search memo-brute-force \
// --seed 2 --cases 50 --relations a,b,tighter-accuracy --search candidate-connections \
// --seed-workload eval/rangeheavy.workload.json --out out
//
// Metamorphic fuzz testing for missed reuse and sharing
Expand All @@ -15,7 +15,7 @@
// is filed.
//
// `--relations` takes relation names or the letters a–k (default: all);
// `--search` is `legacy` (the default search), `memo-brute-force` or
// `--search` is `candidate-connections` (the default search) or
// `exhaustive`; `--ignore-accuracy-guarantees` plans as `stage_pipeline`
// does by default (guarantees are enforced here by default, as
// `plan_installable` does). A relation is violated when it misses by more
Expand All @@ -33,7 +33,7 @@ use asap_devtools::planner_fuzz::report::{run_campaign, serve, Campaign, WorkerR
use asap_devtools::planner_fuzz::{read_seed_file, FuzzSettings, SearchKind};

const USAGE: &str = "usage: planner_fuzz --seed <u64> --cases <n> --time-budget-s <s> \
[--relations <name|letter>,...] [--search legacy|memo-brute-force|exhaustive] \
[--relations <name|letter>,...] [--search candidate-connections|exhaustive] \
[--seed-workload <file>]... [--ignore-accuracy-guarantees] [--relative-tolerance <f64>] \
[--absolute-tolerance <f64>] [--max-shrink-attempts <n>] [--case-timeout-s <s>] --out <dir>";

Expand Down Expand Up @@ -93,8 +93,7 @@ fn run(args: Vec<String>) -> Result<(), String> {
}
"--search" => {
settings.search = match value()?.as_str() {
"legacy" | "default" => SearchKind::Legacy,
"memo-brute-force" => SearchKind::MemoBruteForce,
"candidate-connections" => SearchKind::CandidateConnections,
"exhaustive" => SearchKind::Exhaustive,
other => return Err(format!("unknown search {other}")),
}
Expand Down
145 changes: 49 additions & 96 deletions crates/devtools/src/bin/stage_pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,35 +22,12 @@
// --sql "SELECT service, COUNT(*) FROM metrics GROUP BY service" --out run.json
//
// Writes an `asap-stage-pipeline/v1` document (tools/dag-viewer) with the
// four planner stages (#509 MVP), searched with the default search, the one
// `plan_installable` runs (`Stage1ToStage3Search::default`: each query's own
// candidates are checked first, a whole-workload choice that picks a
// discarded one is not composed, independent components are searched
// separately, and without raw data retained the candidates that scan it at
// query time are skipped), over the models `plan_installable` plans with:
// the stage crates' built-in models, the declared capabilities with the
// executor's summary families when none are declared, and, for a PromQL
// workload, the executor's compile check (it does not compile SQL table
// scans, so a SQL workload plans without it). It keeps only the best candidate, so the document
// holds the selected Stage 1 and Stage 2 candidates, the rejections Stage 3
// recorded (the first of each cause, under `search_rejected` since their
// candidates are not kept) and the search's report; `search` is
// `"default"`. `--exhaustive` instead composes and prices every candidate
// (the search the default one is tested against, for small workloads and
// comparison) and the document lists them; `search` is `"exhaustive"`.
// `--search legacy|memo-brute-force|memo-milp` picks the default search's
// algorithm (`SearchAlgorithm`): `legacy` (the default, `search` is
// `"default"`), the memo over logical sub-expressions searched by brute
// force (the reference), or solved as a MILP; `search` names it, and the
// search report says which ran (`algorithm`, omitted for legacy) and,
// when the legacy search ran instead, why (`routed_to_legacy`). Not with
// `--exhaustive`. `--milp-time-limit-s <f64>` limits the MILP search: at
// the limit it selects its best plan so far and reports
// `time_limit_reached` with the gap to its bound. The memo-MILP search's
// report also has `milp.queries_alone_cost` (each query planned alone)
// and `memo_plan`: every memo group with its chosen implementation
// candidate, and the coupling sites at their chosen thresholds with their
// reader groups, which the viewer shows as the plan's shared units.
// four planner stages, using the joint candidate-connection engine. The default
// document keeps the selected plan; `--exhaustive` archives evaluated plans
// and rejections from the same finite catalog, not an independent optimizer.
// `--search candidate-connections` explicitly names this engine.
// `--milp-time-limit-s` bounds planning in either mode; a timeout reports a
// partial result, never an optimality claim for unexplored candidates.
// Unlike `plan_installable`'s default settings, Stage 3 ignores accuracy
// guarantees unless `--enforce-accuracy-guarantees` is given (below);
// `accuracy_guarantees` is `"ignored"` or `"enforced"`.
Expand Down Expand Up @@ -136,14 +113,9 @@
// When Pass 1 fails, the document still has the Stage 0 DAG; Stage 1 has
// `error` and no candidates, and nothing is selected.
//
// Under `--exhaustive`, a workload with more than `--max-combinations`
// (default 10^4) Stage 1 candidates, Pass 2's included, is refused as soon
// as enumeration passes the limit, and so is one whose Stage 2 physical
// candidates (one per materialization assignment, counted before they are
// built) pass it: the run exits with status 3 and the limit, and writes no
// document. The default search has no limit.
// Planning never caps or truncates the candidates it ranks; only the display
// does (`--max-displayed`).
// Under `--exhaustive`, `--max-combinations` (default 10^4) bounds evaluated
// assignments. The report distinguishes catalog exhaustion from budget limits.
// Display truncation (`--max-displayed`) is separate from the planning budgets.
//
// `--workload` may repeat, so one run plans queries in several languages
// (e.g. a PromQL file and a SQL file) together. Each file is lowered by
Expand Down Expand Up @@ -246,8 +218,8 @@ use asap_devtools::planner_layering_examples::{
example1, example2, pattern_a, pattern_b, PatternARecurrence, SHARED_ROWS_PER_SECOND,
};
use asap_devtools::planning_stages::{
lower_promql_roots, lower_sql_roots, run_planning_stages_over, run_search_over, sql_catalog,
PlanningStagesError, PlanningStagesRun,
lower_promql_roots, lower_sql_roots, run_search_over, sql_catalog, PlanningStagesError,
PlanningStagesRun,
};
use asap_devtools::SqlCatalog;
use asap_frontend_promql::{counter_function_over_gauge_warnings, promql_metric_names};
Expand Down Expand Up @@ -276,20 +248,16 @@ const USAGE: &str =
[--name <name>]... [--table <json>...] [--max-error <f64> --failure-probability <f64> --accuracy-metric <metric>] [--sample-interval-ms <u64>] [--series-count <u64>] [--rows-per-second <f64>] [--rows-held <u64>])) \
[--cost-model usage-over-time|aws-fargate-us-east-1|provisioned-capacity|cluster-capacity [--cluster-memory-bytes <n> \
--cluster-disk-bytes <n> [--cluster-cpu-cores <n>] --cluster-objective lowest-latency|least-memory|pareto-front]] \
[--plan-selection-settings <file>] [--enforce-accuracy-guarantees] [--deployment-capabilities <json>] [--exhaustive [--max-combinations <n>] | --search legacy|memo-brute-force|memo-milp [--milp-time-limit-s <f64>]] [--max-displayed <n>] --out <file>\n\
--search legacy is the default search (the document's \"search\" is \"default\").";
[--plan-selection-settings <file>] [--enforce-accuracy-guarantees] [--deployment-capabilities <json>] [--exhaustive [--max-combinations <n>] | --search candidate-connections] [--milp-time-limit-s <f64>] [--max-displayed <n>] --out <file>\n\
--search candidate-connections is the default search.";

/// The default `--max-combinations`: the most Stage 1 candidates (Pass 2's
/// included) an exhaustive run enumerates.
/// Default evaluator-assignment budget for an exhaustive archive run.
const MAX_COMBINATIONS: usize = 10_000;

/// Why a run wrote no document.
enum Failure {
/// Bad arguments or a query that does not plan (exit status 2).
Usage(String),
/// More Stage 1 or Stage 2 candidates than `--max-combinations` under
/// `--exhaustive` (exit status 3).
TooManyCandidates(PlanningStagesError),
}

impl From<String> for Failure {
Expand All @@ -311,15 +279,6 @@ fn main() {
eprintln!("stage_pipeline: {message}\n{USAGE}");
std::process::exit(2);
}
Err(Failure::TooManyCandidates(error)) => {
// Only the exhaustive search (`--exhaustive`) counts against
// the limit.
eprintln!(
"stage_pipeline: {error}; --exhaustive composes every candidate, so raise \
--max-combinations or drop --exhaustive to run the default search, which prunes"
);
std::process::exit(3);
}
}
}

Expand Down Expand Up @@ -351,10 +310,6 @@ fn document_of(
"stage2_physical_asap_optimization": { "candidates": [], "pruned": [] },
"stage3_plan_selection": { "costs": {}, "selected": null, "rejected": [], "stored_outputs": [] },
})),
Err(
error @ (PlanningStagesError::TooManyCandidates { .. }
| PlanningStagesError::TooManyPhysicalCandidates { .. }),
) => Err(Failure::TooManyCandidates(error)),
Err(error @ PlanningStagesError::Validation(_)) => Err(error.to_string().into()),
}
}
Expand Down Expand Up @@ -382,7 +337,7 @@ fn run(args: Vec<String>) -> Result<(), Failure> {
let mut plan_selection_settings_path: Option<String> = None;
let mut enforce_accuracy_guarantees = false;
let mut exhaustive = false;
// `--search`: the default search's algorithm, legacy unless given;
// `--search` explicitly names the candidate-connection engine;
// `--milp-time-limit-s` limits the MILP search.
let mut search_algorithm: Option<SearchAlgorithm> = None;
let mut milp_time_limit_ms: Option<u64> = None;
Expand Down Expand Up @@ -450,14 +405,11 @@ fn run(args: Vec<String>) -> Result<(), Failure> {
"--exhaustive" => exhaustive = true,
"--search" => {
search_algorithm = Some(match value()?.as_str() {
"legacy" => SearchAlgorithm::Legacy,
"memo-brute-force" => SearchAlgorithm::MemoBruteForce,
"memo-milp" => SearchAlgorithm::MemoMilp,
"candidate-connections" => SearchAlgorithm::CandidateConnections,
other => {
return Err(format!(
"--search {other}: expected legacy, memo-brute-force or memo-milp"
return Err(
format!("--search {other}: expected candidate-connections").into()
)
.into())
}
})
}
Expand Down Expand Up @@ -487,19 +439,14 @@ fn run(args: Vec<String>) -> Result<(), Failure> {
);
}
if max_combinations_given && !exhaustive {
return Err("--max-combinations limits the exhaustive search: it needs --exhaustive (the default search has no limit)".into());
return Err("--max-combinations limits the exhaustive search: it needs --exhaustive (bounds evaluated assignments)".into());
}
if exhaustive && search_algorithm.is_some() {
return Err(
"--search picks the default search's algorithm: drop it with --exhaustive".into(),
);
}
let search_algorithm = search_algorithm.unwrap_or_default();
if milp_time_limit_ms.is_some() && search_algorithm != SearchAlgorithm::MemoMilp {
return Err(
"--milp-time-limit-s limits the MILP search: it needs --search memo-milp".into(),
);
}
let search_algorithm = search_algorithm.unwrap_or(SearchAlgorithm::CandidateConnections);
if plan_selection_settings_path.is_some()
&& (cost_model.is_some()
|| cluster_memory.is_some()
Expand Down Expand Up @@ -711,25 +658,19 @@ fn run(args: Vec<String>) -> Result<(), Failure> {
.with_plan_selection(&plan_selection)
.with_deployment_capabilities(&deployment_capabilities)
.with_executor_compile_check(&PlannerExecutorCompileCheck);
let result = match exhaustive {
true => run_planning_stages_over(
roots.clone(),
&entries,
&data,
Some(max_combinations),
models,
),
false => run_search_over(
roots.clone(),
&entries,
&data,
models,
Stage1ToStage3Search {
milp_time_limit_ms,
..Stage1ToStage3Search::with_algorithm(search_algorithm)
},
),
};
let result = run_search_over(
roots.clone(),
&entries,
&data,
models,
Stage1ToStage3Search {
search: search_algorithm,
exhaustive,
keep_every_candidate: exhaustive,
max_evaluations: exhaustive.then_some(max_combinations),
milp_time_limit_ms,
},
);
// A rate, increase or irate over a declared gauge (the query still plans).
let mut warnings = counter_function_over_gauge_warnings_of(&described, &data)?;
// A selected plan's very large peak memory, when the deployment
Expand All @@ -754,9 +695,7 @@ fn run(args: Vec<String>) -> Result<(), Failure> {
serde_json::to_value(&data).map_err(|e| e.to_string())?;
document["search"] = json!(match (exhaustive, search_algorithm) {
(true, _) => "exhaustive",
(false, SearchAlgorithm::Legacy) => "default",
(false, SearchAlgorithm::MemoBruteForce) => "memo-brute-force",
(false, SearchAlgorithm::MemoMilp) => "memo-milp",
(false, SearchAlgorithm::CandidateConnections) => "candidate-connections",
});
document["accuracy_guarantees"] = json!(if enforce_accuracy_guarantees {
"enforced"
Expand Down Expand Up @@ -1193,7 +1132,21 @@ fn plan_selection_json(
});
}
Err(error) => {
return json!({ "costs": {}, "selected": null, "rejected": [], "error": error.to_string() })
let mut output = json!({ "costs": {}, "selected": null, "rejected": [], "error": error.to_string() });
// A budget stop without an incumbent still carries the search's
// report in its solver diagnostic. Decode that typed report for
// the viewer while preserving the complete original error.
if let SelectionError::Solver(diagnostic) = error {
if let Some((_, report)) = diagnostic.split_once("Search report: ") {
if let Some(Ok(report)) = serde_json::Deserializer::from_str(report)
.into_iter::<asap_plan_selection::CandidateConnectionReport>()
.next()
{
output["search"] = json!({ "candidate_connections": report });
}
}
}
return output;
}
};
let costs: serde_json::Map<_, _> = selection
Expand Down
Loading
Loading