Skip to content

fix(lower): topk over a bare selector preserves by-labels (#30) - #31

Merged
zzylol merged 1 commit into
mainfrom
fix/topk-bare-selector-partition
Jul 2, 2026
Merged

fix(lower): topk over a bare selector preserves by-labels (#30)#31
zzylol merged 1 commit into
mainfrom
fix/topk-bare-selector-partition

Conversation

@zzylol

@zzylol zzylol commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

What & why

Fixes #30 — a follow-up to PR #18 (which removed the L3 Partition node and routed per-group ranking onto Sort.partition_by).

topk(k, <bare instant selector>) by (labels) — e.g. topk(3, http_requests_total) by (job) ("top-3 series per job") — mislowered:

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

The non-heavy-hitter path in build's Outer::TopK else-branch defaulted a bare-selector argument (inner.func == None) to an implicit cross-series AggFunc::Sum. That reducing Sum collapsed every label (including the by partition keys), so Sort.partition_by = [job] no longer resolved at L3. It was also semantically wrong — PromQL topk ranks the raw instant-vector samples, it does not sum them.

How

A bare selector now ranks over the filtered_source directly (label-preserving), so Sort.partition_by ranks within each group:

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

A range-vector-function argument (topk(k, rate(m[5m])), topk by (host) (k, avg_over_time(...))) still reduces per series first — also label-preserving — so those paths are unchanged, as are the heavy-hitter count_over_time and bottomk cases.

Tests

  • topk_over_bare_selector_by_label_ranks_per_groupSort.partition_by = [job] over a bare Scan, no implicit Sum.
  • topk_over_bare_selector_ranks_raw_samples — no by → global ranking over raw samples.
  • Full workspace suite green; cargo clippy --all-targets clean.

Closes #30. Follow-up to #18.

🤖 Generated with Claude Code

…ng by-labels (#30)

`topk(k, <bare instant selector>) by (labels)` — e.g.
`topk(3, http_requests_total) by (job)` ("top-3 series per job") — mislowered.
The non-heavy-hitter path in `build`'s `Outer::TopK` else-branch defaulted a
bare selector argument (`inner.func == None`) to an implicit cross-series
`AggFunc::Sum`. That reducing `Sum` collapsed every label (including the `by`
partition keys) into a single `sum` column, so `Sort.partition_by = [job]` no
longer resolved at L3 — a regression surfaced reviewing the Partition →
`Sort.partition_by` reframe (#12, PR #18). It was also semantically wrong:
PromQL `topk` ranks the raw instant-vector samples, it does not sum them.

A bare selector now ranks over the `filtered_source` directly (label-
preserving), so `Sort.partition_by` ranks within each group. A range-vector-
function argument (`topk(k, rate(m[5m]))`) still reduces per series first —
also label-preserving — so those paths are unchanged, as are the heavy-hitter
`count_over_time` and `bottomk` cases.

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

Tests: bare-selector topk by-label (ranks per group, no implicit Sum) and
bare-selector topk without `by` (ranks raw samples).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant