Skip to content

topk(k, <bare selector>) by (labels) mislowers: implicit Sum eats the partition labels #30

Description

@zzylol

Summary

topk(k, <bare instant selector>) by (labels) — e.g. topk(3, http_requests_total) by (job) ("top-3 series per job") — does not lower correctly. This is a regression surfaced reviewing PR #18 (which removed the L3 Partition node and routed per-group ranking onto Sort.partition_by).

Repro

topk(3, http_requests_total) by (job)
→ column resolution failed: column `job` not found in schema (have: ["sum"])

Root cause

In crates/lower/src/promql.rs, build's Outer::TopK else-branch (the non-heavy-hitter path) defaults a bare selector argument (inner.func == None) to an implicit cross-series AggFunc::Sum:

let func = match &inner.func {
    Some(f) => inner_func(f),
    None => AggFunc::Sum,   // ← here
};
let base = windowed_aggregate(inner, vec![], func);

That reducing Sum collapses every label (including job) into a single sum column, so when the converter then resolves Sort.partition_by = [job] positionally against the child schema (lower.rs), job no longer exists → resolution error.

It is also semantically wrong independent of the by failure: PromQL topk(3, http_requests_total) ranks the raw instant-vector samples — it does not sum them.

Expected L3 shape

A bare selector should rank its own (label-preserving) samples:

topk(3, http_requests_total) by (job)
→ Limit { n: 3 } → Sort { keys: [value desc], partition_by: [job] } → Scan(http_requests_total)

This is exactly the shape PR #18's Sort.partition_by was designed for — no Partition node needed. Range-vector-function arguments (topk(3, rate(m[5m])) by (job), topk(3, avg_over_time(m[5m])) by (job)) already work because they produce a label-preserving per-series reduction.

Fix

In the Outer::TopK else-branch, when the argument is a bare instant selector, rank over the filtered_source directly (label-preserving) instead of wrapping it in a Sum. Only a range-vector-function argument gets the per-series reduction.

Tests

  • topk(3, http_requests_total) by (job)Limit → Sort{value desc, partition_by:[job]} → Scan (job preserved).
  • topk(3, http_requests_total) (no by) → ranks raw samples, no implicit Sum.
  • Regression: topk(k, count_over_time(...)) stays heavy-hitter; bottomk(...) stays generic Sort+Limit.

Follow-up to PR #18.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions