Skip to content

Commit 347ba6a

Browse files
committed
Merge branch 'impl/sds-stack-761' into impl/sds-stack-766
2 parents 131df9f + 5d571e6 commit 347ba6a

23 files changed

Lines changed: 938 additions & 80 deletions

File tree

‎Cargo.lock‎

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ version = "0.1.0"
1414
[workspace.dependencies]
1515
# Keep Planner frontends, selection, and IR on the same immutable revision.
1616
# Alias upstream asap-types because this workspace also defines asap_types.
17-
planner-types = { package = "asap-types", git = "https://github.com/ProjectASAP/ASAPPlanner", rev = "c899084019580eac55e6502eb641d42e748c6ff2" }
18-
asap-aware-mapping = { git = "https://github.com/ProjectASAP/ASAPPlanner", rev = "c899084019580eac55e6502eb641d42e748c6ff2" }
19-
asap-frontend-promql = { git = "https://github.com/ProjectASAP/ASAPPlanner", rev = "c899084019580eac55e6502eb641d42e748c6ff2" }
20-
asap-frontend-sql = { git = "https://github.com/ProjectASAP/ASAPPlanner", rev = "c899084019580eac55e6502eb641d42e748c6ff2" }
17+
planner-types = { package = "asap-types", git = "https://github.com/ProjectASAP/ASAPPlanner", rev = "574bef34c3792c9ebc76773eeff159ffd14194fd" }
18+
asap-aware-mapping = { git = "https://github.com/ProjectASAP/ASAPPlanner", rev = "574bef34c3792c9ebc76773eeff159ffd14194fd" }
19+
asap-frontend-promql = { git = "https://github.com/ProjectASAP/ASAPPlanner", rev = "574bef34c3792c9ebc76773eeff159ffd14194fd" }
20+
asap-frontend-sql = { git = "https://github.com/ProjectASAP/ASAPPlanner", rev = "574bef34c3792c9ebc76773eeff159ffd14194fd" }
2121

2222
# Shared external deps (used by 2+ crates)
2323
serde = { version = "1.0", features = ["derive"] }
@@ -37,8 +37,8 @@ arc-swap = "1.7"
3737
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
3838

3939
# Internal crates
40-
asap-physical-operators = { git = "https://github.com/ProjectASAP/ASAPPlanner", rev = "c899084019580eac55e6502eb641d42e748c6ff2" }
41-
asap_sketch_codec = { git = "https://github.com/ProjectASAP/ASAPPlanner", rev = "c899084019580eac55e6502eb641d42e748c6ff2" }
40+
asap-physical-operators = { git = "https://github.com/ProjectASAP/ASAPPlanner", rev = "574bef34c3792c9ebc76773eeff159ffd14194fd" }
41+
asap_sketch_codec = { git = "https://github.com/ProjectASAP/ASAPPlanner", rev = "574bef34c3792c9ebc76773eeff159ffd14194fd" }
4242
asap_types = { path = "crates/asap_types" }
4343
asap_otel_proto = { path = "crates/asap_otel_proto" }
4444
indexmap = { version = "2.0", features = ["serde"] }

‎control_plane/src/physical/compiler.rs‎

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,11 @@ impl BackendLocalPlanningInput {
984984
&strategy,
985985
&asap_aware_mapping::TargetSubDAG::new(&typed),
986986
);
987+
proposed.candidates.extend(
988+
strategy
989+
.fixed_window_rate_topk_candidates(&typed)
990+
.candidates,
991+
);
987992
proposed.candidates.extend(direct.candidates);
988993
proposed.rejected.extend(direct.rejected);
989994
for candidate in proposed.candidates {
@@ -992,6 +997,16 @@ impl BackendLocalPlanningInput {
992997
};
993998
let compiled = asap_physical_operators::physical_planner::promql_rows::compile_current_series_readout(&root)
994999
.or_else(|_| asap_physical_operators::physical_planner::promql_rows::compile_rate_ranking(&root).map(|(_, program)| program));
1000+
if let Ok(physical) = asap_physical_operators::physical_planner::promql_rows::compile_fixed_window_rate_ranking(&root) {
1001+
planner_selection_trace.push(serde_json::json!({
1002+
"stage":"planner.physical_candidate", "query_id":query.query_id,
1003+
"logical_root_id":crate::planner_selection::explained_root_id(&root, &query.accuracy_target),
1004+
"rationale":candidate.rationale, "physical_candidate":serde_json::from_slice::<Value>(&physical.encode().map_err(|e| CompileError::Snapshot(e.to_string()))?).map_err(|e| CompileError::Snapshot(e.to_string()))?,
1005+
"guarantee":root.guarantee,
1006+
}));
1007+
candidate_roots.push(vec![(index, root)]);
1008+
continue;
1009+
}
9951010
match compiled {
9961011
Ok(program) => {
9971012
planner_selection_trace.push(serde_json::json!({
@@ -1533,7 +1548,7 @@ impl DeploymentPlanCompiler {
15331548
for state in &selected {
15341549
if matches!(&state.node.expr, SummaryExpr::SummaryAgg {
15351550
reduction: planner_types::pre_asap::Reduction::Reduce(keys), ..
1536-
} if keys.is_empty())
1551+
} if keys.is_empty()) || asap_physical_operators::physical_planner::promql_rows::compile_fixed_window_rate_ranking(&query.selected_plan_root).is_ok()
15371552
{
15381553
if let Some(sources) = immutable_materialization_sources(&state.node) {
15391554
canonical_nodes.insert(Rc::as_ptr(&state.node) as usize);
@@ -2115,7 +2130,31 @@ impl DeploymentPlanCompiler {
21152130
&query.selected_plan_root,
21162131
)
21172132
.ok();
2133+
let native_rate = native_rate.or_else(|| {
2134+
let physical = asap_physical_operators::physical_planner::promql_rows::compile_fixed_window_rate_ranking(&query.selected_plan_root).ok()?;
2135+
fn heap(node: &Rc<SummaryNode>) -> Option<Rc<SummaryNode>> {
2136+
match &node.expr {
2137+
SummaryExpr::SummaryAgg { family: SummaryFamilyType::Sketch(..), .. } => Some(node.clone()),
2138+
SummaryExpr::ValueOperation { child, .. } => heap(child),
2139+
SummaryExpr::SummaryEstimate { summary_input, .. } => heap(summary_input),
2140+
_ => None,
2141+
}
2142+
}
2143+
Some((heap(&query.selected_plan_root)?, physical.query))
2144+
});
21182145
let mut entry = if let Some((source, program)) = native_rate {
2146+
let native_state_binding = if let SummaryExpr::SummaryAgg {
2147+
family: SummaryFamilyType::Sketch(..),
2148+
..
2149+
} = &source.expr
2150+
{
2151+
let SummaryExpr::SummaryAgg { family, .. } = &source.expr else {
2152+
unreachable!()
2153+
};
2154+
Some(binding(&source, family)?)
2155+
} else {
2156+
None
2157+
};
21192158
let mut entry = crate::query_plan::compile_bound_composable_mapped(
21202159
query.query_id.clone(),
21212160
canonical.clone(),
@@ -2132,6 +2171,12 @@ impl DeploymentPlanCompiler {
21322171
}
21332172
},
21342173
)?;
2174+
if let Some(binding) = native_state_binding {
2175+
entry.nodes.insert(
2176+
entry.root,
2177+
crate::query_plan::QueryPlanNode::ReadMaterialization { binding },
2178+
);
2179+
}
21352180
let root = crate::query_plan::QueryNodeId(
21362181
entry.nodes.keys().map(|id| id.0).max().unwrap_or(0) + 1,
21372182
);
@@ -2297,7 +2342,7 @@ impl DeploymentPlanCompiler {
22972342
.find(|entry| entry.query_id == query_id)
22982343
.expect("compiled query entry exists")
22992344
.root;
2300-
let installed = super::executable_binding::install_selected_dag(
2345+
let mut installed = super::executable_binding::install_selected_dag(
23012346
query_id.clone(),
23022347
&compiled.dag,
23032348
query_plan_sink,
@@ -2313,11 +2358,35 @@ impl DeploymentPlanCompiler {
23132358
query_id: query_id.clone(),
23142359
reason,
23152360
})?;
2361+
if let Ok(physical) = asap_physical_operators::physical_planner::promql_rows::compile_fixed_window_rate_ranking(&request.queries[query_index].selected_plan_root) {
2362+
let program = physical.precompute.ok_or_else(|| CompileError::Snapshot("fixed-window candidate has no maintenance program".into()))?;
2363+
let sink = PostAsapNodeId(u32::try_from(program.roots()[0]).map_err(|_| CompileError::Snapshot("maintenance sink overflow".into()))?);
2364+
installed.native_programs.insert(sink, serde_json::from_slice(&program.encode().map_err(|e| CompileError::Snapshot(e.to_string()))?).map_err(|e| CompileError::Snapshot(e.to_string()))?);
2365+
}
23162366
query_plan
23172367
.selected_dags
23182368
.insert(query_id.clone(), installed.document.clone());
23192369
installed_dags.insert(query_id, installed);
23202370
}
2371+
let maintenance_lookbacks = materializations
2372+
.iter()
2373+
.filter_map(|target| {
2374+
target
2375+
.derived_input
2376+
.as_ref()
2377+
.map(|input| (input, target.stored_window_ms()))
2378+
})
2379+
.flat_map(|(input, window)| input.inputs.iter().map(move |source| (*source, window)))
2380+
.fold(
2381+
BTreeMap::<asap_types::sds::StoredOutputId, u64>::new(),
2382+
|mut windows, (source, window)| {
2383+
windows
2384+
.entry(source)
2385+
.and_modify(|old| *old = (*old).max(window))
2386+
.or_insert(window);
2387+
windows
2388+
},
2389+
);
23212390
for materialization in &mut materializations {
23222391
let fingerprint = materialization.policy_fingerprint();
23232392
let max_lookback_ms = query_plan
@@ -2326,6 +2395,7 @@ impl DeploymentPlanCompiler {
23262395
.flat_map(QueryPlanEntry::materialization_bindings)
23272396
.filter(|binding| binding.materialization.fingerprint() == fingerprint)
23282397
.filter_map(|binding| binding.readout_lookback_ms)
2398+
.chain(maintenance_lookbacks.get(&fingerprint.into()).copied())
23292399
.max();
23302400
if let Some(lookback_ms) = max_lookback_ms {
23312401
materialization.num_aggregates_to_retain = Some(retained_state_count(
@@ -3434,6 +3504,11 @@ pub(crate) fn retained_partition_count(
34343504
A::Increase | A::Rate | A::Min | A::Max
34353505
)
34363506
|| !materialization.grouping_labels.names().is_empty()
3507+
|| (materialization.derived_input.is_some()
3508+
&& matches!(
3509+
materialization.aggregation_type,
3510+
A::CountMinSketchWithHeap | A::CountSketchWithHeap
3511+
))
34373512
{
34383513
u128::from(input_cardinality.unwrap_or(1).max(1))
34393514
} else {
@@ -3761,6 +3836,21 @@ fn immutable_materialization_sources(node: &SummaryNode) -> Option<Vec<Rc<Summar
37613836
// because Planner made the exact accumulator boundary explicit.
37623837
return None;
37633838
};
3839+
if matches!(&node.expr, SummaryExpr::SummaryAgg { family: SummaryFamilyType::Sketch(kind, _), .. }
3840+
if matches!(kind.algorithm(), SketchAlgorithm::CmsWithHeap | SketchAlgorithm::CountSketchWithHeap))
3841+
{
3842+
if let SummaryExpr::ValueOperation {
3843+
child: source,
3844+
operation: planner_types::post_asap::ValueOperation::FinalizeExactAccumulator,
3845+
timing: ExecutionTiming::IngestionTime,
3846+
} = &child.expr
3847+
{
3848+
if matches!(&source.expr, SummaryExpr::SummaryAgg { family: SummaryFamilyType::ExactAggregate(ExactKind::Rate, _), reduction: planner_types::pre_asap::Reduction::PerEntity, child, .. } if matches!(child.expr, SummaryExpr::KeepPreAsap(_)))
3849+
{
3850+
return Some(vec![source.clone()]);
3851+
}
3852+
}
3853+
}
37643854
if input.item.is_some()
37653855
|| !matches!(
37663856
input.weight,
@@ -3934,6 +4024,22 @@ fn scoped_materialization(
39344024
let SummaryExpr::SummaryAgg { reduction, .. } = &node.expr else {
39354025
anyhow::bail!("materialization lacks SummaryAgg partition contract");
39364026
};
4027+
if immutable_materialization_sources(node).is_some_and(|sources| {
4028+
sources.iter().any(|source| {
4029+
matches!(
4030+
&source.expr,
4031+
SummaryExpr::SummaryAgg {
4032+
family: SummaryFamilyType::ExactAggregate(
4033+
planner_types::post_asap::ExactKind::Rate,
4034+
_
4035+
),
4036+
..
4037+
}
4038+
)
4039+
})
4040+
}) {
4041+
config.grouping_labels = asap_types::KeyByLabelNames { labels: Vec::new() }.into();
4042+
}
39374043
config.partitioning = Some(match reduction {
39384044
planner_types::pre_asap::Reduction::PerEntity => {
39394045
asap_types::sds::PopulationPartitioning::PerEntity

‎control_plane/src/physical/executable_binding.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ pub fn install_selected_dag(
5555
}
5656
precompute_sinks.sort();
5757
let installed = InstalledPostAsapDag {
58+
native_programs: std::collections::BTreeMap::new(),
5859
document: OwnedPostAsapDag::from_executable(query_id, dag)?,
5960
binding: BackendExecutableBinding {
6061
nodes,

‎control_plane/src/physical/plan_dot.rs‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ pub fn render(plan: &CompiledPhysicalPlan) -> String {
6666
edge.role
6767
));
6868
}
69+
for (sink, program) in &installed.native_programs {
70+
render_native(
71+
&mut dot,
72+
&format!("maintenance_{dag_index}_{}", sink.0),
73+
"Planner maintenance operators",
74+
program,
75+
);
76+
}
6977
dot.push_str(" }\n");
7078
}
7179
dot.push_str(" }\n");
@@ -104,12 +112,48 @@ pub fn render(plan: &CompiledPhysicalPlan) -> String {
104112
));
105113
}
106114
}
115+
if let Some(program) = &entry.physical_dag {
116+
render_native(
117+
&mut dot,
118+
&format!("native_query_{query_index}"),
119+
"Planner query operators",
120+
program,
121+
);
122+
}
107123
dot.push_str(" }\n");
108124
}
109125
dot.push_str("}\n");
110126
dot
111127
}
112128

129+
fn render_native(dot: &mut String, prefix: &str, label: &str, program: &serde_json::Value) {
130+
let Some(nodes) = program["nodes"].as_object() else {
131+
return;
132+
};
133+
dot.push_str(&format!(
134+
" subgraph cluster_{prefix} {{\n label=\"{}\";\n",
135+
escape(label)
136+
));
137+
for (id, node) in nodes {
138+
let operator = node.get("Operator");
139+
let label = operator
140+
.map(|op| op["operator"]["kind"].to_string())
141+
.unwrap_or_else(|| "Bound physical input".into());
142+
emit_node(
143+
dot,
144+
&format!("{prefix}_{id}"),
145+
&format!("#{id}\n{label}"),
146+
"",
147+
);
148+
if let Some(inputs) = operator.and_then(|op| op["inputs"].as_array()) {
149+
for input in inputs {
150+
dot.push_str(&format!(" {prefix}_{input} -> {prefix}_{id};\n"));
151+
}
152+
}
153+
}
154+
dot.push_str(" }\n");
155+
}
156+
113157
fn materialization_node(id: u64) -> String {
114158
format!("materialization_{id:016x}")
115159
}

‎control_plane/src/physical/workload_cost.rs‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,28 @@ pub fn manifest(
215215
}
216216
}
217217
}
218+
for installed in plan.precompute_plan.executable_dags.values() {
219+
for (sink, program) in &installed.native_programs {
220+
let Some(asap_types::executable_plan::BackendNodeBinding::Materialization {
221+
stored_output,
222+
}) = installed.binding.node(*sink)
223+
else {
224+
return Err(invalid("native maintenance sink is unbound"));
225+
};
226+
let config = plan
227+
.precompute_plan
228+
.materializations
229+
.iter()
230+
.find(|m| m.policy_fingerprint() == stored_output.fingerprint())
231+
.ok_or_else(|| invalid("native maintenance config is absent"))?;
232+
add(
233+
format!("maintenance:{}", stored_output.0),
234+
json!({"physical_program":program,"stored_output":stored_output,"window_ms":config.stored_window_ms(),"interval_ms":config.slide_interval.saturating_mul(1000)}),
235+
"horizon",
236+
1.0,
237+
);
238+
}
239+
}
218240
for rule in &plan.transmission_plan.rules {
219241
add(
220242
format!("transport:{}:{}", rule.producer_id, rule.materialization.0),

0 commit comments

Comments
 (0)