Skip to content

PromQL aggregates grouped with without (...) get no plan: the executor rejects "grouping without requires resolved label columns" #861

Open

Description

@zzylol

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.json

run.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:

  1. crates/frontend-promql/src/promql.rs:2043 (resolve_group) lowers without (instance) to Aggregate { reduction: Reduce(GroupKeys::without([instance])) }. This keeps only the excluded label positions.
  2. The scan schema is closed. It has ts, value, the referenced label instance, 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 contains instance.
  3. crates/executor/src/physical_planner/mod.rs:1226, in groups (:1224), rejects every without grouping:
    fn groups(input: &SchemaRef, groups: &GroupKeys) -> Result<Vec<usize>, Error> {
        if groups.is_without() {
            return Err(invalid("grouping without requires resolved label columns"));
        }
    The aggregate lowering at mod.rs:998 reaches it, and so do the window partitions (:917, :928) and the summary builds (:1084, :1105, :1143, :1162).
  4. Stage 3 runs that compile through PlannerExecutorCompileCheck (crates/planner/src/installable.rs:418, which plan_installable installs at :365). At crates/plan-selection/src/lib.rs:1302 it rejects the candidate as RejectionCause::ExecutorCannotCompile. This is the only candidate, so no plan is selected, and plan_installable has 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 a by grouping over a projected key: the identity column with the labels in L (and __name__) removed, similar to the existing promql_drop_metric_name projection (crates/frontend-common/src/resolve.rs:825). The executor would then see an ordinary by grouping, 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 a without grouping when the input has a series identity column. It would group on a LabelSet { without: true } key over that column, the same way promql_values::grouped does. The IR would stay unchanged, and the semantics would live in the executor next to the existing LabelSet expression.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpromqlPromQL front-end lowering (L1→L2)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions