Skip to content

Streaming labels values PR 3 - #15347

Merged
tcp13equals2 merged 5 commits into
mainfrom
streaming-labels-values-PR-3
May 22, 2026
Merged

Streaming labels values PR 3#15347
tcp13equals2 merged 5 commits into
mainfrom
streaming-labels-values-PR-3

Conversation

@tcp13equals2

@tcp13equals2 tcp13equals2 commented May 15, 2026

Copy link
Copy Markdown
Contributor

What this PR does

This is the third PR in the streaming labels/values search API stack.

See prometheus/proposals#74.

This PR builds upon #15233 and #15301

This PR wires the querier-side multi-source fan-out that consumes the work from the above PRs.

Reachable but unused - there is no Mimir-internal caller from the HTTP layer — the new methods are exercised only by unit tests in this PR. The user-visible HTTP endpoints will come in a subsequent PR.

Which issue(s) this PR fixes or relates to

Fixes #

Checklist

  • Tests updated.
  • Documentation added.
  • CHANGELOG.md updated - the order of entries should be [CHANGE], [FEATURE], [ENHANCEMENT], [BUGFIX]. If changelog entry is not needed, please add the changelog-not-needed label to the PR.
  • about-versioning.md updated with experimental features.

Note

Medium Risk
Adds new querier-level SearchLabelNames/SearchLabelValues fan-out and limit-clamping logic plus wrapper passthroughs; while covered by unit tests, it touches core query orchestration and could affect search correctness, ordering, and per-tenant limits.

Overview
Adds multiQuerier.SearchLabelNames/SearchLabelValues to fan out streaming label/values search across distributor and block-store, then merge/deduplicate ordered storage.SearchResultSets while enforcing per-tenant Limit via MaxLabelNamesLimit/MaxLabelValuesLimit (including clamp warnings).

Extends memoryTrackingQuerier and lazyquery.LazyQuerier with search pass-through methods that type-assert for search support and otherwise return ErrSearchResultSet, intentionally skipping additional memory tracking on the streaming search path. Comprehensive unit tests cover pass-through behavior, error propagation, ordering/dedup, empty-range short-circuit, and limit-clamp warnings.

Reviewed by Cursor Bugbot for commit fabc599. Bugbot is set up for automated code reviews on this repo. Configure here.

@tcp13equals2 tcp13equals2 added the changelog-not-needed PRs that don't need a CHANGELOG.md entry label May 15, 2026
@tcp13equals2 tcp13equals2 changed the title Streaming labels values pr 3 Streaming labels values PR 3 May 15, 2026
@tcp13equals2 tcp13equals2 mentioned this pull request May 15, 2026
4 tasks
@tcp13equals2
tcp13equals2 force-pushed the streaming-labels-values-PR-2 branch from d0a2d1a to 7331937 Compare May 18, 2026 03:05
@tcp13equals2
tcp13equals2 force-pushed the streaming-labels-values-PR-3 branch 4 times, most recently from a50005a to 06b3a65 Compare May 18, 2026 06:28
@tcp13equals2
tcp13equals2 marked this pull request as ready for review May 18, 2026 06:37
@tcp13equals2
tcp13equals2 requested a review from a team as a code owner May 18, 2026 06:37
@tcp13equals2
tcp13equals2 force-pushed the streaming-labels-values-PR-2 branch from fd387fc to f239bd3 Compare May 19, 2026 01:25
@tcp13equals2
tcp13equals2 force-pushed the streaming-labels-values-PR-3 branch from a1d1b8a to faf7f73 Compare May 19, 2026 02:25
@tcp13equals2
tcp13equals2 requested review from aknuds1 and ldufr May 19, 2026 02:30
@tcp13equals2
tcp13equals2 force-pushed the streaming-labels-values-PR-2 branch from f4e2ea0 to f2c9f0a Compare May 20, 2026 07:01
@tcp13equals2
tcp13equals2 force-pushed the streaming-labels-values-PR-3 branch 2 times, most recently from 4f60313 to 8b402b3 Compare May 21, 2026 01:47
@tcp13equals2
tcp13equals2 force-pushed the streaming-labels-values-PR-2 branch from 6f457cd to bb35558 Compare May 22, 2026 02:11
@tcp13equals2
tcp13equals2 force-pushed the streaming-labels-values-PR-3 branch from 8b402b3 to 46fff02 Compare May 22, 2026 02:18
@tcp13equals2 tcp13equals2 mentioned this pull request May 22, 2026
4 tasks
ldufr
ldufr previously approved these changes May 22, 2026
@tcp13equals2
tcp13equals2 changed the base branch from streaming-labels-values-PR-2 to main May 22, 2026 05:20
@tcp13equals2
tcp13equals2 dismissed ldufr’s stale review May 22, 2026 05:20

The base branch was changed.

tcp13equals2 and others added 5 commits May 22, 2026 13:21
Wires multiQuerier as the cross-source fan-out point for streaming
label/value search. New file pkg/querier/multi_searcher.go declares a
local mimirSearcher interface that matches the Mimir-extended Searcher
signature (extra *streaminglabelvalues.Params argument — see spec
invariant 9 for why params travel separately from the opaque
hints.Filter). distributorQuerier and blocksStoreQuerier (added in
PR #2) both satisfy it.

Per-tenant clamping reuses the existing MaxLabelNamesLimit /
MaxLabelValuesLimit ceilings — search returns label names/values just
like the existing endpoints, so a separate per-tenant cap would be
configuration churn for no real gain (spec § PR #3 left this open;
"reuse" is the spec's preference).

When the clamp fires, an extra warning-only SearchResultSet is added
to the fan-out so the merge primitive surfaces the MaxLimitError via
Warnings(), mirroring how the existing LabelNames/LabelValues path
returns warnings alongside the result.

Time-range short-circuit and child type-assertion failures return
storage.EmptySearchResultSet / storage.ErrSearchResultSet so the merge
composes cleanly without special-casing in callers.

Reachable but unused — no HTTP route yet (PR #4).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ough

Symmetric with the existing LabelNames/LabelValues pass-through.
Memory tracking is intentionally skipped on the search path: the
streaming SearchResultSet is already capped by the per-tenant Limit
applied at the merge layer in multiQuerier, so byte-counting each
emitted Value would be redundant work on the hot path.

When the inner querier does not implement mimirSearcher,
SearchLabelNames/Values returns storage.ErrSearchResultSet with a
descriptive message so callers can distinguish "no inner support"
from a real iteration failure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Straight pass-through to the wrapped querier when it implements the
search interface, matching the existing LabelNames/LabelValues
pass-through pattern. Prometheus's lazySearchResultSet already covers
deferred init at the merge layer, so no extra wrapping is needed here.

The mimirSearcher interface is declared locally as `searcher` in
lazyquery.go to avoid an import cycle on pkg/querier; the Mimir
extension to Prometheus's storage.Searcher (extra
*streaminglabelvalues.Params argument — see spec invariant 9) means
we cannot type-assert to the upstream storage.Searcher directly.

When the wrapped querier does not implement search,
SearchLabelNames/Values returns storage.ErrSearchResultSet so the
absence of support surfaces as an iteration error rather than a panic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t risk

Two small cleanups on the PR-3 surface:

* fanOutSearch took a context.Context that it never used (both call
  sites already had ctx in scope and threaded it through their own
  closures). Drop the unused parameter.

* lazyquery's local `searcher` interface is a manual copy of
  pkg/querier.mimirSearcher, declared locally to avoid an import cycle.
  The runtime type assertion silently falls back to "search not
  supported" if the two definitions ever drift. The existing comment
  noted the cycle but not the consequence; spell out the drift risk so
  a future signature change leaves a breadcrumb here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tcp13equals2
tcp13equals2 force-pushed the streaming-labels-values-PR-3 branch from 46fff02 to fabc599 Compare May 22, 2026 05:22
@tcp13equals2
tcp13equals2 merged commit 8de4d1b into main May 22, 2026
84 checks passed
@tcp13equals2
tcp13equals2 deleted the streaming-labels-values-PR-3 branch May 22, 2026 06:32
@tcp13equals2 tcp13equals2 mentioned this pull request May 25, 2026
4 tasks
tcp13equals2 added a commit that referenced this pull request May 28, 2026
#### What this PR does

This is the 4th PR in the streaming labels/values search API stack.

See prometheus/proposals#74.

This PR builds upon #15233,
#15301 and
#15347

This PR adds three experimental NDJSON-streaming HTTP endpoints on the
querier;

* /api/v1/search/metric_names — searches values of the __name__ label
* /api/v1/search/label_names — searches for label names 
* /api/v1/search/label_values — searches for values of a single label

The HTTP endpoints have been placed behind a feature flag (
-querier.experimental-search-api-enabled ) and this defaults to false.
  
Note - in a subsequent PR will be support for the metadata query param
to decorate metric names, additional documentation, bechmarks and
updates to dashboard mixins.

#### Which issue(s) this PR fixes or relates to

Fixes #<issue number>

#### Checklist

- [x] Tests updated.
- [ ] Documentation added.
- [x] `CHANGELOG.md` updated - the order of entries should be
`[CHANGE]`, `[FEATURE]`, `[ENHANCEMENT]`, `[BUGFIX]`. If changelog entry
is not needed, please add the `changelog-not-needed` label to the PR.
- [ ]
[`about-versioning.md`](https://github.com/grafana/mimir/blob/main/docs/sources/mimir/configure/about-versioning.md)
updated with experimental features.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> New query surface can drive expensive label scans across
ingesters/store-gateways; mitigated by default-off flag, limits, and
streaming, but multi-tenant federation fan-out increases blast radius
when enabled.
> 
> **Overview**
> Adds **experimental** Prometheus-style **streaming label/value
search** on the querier: NDJSON endpoints at
`/api/v1/search/{metric_names,label_names,label_values}`, gated by
**`-querier.experimental-search-api-enabled`** (default off). When
disabled, callers get **404** `feature_not_enabled`.
> 
> The new **`search_handler`** layer parses query params (fuzz, sort,
limits, `match[]` OR-union), opens a **`mimirSearcher`**, streams
batched NDJSON with success/error trailers, and sets **`has_more`**
using a limit+1 probe plus typed **`MaxLimitError`** clamp warnings.
**`MaxLimitError`** is refactored so handlers can read which limit was
enforced without parsing messages.
> 
> Routes are wired in **`pkg/api`** and **`handlers.go`** with usage
stats. **Tenant federation** **`merge_queryable`** fans out
**`SearchLabelNames` / `SearchLabelValues`**, merges result sets, and
special-cases synthetic **`__tenant_id__`** (and retain-prefix) label
values like existing **`LabelValues`**.
> 
> Changelog and config docs flag the feature as experimental.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
bc74dda. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tcp13equals2 added a commit that referenced this pull request May 28, 2026
#### What this PR does

This is the fifth PR in the streaming label/value search API
implementation.

See also #15233,
#15301,
#15347,
#15349.

This PR focuses on adding support for the `include_metadata` enrichment
on results from ` /api/v1/search/metric_names`.

This feature allows for metric metadata to be included in the metric
name results. This allows for a client to avoid needing to make a
separate API call to retrieve metadata records.

It should be noted that in Mimir, only the ingesters maintain metric
metadata information - and only if it has been included in the remote
write. This metadata information is only maintained in the ingesters for
a short period of time (10 minutes).

To maintain simplicity, this implementation only decorates in the most
recent metadata record for a metric. This is regardless of any time
range submitted in the search. In the future if Mimir was persist or
maintain metadata for longer this implementation choice could be
reviewed.

No metadata is included if the search time range only requires
store-gateways to full-fill the request. This is intentional since "old"
metrics which have not been seen (metadata record pushed) by an ingester
in the last 10 minutes will not have a metadata in Mimir.

#### Which issue(s) this PR fixes or relates to

Fixes #<issue number>

#### Checklist

- [x] Tests updated.
- [ ] Documentation added.
- [x] `CHANGELOG.md` updated - the order of entries should be
`[CHANGE]`, `[FEATURE]`, `[ENHANCEMENT]`, `[BUGFIX]`. If changelog entry
is not needed, please add the `changelog-not-needed` label to the PR.
- [ ]
[`about-versioning.md`](https://github.com/grafana/mimir/blob/main/docs/sources/mimir/configure/about-versioning.md)
updated with experimental features.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Additive, flag-gated experimental API behavior with ingester-only
enrichment; no auth or ingestion path changes.
> 
> **Overview**
> Adds optional **`include_metadata`** to the experimental streaming
search API so **`/api/v1/search/metric_names`** can return **type**,
**help**, and **unit** on each metric without a separate metadata call.
> 
> The flag is parsed on the querier HTTP handler, carried on
**`streaminglabelvalues.Params`**, and sent to ingesters as
**`include_metadata`** on **`SearchLabelValues`** when searching
**`__name__`**. Ingesters attach **`MetricMetadata`** per batch (most
recent in-memory record per metric on that replica); the distributor
maps wire metadata into **`storage.SearchResult`**, and the metric-names
NDJSON builder emits the extra fields. **Store-gateway–only** searches
stay un-enriched by design.
> 
> Also tightens search plumbing: shared **`parseBoolParam`**,
**`buildSearchHints`** rejects negative limits and clamps huge values,
and dev **`mimir.yaml`** turns on **`experimental_search_api_enabled`**.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
1b2c64f. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tcp13equals2 added a commit that referenced this pull request Jun 4, 2026
#### What this PR does

This is the 6th PR in the streaming label/value search API
implementation.

See also #15233,
#15301,
#15347,
#15349 and
#15364.

This PR adds a README.md to assist in documenting this implementation,
benchmark test files and some enhancements found whilst running the
newly added benchmarks.

No changelog has been recorded since these are internal implementation,
testing and documentation changes with no user facing modifications.

#### Benchmarks: legacy `LabelValues` vs new `SearchLabelValues`
Apple M4 Pro, `go test -benchmem -benchtime=5s -count=3`, medians of 3
runs reported.
##### Ingester (`pkg/ingester`)
`BenchmarkIngester_LegacyVsSearchLabelValues` — in-process, no RPC
framing.
| Cardinality | Path | ns/op | B/op | allocs/op | Δ time | Δ B/op | Δ
allocs |
| ----------- | ------ | ----------: | ----------: | ---------: |
-------- | -------- | -------- |
| 4 199 | legacy | 211 794 | 90 418 | 4 209 | | | |
| 4 199 | new | 216 027 | 223 578 | 21 | +2% | +147% | **−99.5%** |
| 1 000 003 | legacy | 149 987 546 | 23 942 547 | 1 000 161 | | | |
| 1 000 003 | new | 130 728 720 | 48 140 526 | 158 | **−13%** | +101% |
**−99.98%** |
**Observations**
- At 4 199 values: parity on wall-clock, but allocations drop from 4 209
→ 21 (≈200×).
- At 1 000 003 values: new path is 13% faster, uses 2× the memory, but
allocates **6 330× fewer objects** (158 vs 1 000 161).

The allocation reduction is due to the batch buffer reuse and
synchronous-send which make skipping the clone safe.

```
  q, err := db.Querier(...)                                                                                                                                                                                                              
  defer q.Close()                                                                                                                                                                                                                                    
  ...                                                                                                                                                                                                                                                        
  rs := searcher.SearchLabelValues(ctx, ...)            // result set holds yolo strings                                                                                                                                                              
  defer rs.Close()                                      //                                                                                                                                                                                              
  return streamSearchResults(ctx, rs, stream.Send, ...) // synchronous; blocks until all batches sent
```

The increase in B/op is due to the wrapping of each record in a
`SearchResult{Value, Score}`.

##### Store-gateway (`pkg/storegateway`)
`BenchmarkBucketStoreSearchLabelValuesVsLabelValues` — bucket store,
cold index cache (`worstCaseFetchedDataStrategy`).
| Cardinality | Path | ns/op | B/op | allocs/op | Δ time | Δ B/op | Δ
allocs |
| ----------- | ------ | -------------: | ----------: | ----------: |
-------- | ------ | -------- |
| 1 000 | legacy | 21 346 956 | 13 931 796 | 61 668 | | | |
| 1 000 | new | 21 368 149 | 14 004 859 | 61 681 | +0.1% | +0.5% |
+0.02% |
| 1 000 000 | legacy | 32 330 190 178 | 3 482 e6 | 51 001 391 | | | |
| 1 000 000 | new | 29 567 861 292 | 3 569 e6 | 51 009 234 | **−9%** |
+2.5% | +0.02% |
**Observations**
- At 1 000 values: parity.
- At 1 000 000 values: new path is ~9% faster on time. Memory and
allocation count are essentially unchanged because the bulk of the work
at the store-gateway layer is in the shared TSDB postings/chunk walk.

Although the same SearchResult wrapper is present it is lost in the
other SG B/op noise.

#### Which issue(s) this PR fixes or relates to

Fixes #<issue number>

#### Checklist

- [x] Tests updated.
- [x] Documentation added.
- [ ] `CHANGELOG.md` updated - the order of entries should be
`[CHANGE]`, `[FEATURE]`, `[ENHANCEMENT]`, `[BUGFIX]`. If changelog entry
is not needed, please add the `changelog-not-needed` label to the PR.
- [ ]
[`about-versioning.md`](https://github.com/grafana/mimir/blob/main/docs/sources/mimir/configure/about-versioning.md)
updated with experimental features.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> No user-facing API or behavior changes beyond optional
micro-optimizations on an experimental, flag-gated search path; risk is
mainly benchmark/doc churn and regression in edge fast paths (single
tenant/block, cancel lifecycle).
> 
> **Overview**
> Adds **`docs/internal/streaming-label-value-search/README.md`**, an
internal guide for the experimental streaming label/value search stack
(NDJSON HTTP endpoints, data flow, merge layers, wire batching,
benchmarks).
> 
> **Benchmark coverage** is added across ingester, distributor,
store-gateway, tenant federation, and HTTP handler tests (including
legacy vs new parity benches and distributor merge/dedup scenarios).
> 
> **Performance tweaks** found while benchmarking: HTTP
**`search_handler`** pools NDJSON batch envelopes, splits score vs
no-score record types to avoid `*float64` allocs, and writes a prebuilt
success trailer; **distributor** stores stream cancel funcs on
`ingesterSearchResultSet` instead of per-ingester closures; **tenant
federation** uses `tenantJobsForSearch` (skip map work when no id-label
matcher) and a single-tenant job bypass; **store-gateway** skips
`MergeSearchResultSets` when only one block matches.
Ingester/store-gateway sources link to the new README.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
3990db9. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog-not-needed PRs that don't need a CHANGELOG.md entry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants