Skip to content

Commit 06b883c

Browse files
feat(query-engine): serve topk(sum by (l) (f(m[w]))) from its planned heap
Register the shared topk-of-sum patterns so the engine matches the planner's collapsed CountMinSketchWithHeap and answers with the inner sum's labels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent cee0a2e commit 06b883c

2 files changed

Lines changed: 57 additions & 0 deletions

File tree

‎asap-query-engine/src/engines/simple_engine/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,8 @@ impl SimpleEngine {
561561
spatial_pattern("generic", &spatial_pattern_blocks),
562562
];
563563
controller_patterns.extend(one_temporal_one_spatial_collapsable_patterns);
564+
controller_patterns
565+
.extend(promql_utilities::query_logics::parsing::build_topk_of_sum_patterns());
564566

565567
Self {
566568
store,

‎asap-query-engine/tests/e2e_precompute_equivalence.rs‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,61 @@ async fn e2e_nested_topk_executes_after_its_planned_sum_anchor() {
454454
assert_eq!(vector.values[0].value, 9.0);
455455
}
456456

457+
#[tokio::test]
458+
async fn e2e_topk_of_sum_ranks_groups_from_one_planned_heap() {
459+
let metric = "topk_of_sum_requests";
460+
let query = "topk(1, sum by (job) (sum_over_time(topk_of_sum_requests[1s])))";
461+
let (streaming_config, inference_config) = plan_promql_query(
462+
metric,
463+
vec!["instance".to_string(), "job".to_string()],
464+
query,
465+
1_000,
466+
);
467+
let heaps: Vec<_> = streaming_config
468+
.get_all_aggregation_configs()
469+
.values()
470+
.map(|config| (config.aggregation_type, config.aggregated_labels.clone()))
471+
.collect();
472+
assert_eq!(
473+
heaps,
474+
vec![(
475+
AggregationType::CountMinSketchWithHeap,
476+
KeyByLabelNames::new(vec!["job".to_string()])
477+
)]
478+
);
479+
480+
// The top series is worker's `c` (4), but the top job is api (2 + 3).
481+
let samples = vec![
482+
make_timeseries(metric, vec![("job", "api"), ("instance", "a")], 1_000, 2.0),
483+
make_timeseries(metric, vec![("job", "api"), ("instance", "b")], 1_000, 3.0),
484+
make_timeseries(
485+
metric,
486+
vec![("job", "worker"), ("instance", "c")],
487+
1_000,
488+
4.0,
489+
),
490+
make_timeseries(metric, vec![("job", "api"), ("instance", "a")], 3_000, 0.0),
491+
make_timeseries(
492+
metric,
493+
vec![("job", "worker"), ("instance", "c")],
494+
3_000,
495+
0.0,
496+
),
497+
];
498+
let engine =
499+
build_engine_from_configs(19429, streaming_config, inference_config, samples, 1_000).await;
500+
501+
let Some((_, QueryResult::Vector(vector))) = engine
502+
.handle_query_promql(query.to_string(), 1.0)
503+
.expect("topk of sum should execute natively")
504+
else {
505+
panic!("topk of sum should return an instant vector");
506+
};
507+
assert_eq!(vector.values.len(), 1, "result: {:?}", vector.values);
508+
assert_eq!(vector.values[0].labels.labels, vec!["api"]);
509+
assert_eq!(vector.values[0].value, 5.0);
510+
}
511+
457512
#[tokio::test]
458513
async fn e2e_nested_aggregation_preserves_plain_topk_labels() {
459514
use asap_types::query_config::{

0 commit comments

Comments
 (0)