Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 23 additions & 12 deletions crates/plan-selection/src/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
//! candidates' contexts apart), the timings, the warm start's cost, the
//! objective, `plan_cost` and the dual bound (measurement only).

use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::time::Instant;

use asap_cost::{kept_tumbling_window_chains, node_map, plan_cost, CostInputs};
Expand Down Expand Up @@ -106,9 +106,10 @@ pub fn select_plan(
split_into_parts: true,
},
)
.map_err(selection_error)?;
.map_err(|error| selection_error(error, &inputs))?;
let build_ms = construction_started.elapsed().as_millis() as u64;
let solution = solve_selection_milp(milp, deadline, build_ms).map_err(selection_error)?;
let solution = solve_selection_milp(milp, deadline, build_ms)
.map_err(|error| selection_error(error, &inputs))?;

let validation_started = Instant::now();
let chosen = ChosenCandidates {
Expand Down Expand Up @@ -212,8 +213,10 @@ pub fn select_plan(
})
}

/// The selection MILP's error as Stage 3's.
fn selection_error(error: SelectionMilpError) -> ErrorSelectingPlan {
/// The selection MILP's error as Stage 3's. An infeasible MILP also names
/// why candidates were left out unpriced: a missing source fact there is
/// usually the cause, and "infeasible" alone hides it.
fn selection_error(error: SelectionMilpError, inputs: &MilpInputs) -> ErrorSelectingPlan {
let infeasible = |id: String, cause: RejectionCause, reason: String| {
ErrorSelectingPlan::NoValidCandidate(vec![Rejection { id, cause, reason }])
};
Expand All @@ -226,13 +229,21 @@ fn selection_error(error: SelectionMilpError) -> ErrorSelectingPlan {
roots,
entries,
},
SelectionMilpError::Infeasible => infeasible(
"workload".into(),
RejectionCause::Infeasible,
"no plan meets every query's accuracy target, latency bound and the reader \
constraints together (the selection MILP is infeasible)"
.into(),
),
SelectionMilpError::Infeasible => {
let unpriced: BTreeSet<&str> = inputs
.candidate_data
.iter()
.filter_map(|data| data.usage.as_ref().err().map(String::as_str))
.collect();
let mut reason = "no plan meets every query's accuracy target, latency bound and \
the reader constraints together (the selection MILP is infeasible)"
.to_string();
if !unpriced.is_empty() {
reason += "; candidates left out because they could not be priced: ";
reason += &unpriced.into_iter().collect::<Vec<_>>().join("; ");
}
infeasible("workload".into(), RejectionCause::Infeasible, reason)
}
SelectionMilpError::NoSolution { status } => ErrorSelectingPlan::NoPlanByDeadline(format!(
"HiGHS stopped ({status}) without an integral plan"
)),
Expand Down
20 changes: 20 additions & 0 deletions crates/plan-selection/src/select/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,23 @@ fn cost_model_2_is_unsupported() {
Err(ErrorSelectingPlan::UnsupportedCostModel(_))
));
}

// With no fact bounding `m`'s rows, no candidate can be priced and the MILP
// is infeasible; the rejection names the missing fact, not only
// "infeasible" (the DAG viewer shows it).
#[test]
fn an_unpriceable_workload_names_the_missing_fact() {
let mut fixture = shared_sum();
fixture.data = asap_types::workload::DataWorkload::default();
let stage2 = realizer(&fixture);
match select(&fixture, &BuiltinAccuracyModel, &stage2) {
Err(ErrorSelectingPlan::NoValidCandidate(rejected)) => {
let reasons = format!("{rejected:?}");
assert!(
reasons.contains("pricing source m needs an arrival rate"),
"{reasons}"
)
}
other => panic!("{other:?}"),
}
}
3 changes: 2 additions & 1 deletion crates/plan-selection/src/selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ use crate::checks::{accuracy_check, accuracy_targets, latency_violation};
use crate::report::{
AcceptedAccuracy, ErrorSelectingPlan, Rejection, RejectionCause, WholePlanSelection,
};
use crate::{AnalyticalCostError, SelectionModels};
use crate::SelectionModels;
use asap_cost::analytical::AnalyticalCostError;
use asap_cost::{
kept_tumbling_window_chains, node_map, plan_cost, query_reach, tumbling_window_retention_ms,
};
Expand Down
Loading