Repository navigation
PromQL aggregates grouped with without (...) get no plan: the executor rejects "grouping without requires resolved label columns" #861
Description
Example
Every PromQL grouped aggregate written with without (...) gets no plan. Stage 3 discards its only candidate because the executor cannot compile it:
cargo run -p asap-devtools --bin stage_pipeline -- \
--promql "sum without (instance) (data)" --out run.jsonrun.json, stage3_plan_selection:
"selected": null,
"rejected_by_cause": {"executor_cannot_compile": 1},
"search_rejected": [{"cause": "executor_cannot_compile", "id": "P1",
"reason": "query run: invalid DAG: node 2: invalid DAG: grouping without requires resolved label columns"}]Results from a debug build of origin/main at a7610ed:
| Query | Stage 3 result |
|---|---|
sum without (instance) (data) |
no plan, executor_cannot_compile |
quantile without (host) (0.5, data) |
no plan, executor_cannot_compile |
avg without (instance) (data) |
no plan, executor_cannot_compile |
max without (instance) (data) |
no plan, executor_cannot_compile |
count without (instance) (data) |
no plan, executor_cannot_compile |
sum without (instance) (rate(data[5m])) |
no plan, executor_cannot_compile |
sum by (host) (data) |
plan P1 selected |
quantile by (host) (0.5, data) |
plan P1 selected |
Expected: these queries plan and run like their by (...) forms. As in Prometheus, sum without (instance) (data) groups the series by every label except instance (and __name__), and returns one sample per group with that label set.
Current behavior
The front end and Stages 1 and 2 accept the query. The failure happens in the executor compile check that Stage 3 runs on each candidate it might select:
crates/frontend-promql/src/promql.rs:2043(resolve_group) lowerswithout (instance)toAggregate { reduction: Reduce(GroupKeys::without([instance])) }. This keeps only the excluded label positions.- The scan schema is closed. It has
ts,value, the referenced labelinstance, and$promql_series_identity, a Utf8 column that holds the series' complete label map as JSON.crates/types/src/ir/schema/aggregate_schema.rs:91/:190(without_output_schema) builds the Aggregate's output by dropping the excluded columns. That output is[$promql_series_identity, sum], and its identity column still containsinstance. crates/executor/src/physical_planner/mod.rs:1226, ingroups(:1224), rejects everywithoutgrouping:The aggregate lowering atfn groups(input: &SchemaRef, groups: &GroupKeys) -> Result<Vec<usize>, Error> { if groups.is_without() { return Err(invalid("grouping without requires resolved label columns")); }
mod.rs:998reaches it, and so do the window partitions (:917,:928) and the summary builds (:1084,:1105,:1143,:1162).- Stage 3 runs that compile through
PlannerExecutorCompileCheck(crates/planner/src/installable.rs:418, whichplan_installableinstalls at:365). Atcrates/plan-selection/src/lib.rs:1302it rejects the candidate asRejectionCause::ExecutorCannotCompile. This is the only candidate, so no plan is selected, andplan_installablehas no plan to install.
Something similar already works nearby. The executor's PromQL vector path, grouped in crates/executor/src/physical_planner/promql_values.rs:98, handles without with Expression::LabelSet { column, labels, without: true }. That expression computes the group's label set from a row's labels at run time.
Possible fix locations (not chosen)
The labels to keep are not a fixed list of columns. They are whatever each series' $promql_series_identity holds, minus the excluded names, so they are only known per row at run time. Either layer can build the expression that computes them:
- At lowering (front end / IR). The scan's schema is known when the Aggregate is lowered. Lowering could rewrite
without (L)into abygrouping over a projected key: the identity column with the labels inL(and__name__) removed, similar to the existingpromql_drop_metric_nameprojection (crates/frontend-common/src/resolve.rs:825). The executor would then see an ordinarybygrouping, and the Aggregate's output identity would no longer contain the excluded labels. Stages 1 to 3 (sharing, summaries, costing) would also see a plain group key. - At executor compile.
groups(crates/executor/src/physical_planner/mod.rs:1224) could resolve awithoutgrouping when the input has a series identity column. It would group on aLabelSet { without: true }key over that column, the same waypromql_values::groupeddoes. The IR would stay unchanged, and the semantics would live in the executor next to the existingLabelSetexpression.
Impact
Every reducing PromQL aggregate (sum, avg, min, max, count, quantile, ...) written with without gets no plan through stage_pipeline or plan_installable. without is common in dashboards, for example sum without (instance) (rate(...)).
Regression test
A stage_pipeline test (or a plan_installable test) with sum without (instance) (data) and quantile without (host) (0.5, data). It fails today with executor_cannot_compile. After the fix it should select a plan and run it on series such as {instance="a", job="x"} and {instance="b", job="x"}, returning one sample labelled {job="x"}.
Related
- PromQL without(...) grouping unsupported — usage-derived schema can't enumerate the complement #39 (closed):
without(...)was first rejected because the usage-derived schema could not list the complement. Lowering now carries the excluded labels and leaves the kept set to run time, but the executor never resolves it.
Activity