Skip to content
Merged
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
49 changes: 42 additions & 7 deletions crates/asap-aware-mapping/src/accuracy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,14 @@ pub trait AccuracyModel {
stats: &PropagationStats,
) -> Result<AccuracyGuarantee, AccuracyError>;

/// Whether `guarantee` meets `target`: a bound in the target's metric
/// (or one with a sound direct comparison) within its ε, and within its
/// δ when it states one, over the values the target's scope covers (a
/// per-value δ does not meet a whole-result target). A bound in another metric does not satisfy,
/// however small. Unknown required dimensions fail; selection
/// separately excludes missing accuracy evidence.
/// Whether `guarantee` meets `target`: a bound within its ε, and within
/// its δ when it states one, over the values the target's scope covers
/// (a per-value δ does not meet a whole-result target). When the target
/// sets a metric, the bound must be in that metric (or one with a sound
/// direct comparison): a bound in another metric does not satisfy,
/// however small. When it sets none, a bound in any metric does, each
/// compared in its own metric. Unknown required dimensions fail;
/// selection separately excludes missing accuracy evidence.
fn satisfies(&self, guarantee: &AccuracyGuarantee, target: &AccuracyTarget) -> bool;
}

Expand Down Expand Up @@ -119,7 +121,9 @@ impl AccuracyModel for DefaultAccuracyModel {
if target.is_exact() || guarantee.is_exact() {
return guarantee.is_exact();
}
metric_meets(guarantee.spec.metric, target.metric)
target
.metric
.is_none_or(|metric| metric_meets(guarantee.spec.metric, metric))
&& scope_meets(guarantee, target)
&& within(guarantee.spec.error.evaluate(), target.error)
&& target
Expand Down Expand Up @@ -336,4 +340,35 @@ mod tests {
assert!(model.satisfies(&never_fails, &whole));
assert!(model.satisfies(&whole_result_guarantee(&never_fails, None), &whole));
}

/// A target whose metric the user did not set accepts a guarantee in
/// any metric whose own ε, δ and scope meet the target's numbers: a
/// rank, a relative or an absolute bound at any scale. A larger bound
/// or failure probability still misses it.
#[test]
fn a_target_without_a_metric_accepts_any_metric() {
let model = DefaultAccuracyModel;
let target = AccuracyTarget::epsilon_delta(0.01, 0.01).with_scope(AccuracyScope::PerValue);
assert_eq!(target.metric, None);
for metric in [
ErrorMetric::ABSOLUTE,
ErrorMetric::ABSOLUTE_STREAM_L1,
ErrorMetric::ABSOLUTE_STREAM_L2,
ErrorMetric::Relative,
ErrorMetric::Rank,
] {
assert!(
model.satisfies(&guarantee(metric, 0.01, 0.01), &target),
"{metric:?}"
);
assert!(
!model.satisfies(&guarantee(metric, 0.02, 0.01), &target),
"{metric:?}"
);
assert!(
!model.satisfies(&guarantee(metric, 0.01, 0.02), &target),
"{metric:?}"
);
}
}
}
6 changes: 3 additions & 3 deletions crates/asap-aware-mapping/src/asap_aware_cse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -892,13 +892,13 @@ mod tests {
use super::*;
use crate::test_support::lower_promql;
use asap_types::ir::schema_support::with_promql_series_identity;
use asap_types::types::RequestedAccuracy;
use asap_types::types::AccuracyTarget;

fn roots(queries: &[&str]) -> Vec<QueryRoot> {
queries
.iter()
.map(|q| {
let root = lower_promql(q, RequestedAccuracy::exact());
let root = lower_promql(q, AccuracyTarget::exact());
QueryRoot::Operator(with_promql_series_identity(&root).unwrap())
})
.collect()
Expand Down Expand Up @@ -1079,7 +1079,7 @@ mod tests {
.map(|(i, (q, epsilon))| {
let root = lower_promql(
q,
asap_types::types::RequestedAccuracy::epsilon_delta(*epsilon, 0.01)
asap_types::types::AccuracyTarget::epsilon_delta(*epsilon, 0.01)
.with_metric(asap_types::types::ErrorMetric::Rank),
);
(
Expand Down
2 changes: 1 addition & 1 deletion crates/asap-aware-mapping/src/maintained_population.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ mod tests {
}

fn lower(q: &str) -> Rc<OperatorNode> {
lower_promql(q, asap_types::types::RequestedAccuracy::exact())
lower_promql(q, asap_types::types::AccuracyTarget::exact())
}

// Instant scalar aggregations share the same retractable series population.
Expand Down
43 changes: 6 additions & 37 deletions crates/asap-aware-mapping/src/pass/stage1_to_stage3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use asap_types::ir::flat::flatten;
use asap_types::ir::schema_support::with_promql_series_identity;
use asap_types::ir::{ASAPOp, OperatorNode, QueryRoot};
use asap_types::post_asap::{FieldDataType, SketchAlgorithm};
use asap_types::types::{AccuracyTarget, RequestedAccuracy};
use asap_types::types::AccuracyTarget;
use asap_types::workload::{DataWorkload, QueryLanguage, QueryRecurrence, RepeatedDemand};

use super::{OptimizationInput, OptimizationPass, OptimizeError, PlanOutput, QueryPlan};
Expand Down Expand Up @@ -154,7 +154,7 @@ impl<Id> Stage1ToStage3Run<Id> {
/// listed with its reason, and a failed selection is reported in the run.
pub fn run_stage1_to_stage3<Id: Clone + std::fmt::Debug>(
roots: Vec<(Id, QueryRoot)>,
targets: &[Option<RequestedAccuracy>],
targets: &[Option<AccuracyTarget>],
recurrence: &[QueryRecurrence],
data: &DataWorkload,
models: PlanningModels<'_>,
Expand Down Expand Up @@ -487,7 +487,7 @@ fn readers_of(roots: &[QueryRoot], node: &Rc<OperatorNode>) -> Vec<usize> {
/// enumerating.
pub fn run_stage1_to_stage3_from_pass1<Id: Clone + std::fmt::Debug>(
per_computation: PerComputationCandidates<Id>,
targets: &[Option<RequestedAccuracy>],
targets: &[Option<AccuracyTarget>],
recurrence: &[QueryRecurrence],
data: &DataWorkload,
models: PlanningModels<'_>,
Expand All @@ -502,7 +502,7 @@ pub fn run_stage1_to_stage3_from_pass1<Id: Clone + std::fmt::Debug>(
pub fn run_stage2_and_stage3<Id: Clone + std::fmt::Debug>(
per_computation: PerComputationCandidates<Id>,
stage1: Vec<Stage1Candidate>,
targets: &[Option<RequestedAccuracy>],
targets: &[Option<AccuracyTarget>],
recurrence: &[QueryRecurrence],
data: &DataWorkload,
models: PlanningModels<'_>,
Expand Down Expand Up @@ -545,13 +545,7 @@ pub fn run_stage2_and_stage3<Id: Clone + std::fmt::Debug>(
roots: candidate.roots,
});
}
let (resolved, selection) = match resolve_targets(&per_computation.roots, targets) {
Ok(targets) => {
let selection = select_plan(&physical, &targets, recurrence, data, models);
(Some(targets), selection)
}
Err(error) => (None, Err(error)),
};
let selection = select_plan(&physical, targets, recurrence, data, models);
let selected_logical = selection.as_ref().ok().and_then(|selection| {
let from = &physical
.iter()
Expand All @@ -564,8 +558,7 @@ pub fn run_stage2_and_stage3<Id: Clone + std::fmt::Debug>(
.as_ref()
.ok()?;
// As Stage 3 sized it for its queries' failure probabilities.
let sized =
crate::stage3_plan_selection::sized_roots(roots, resolved.as_deref()?, data).ok()?;
let sized = crate::stage3_plan_selection::sized_roots(roots, targets, data).ok()?;
Some(crate::stage3_plan_selection::with_confirmed_weights(
&sized, data,
))
Expand All @@ -582,30 +575,6 @@ pub fn run_stage2_and_stage3<Id: Clone + std::fmt::Debug>(
}
}

/// Each query's requested accuracy resolved for its frontend root
/// ([`RequestedAccuracy::for_root`]): the metric plan selection checks.
fn resolve_targets<Id>(
roots: &[(Id, QueryRoot)],
targets: &[Option<RequestedAccuracy>],
) -> Result<Vec<Option<AccuracyTarget>>, SelectionError> {
roots
.iter()
.zip(targets)
.enumerate()
.map(|(query, ((_, root), requested))| {
requested
.as_ref()
.map(|requested| requested.for_root(root))
.transpose()
.map_err(|error| SelectionError::AccuracyRequest {
query: query + 1,
error,
})
})
.chain(targets.iter().skip(roots.len()).map(|_| Ok(None)))
.collect()
}

/// Mixed-radix increment of `choice`: the last target varies fastest.
/// `false` when a carry out of the first target ends the enumeration.
fn next_choice<Id>(inventory: &PerComputationCandidates<Id>, choice: &mut [usize]) -> bool {
Expand Down
28 changes: 20 additions & 8 deletions crates/asap-aware-mapping/src/per_computation_candidates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2738,7 +2738,19 @@ fn sized(algorithm: &SketchAlgorithm, intent: &AggIntent, share: usize) -> Sketc
&query,
)
})
.map_or(target.metric, |guarantee| guarantee.spec.metric);
.map(|guarantee| guarantee.spec.metric)
.or(target.metric);
// The split needs a concrete metric. A summary without an error model
// whose intent's target sets none is not split (never a guessed
// metric): it stays sized for the whole target, and Stage 3 rejects the
// candidate if its composed output then misses the query's target.
let Some(metric) = metric else {
debug_assert!(
target.is_exact(),
"sizing {algorithm:?} for a share of an approximate target without a metric"
);
return whole;
};
equal_share(&target, metric, share).map_or(whole, |share| size(&share))
}

Expand Down Expand Up @@ -2936,7 +2948,7 @@ fn statistic(intent: &AggIntent) -> Result<SketchStatistic, PerComputationCandid
#[cfg(test)]
mod tests {
use super::*;
use asap_types::types::RequestedAccuracy;
use asap_types::types::AccuracyTarget;
/// Approximate count keeps one exact option, its accumulator, beside the
/// universal summary; plain exact execution is not offered.
#[test]
Expand All @@ -2961,7 +2973,7 @@ mod tests {
mod guarantee_composition {
use super::*;
use crate::test_support::lower_promql;
use asap_types::types::RequestedAccuracy;
use asap_types::types::AccuracyTarget;

fn with_bound(metric: ErrorMetric, bound: f64, delta: f64) -> AccuracyGuarantee {
AccuracyGuarantee::new(
Expand All @@ -2983,7 +2995,7 @@ mod tests {
}
/// `query` carrying `guarantee`.
fn node_over(query: &str, guarantee: AccuracyGuarantee) -> Rc<OperatorNode> {
let node = lower_promql(query, RequestedAccuracy::exact());
let node = lower_promql(query, AccuracyTarget::exact());
Rc::new((*node).clone().with_guarantee(Some(guarantee)))
}
fn scalar(guarantee: AccuracyGuarantee) -> ScalarExpr {
Expand Down Expand Up @@ -3591,7 +3603,7 @@ mod tests {

fn inventory(query: &str) -> PerComputationCandidates<usize> {
let root =
crate::test_support::lower_promql(query, RequestedAccuracy::epsilon_delta(0.01, 0.001));
crate::test_support::lower_promql(query, AccuracyTarget::epsilon_delta(0.01, 0.001));
// Per-series state ranks each series' identity, which the rows carry
// as a column; a query over aggregated rows is planned over labels.
let root =
Expand Down Expand Up @@ -3704,7 +3716,7 @@ mod tests {
fn whole_expression_topk_partitions_index_the_heap_input() {
let root = crate::test_support::lower_promql(
"topk by (job) (2, sum by (service, job) (rate(m[1m])))",
RequestedAccuracy::epsilon(0.1),
AccuracyTarget::epsilon(0.1),
);
let inventory =
generate_per_computation_candidates(vec![(0, QueryRoot::Operator(root))]).unwrap();
Expand Down Expand Up @@ -3786,7 +3798,7 @@ mod tests {
("rate(m[1m])", None),
("max_over_time(m[1m])", None),
] {
let root = crate::test_support::lower_promql(query, RequestedAccuracy::exact());
let root = crate::test_support::lower_promql(query, AccuracyTarget::exact());
assert_eq!(samples_of_metric(&root).as_deref(), metric, "{query}");
}
}
Expand Down Expand Up @@ -4068,7 +4080,7 @@ mod tests {
grouping,
)
};
let input = |query| crate::test_support::lower_promql(query, RequestedAccuracy::exact());
let input = |query| crate::test_support::lower_promql(query, AccuracyTarget::exact());
let guarantee = |family: &FieldDataType, query| {
source_distinct_guarantee(family, &SketchStatistic::Cardinality, &input(query))
};
Expand Down
4 changes: 2 additions & 2 deletions crates/asap-aware-mapping/src/query_physical_lowering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1318,7 +1318,7 @@ mod tests {
PromqlEdgeStatistics, PromqlUnaryEdgeStatistics, PromqlValueKind, UnaryEdgeStatistics,
};
use asap_types::ir::{BinaryOperator, ExprSemantics, Predicate, SortKey, TimeRangeKind};
use asap_types::types::RequestedAccuracy;
use asap_types::types::AccuracyTarget;
use asap_types::workload::{
DataArrival, DurationMs, QueryRecurrence, QueryTimeScope, TimeSelection, TimestampMs,
};
Expand Down Expand Up @@ -2885,7 +2885,7 @@ mod tests {
query_batch: Some(vec![BatchEntry {
query: Query(query.into()),
requirements: QueryRequirements {
accuracy: AccuracyRequirement::Explicit(RequestedAccuracy::exact()),
accuracy: AccuracyRequirement::Explicit(AccuracyTarget::exact()),
..Default::default()
},
predictability: Predictability::Unknown,
Expand Down
Loading
Loading