Skip to content

Commit 8909ea1

Browse files
authored
Merge pull request #335 from ProjectASAP/feat/cost-streaming-summary-dag
feat(cost): cost complete streaming summary DAGs
2 parents 3ff2062 + 673e74a commit 8909ea1

12 files changed

Lines changed: 4476 additions & 1528 deletions

File tree

‎crates/asap-aware-mapping/src/analytical_cost.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1948,7 +1948,7 @@ pub enum AnalyticalCostError {
19481948
IncompatibleLifecycleGuarantee,
19491949
#[error("bootstrap row and byte evidence must either both be zero or both be non-zero")]
19501950
InconsistentBootstrapEvidence,
1951-
#[error("summary operation cost {0} must be finite and non-negative, got {1}")]
1951+
#[error("required summary operation cost {0} must be finite and positive, got {1}")]
19521952
InvalidOperationCost(&'static str, f64),
19531953
#[error("required analytical input {0} is missing or zero")]
19541954
MissingOrZero(&'static str),

‎crates/asap-aware-mapping/src/cost_model.rs‎

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ use std::rc::Rc;
5050

5151
use asap_types::post_asap::{
5252
GroupingStrategy, HydraParams, SketchAlgorithm, SketchParams, SketchQuery, SummaryExpr,
53-
SummaryFamilyType, SummaryNode,
53+
SummaryFamilyType, SummaryMaintenanceLifecycleGuarantee, SummaryNode,
5454
};
5555
use asap_types::pre_asap::agg_intent::AggIntent;
5656
use asap_types::pre_asap::expr_ir::ColumnRef;
@@ -99,6 +99,15 @@ pub struct CseCandidate<'a> {
9999
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
100100
pub struct Cost(pub f64);
101101

102+
/// One physical summary state and the lifecycle selected for that exact DAG
103+
/// node. Node identity is preserved so whole-DAG models can bind per-state
104+
/// evidence without relying on traversal order.
105+
pub struct CostedSummaryDeployment<'a> {
106+
pub summary: &'a SummaryNode,
107+
pub guarantee: &'a SummaryMaintenanceLifecycleGuarantee,
108+
pub selected_cost: Cost,
109+
}
110+
102111
impl Cost {
103112
/// The cost of an operation that costs nothing at all.
104113
pub const ZERO: Cost = Cost(0.0);
@@ -543,6 +552,17 @@ pub trait CostModel {
543552
SummaryMaintenanceLifecycleCostInputs::default()
544553
}
545554

555+
/// Horizon-aware form used by lifecycle planning. Models whose retention
556+
/// objective is capacity rather than byte-seconds can normalize their
557+
/// rate so the horizon integral equals one peak-capacity charge.
558+
fn summary_maintenance_lifecycle_cost_inputs_for_horizon(
559+
&self,
560+
summary: &SummaryNode,
561+
_horizon: Option<Horizon>,
562+
) -> SummaryMaintenanceLifecycleCostInputs {
563+
self.summary_maintenance_lifecycle_cost_inputs(summary)
564+
}
565+
546566
/// Physical update/merge/delete support for one concrete summary. The
547567
/// conservative default advertises no long-lived maintenance capability.
548568
fn summary_maintenance_capabilities(
@@ -552,12 +572,44 @@ pub trait CostModel {
552572
SummaryMaintenanceCapabilities::default()
553573
}
554574

575+
/// Replace the sum of selected per-state lifecycle costs with a complete
576+
/// root-DAG cost. The default preserves legacy models. Evidence-strict
577+
/// models return `None` when any root operation is unavailable; callers
578+
/// must not then reuse the partial per-state sum.
579+
fn complete_summary_candidate_cost(
580+
&self,
581+
_root: &SummaryNode,
582+
_target: Option<&QueryExpr>,
583+
deployments: &[CostedSummaryDeployment<'_>],
584+
_horizon: Option<Horizon>,
585+
_expected_reads: Option<f64>,
586+
) -> Option<Cost> {
587+
Some(Cost(
588+
deployments
589+
.iter()
590+
.map(|deployment| deployment.selected_cost.0)
591+
.sum(),
592+
))
593+
}
594+
555595
/// Cost of evaluating `target` directly from its logical/raw inputs once.
556596
/// When known, lifecycle-aware materialization compares this fallback with
557597
/// the aggregate cost of the selected summary deployments.
558598
fn raw_query_recompute_cost(&self, _target: &QueryExpr) -> Option<Cost> {
559599
None
560600
}
601+
602+
/// Complete raw cost over the comparison context. The default preserves
603+
/// per-read models; context-aware models override this when raw input
604+
/// cardinality changes between evaluations.
605+
fn raw_query_recompute_total_cost(
606+
&self,
607+
target: &QueryExpr,
608+
expected_reads: f64,
609+
) -> Option<Cost> {
610+
self.raw_query_recompute_cost(target)
611+
.map(|per_read| Cost(per_read.0 * expected_reads))
612+
}
561613
}
562614

563615
fn sketch_state(

‎crates/asap-aware-mapping/src/physical_operator_statistics.rs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ impl ComparisonScope {
6969

7070
/// Validate this scope and return its effective query evaluation count.
7171
pub fn validate(&self) -> Result<u64, AnalyticalCostError> {
72-
if self.data_arrival != DataArrival::AtRest {
72+
if !matches!(
73+
self.data_arrival,
74+
DataArrival::AtRest | DataArrival::ContinuouslyIngesting
75+
) {
7376
return Err(AnalyticalCostError::UnsupportedDataArrival(
7477
self.data_arrival,
7578
));

‎crates/asap-aware-mapping/src/replacement.rs‎

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2298,9 +2298,21 @@ pub struct PlanSpace<Id> {
22982298
#[derive(Default)]
22992299
pub(crate) struct CandidateCostOverrides {
23002300
costs: HashMap<(*const QueryExpr, *const ReplacementSubDAG), Cost>,
2301+
raw_costs: HashMap<*const QueryExpr, Cost>,
2302+
/// Targets for which the caller requested an atomic raw-vs-summary
2303+
/// decision. Other memo groups continue through ordinary CSE selection.
2304+
finalized_targets: HashSet<*const QueryExpr>,
23012305
}
23022306

23032307
impl CandidateCostOverrides {
2308+
pub(crate) fn finalize_target(&mut self, target: &Rc<QueryExpr>) {
2309+
self.finalized_targets.insert(Rc::as_ptr(target));
2310+
}
2311+
2312+
fn finalizes(&self, target: &Rc<QueryExpr>) -> bool {
2313+
self.finalized_targets.contains(&Rc::as_ptr(target))
2314+
}
2315+
23042316
pub(crate) fn insert(
23052317
&mut self,
23062318
target: &Rc<QueryExpr>,
@@ -2316,6 +2328,14 @@ impl CandidateCostOverrides {
23162328
.get(&(Rc::as_ptr(target), candidate as *const _))
23172329
.copied()
23182330
}
2331+
2332+
pub(crate) fn insert_raw(&mut self, target: &Rc<QueryExpr>, cost: Cost) {
2333+
self.raw_costs.insert(Rc::as_ptr(target), cost);
2334+
}
2335+
2336+
fn raw(&self, target: &Rc<QueryExpr>) -> Option<Cost> {
2337+
self.raw_costs.get(&Rc::as_ptr(target)).copied()
2338+
}
23192339
}
23202340

23212341
impl<Id> PlanSpace<Id> {
@@ -3129,19 +3149,26 @@ impl<Id> PlanSpace<Id> {
31293149
let effective = effective_uses.get(ptr).copied().unwrap_or(0);
31303150
effective_uses.insert(*ptr, effective);
31313151

3132-
let lifecycle_choice = candidate_costs.and_then(|costs| {
3133-
group
3134-
.candidates
3135-
.iter()
3136-
.filter_map(|candidate| {
3137-
costs
3138-
.get(&group.target, candidate)
3139-
.map(|cost| (candidate, cost))
3140-
})
3141-
.min_by(|(_, a), (_, b)| a.0.total_cmp(&b.0))
3142-
.map(|(candidate, _)| candidate)
3143-
});
3144-
let chosen = if lifecycle_choice.is_some() {
3152+
let lifecycle_choice = candidate_costs
3153+
.filter(|costs| costs.finalizes(&group.target))
3154+
.map(|costs| {
3155+
let summary = group
3156+
.candidates
3157+
.iter()
3158+
.filter_map(|candidate| {
3159+
costs
3160+
.get(&group.target, candidate)
3161+
.map(|cost| (candidate, cost))
3162+
})
3163+
.min_by(|(_, a), (_, b)| a.0.total_cmp(&b.0));
3164+
match (summary, costs.raw(&group.target)) {
3165+
(Some((_, summary_cost)), Some(raw)) if raw.0 <= summary_cost.0 => None,
3166+
(Some((candidate, _)), _) => Some(candidate),
3167+
(None, Some(_)) => None,
3168+
(None, None) => None,
3169+
}
3170+
});
3171+
let chosen = if let Some(lifecycle_choice) = lifecycle_choice {
31453172
lifecycle_choice
31463173
} else if cost_model.candidate_cost_covers_complete_plan() {
31473174
let effective_target = TargetSubDAG::with_consumer_count(&group.target, effective);

0 commit comments

Comments
 (0)