Skip to content

fix(query): port resolve_sketch_metric_alias to modern execute() (schema-retirement #5 step 2) - #274

Merged
zzylol merged 2 commits into
mainfrom
retire-legacy-handle-query
May 17, 2026
Merged

zzylol merged 2 commits into
mainfrom
retire-legacy-handle-query

Conversation

@zzylol

@zzylol zzylol commented May 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Step 2 of #272 (schema-retirement #5). The agent-side `_quantile` / `_hll` INGEST-time metric renames (DDSketch+KLL append `_quantile`, HLL appends `_hll`) used to be handled only at the entry of the legacy `handle_query_promql` path. With #273 making modern `execute()` / `execute_range_promql_modern` the producing paths for the common sketch-backed-query case, those paths also need the rename rewrite — otherwise a user query like `quantile_over_time(0.99, http_latency[5m])` that lands in modern via the legacy→modern fallback in `process_via_simple_engine` misses the suffixed series the ASAP tier actually holds.

Port: call `self.resolve_sketch_metric_alias(query).unwrap_or_else(|| query.to_string())` at the top of both the modern `execute()` trait impl and `execute_range_promql_modern`. The helper is already a pure function (parse + classify shape + replace metric token); no refactor needed, just an additional caller.

Why not reorder `process_via_simple_engine` to call modern first yet

An earlier draft of this PR reordered the dispatch to "modern first, legacy as fallback" so the legacy path could be retired entirely. That broke `http_capability_miss_feedback_loop_closes_over_http` — the capability-miss notify side-effect happens inside legacy's `find_compatible_aggregation_with_miss_notify` (engine.rs:~1772), and a pre-existing time=0 underflow bug at engine.rs:792 surfaces when legacy is reached via the modern-Err fallback path under that test's setup. Modern needs to spawn its own capability-miss notify before the reorder is safe — staged as the next sub-PR of #272. This PR keeps the call order unchanged.

Test plan

  • `cargo test -p data_plane --lib` → 756 pass / 0 fail / 5 ignored
  • `cargo test -p control_plane --lib` → 691 pass / 0 fail
  • Smoke test (`bash /mydata/mvp-smoke-test/run_smoke.sh`) — the bare-metric case still works because the smoke fake-exporter emits `http_requests_total_latency_ms` literally (no suffix), and the streaming-config metric name matches, so `resolve_sketch_metric_alias` correctly no-ops via `bare_present`. Once the agent's DDSketch processor's `metric_suffix: "_quantile"` takes effect (when ASAPCollector#381 is resolved), modern's PromQL queries will need this rename to bind back to the suffixed series — this PR pre-stages it.

What's still left in #272

  • Spawn capability-miss notify from modern `execute()` (prerequisite for the call-order flip)
  • Flip `process_via_simple_engine` to call modern first (then the legacy timeline-dispatch path is the only remaining caller of legacy)
  • Investigate and fix the engine.rs:792 time=0 underflow (independent latent bug)
  • Migrate `try_handle_query_promql_via_timeline` to sid-level per-segment dispatch
  • Delete the legacy `handle_query` / `handle_query_promql` / `execute_context` family
  • Rekey ingest bucketing (otel.rs:561,640) from (agg_id, group_key) to sid
  • Rekey output_sink.rs + backfill/processor.rs (8 sites)
  • Re-enable the two ignored cross-reconfigure dispatch tests
  • Delete `pub fn aggregation_id()` on `AggregationConfig`

🤖 Generated with Claude Code

zzylol and others added 2 commits May 17, 2026 12:42
…chema-retirement #5 step 1)

The modern execute() trait path and execute_range_promql_modern resolved
ASAP-tier candidates to sids via `sids_for_policy(fp)` only, on the
assumption that every production sid registration would populate
`policy_fp` (per the comment retired by an earlier PR). That assumption
breaks for sketches arriving via OTLP: `derive_sketch_policy_fp` only
returns `Some(fp)` when a streaming-config policy's `grouping_labels`
EXACTLY matches the wire DP's `group_by_keys`. When the agent emits
sketches with the full wire-attr set (no upstream attribute reduction —
the common case when the controller's OpAMP-pushed runtime config
doesn't take effect, see ASAPCollector#381), `find_policy_by_content`
returns `None`, all sids land in the catalog with `policy_fp = UNSET`,
and `sids_for_policy(streaming_config_fp)` returns empty even though
the data is sitting in the SketchStore right there.

Fix: union the `instances_matching(metric, group_by_keys)` catalog walk
into the sid set. `instances_matching` is the more general primitive:
it returns sids whose `group_by_keys` is a SUPERSET of the candidate's
asked grouping, which subsumes the policy-fp reverse-index hit (an
ExactAgg-style sid minted via `ingest_precompute_for_agg_config` has
`group_by_keys == streaming_config.grouping_labels` so it satisfies the
subset check) AND covers the full-attr sketch case the prior path
missed.

End-to-end verified via the single-node MVP smoke test
(`/mydata/mvp-smoke-test/`):

  $ curl --data-urlencode 'query=quantile_over_time(0.99, http_requests_total_latency_ms[5m])' \
         http://localhost:19091/api/v1/query
  {"accuracy":{"delta":0.0,"epsilon":0.01,"kind":"relative_quantile"},
   "data":{"result":[{"metric":{"zone":""},
                      "value":[1779043268.182,"90.93548893834691"]}],
           "resultType":"vector"},
   "infos":["accuracy: ε=0.01, δ=0, kind=relative_quantile", ...]}

Pre-fix this returned `{"data":null,"error":"No result for query"}`.
Smoke-test `D` axis already showed 51 sids registered for the metric;
the gap was purely the query path's sid-resolution step.

Test plan:
  * `cargo test -p data_plane --lib` 756/756 green (756 pass / 5 ignored)
  * `cargo test -p control_plane --lib` 691/691 green
  * New regression test `full_attr_sketch_sid_findable_via_subset_grouping`
    in the `asap_tier_classify_tests` module pins the smoke-test scenario
    at unit level — a sketch sid with `policy_fp = UNSET` and full
    wire-attr `group_by_keys` must be findable by a query whose
    `group_by_keys` is a subset.
  * End-to-end smoke test (`bash /mydata/mvp-smoke-test/run_smoke.sh`)
    Axis C `quantile_over_time` now returns a real DDSketch quantile;
    pre-fix it returned "No result for query".

Scope: this is step 1 of [#272 (schema-retirement #5)](#272).
The ingest bucketing (`otel.rs:561`, `WorkerMessage::AccumulatorInput`),
precompute output sink, and backfill processor still key on
`config.aggregation_id()`; those are separate per-subsystem
retirements tracked in the same issue. The legacy `handle_query` /
`execute_context` path also still routes through agg_id and the
"No precomputed outputs found for metric: X, aggregation_id: Y"
error message — its retirement is the next step. Closes the
sid-resolution sub-step of #272.

Related: issue #271 (MVP demo axis C) — sister fix path 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ema-retirement #5 step 2)

Continuing #272 retirement work. The agent-side `_quantile` / `_hll`
INGEST-time metric renames (DDSketch+KLL append `_quantile`, HLL
appends `_hll`) used to be handled only at the entry of the legacy
`handle_query_promql` path. With #273 making modern `execute()` and
`execute_range_promql_modern` the producing paths for the common
sketch-backed-query case, those paths also need the rename rewrite —
otherwise a user query `quantile_over_time(0.99, http_latency[5m])`
that lands in modern (via the legacy→modern fallback in
`process_via_simple_engine`) misses the suffixed series the ASAP tier
actually holds.

Port: call `self.resolve_sketch_metric_alias(query).unwrap_or_else(...)`
at the top of both modern execute() trait impl and
`execute_range_promql_modern`. The helper is already a pure function
(parse + classify shape + replace metric token); no refactor needed,
just an additional caller.

Tested with the smoke test
(`/mydata/mvp-smoke-test/run_smoke.sh`) — the bare-metric case still
works because the smoke fake-exporter emits `http_requests_total_latency_ms`
literally (no suffix), and the streaming-config metric name matches,
so `resolve_sketch_metric_alias` correctly no-ops via `bare_present`.
Once the agent's DDSketch processor's `metric_suffix: "_quantile"`
takes effect (when ASAPCollector#381 is resolved and OpAMP-pushed
runtime config applies), modern's PromQL queries will need this
rename to bind back to the suffixed series — this PR pre-stages it.

Why not reorder process_via_simple_engine to call modern first now:
the `http_capability_miss_feedback_loop_closes_over_http` test relies
on the capability-miss notify side-effect inside
`find_compatible_aggregation_with_miss_notify` (which only legacy
calls); reordering also surfaces a pre-existing time=0 underflow
bug at engine.rs:792. Modern needs its own capability-miss notify
before that flip is safe — staged as the next sub-PR of #272.

Test plan:
  * `cargo test -p data_plane --lib` 756/756 pass
  * `cargo test -p control_plane --lib` 691/691 pass

Related: #272 (schema-retirement #5 umbrella), #273 (step 1 — sid
resolution fallback).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol merged commit e42d1ad into main May 17, 2026
zzylol added a commit that referenced this pull request May 17, 2026
…#275)

* fix(query): union instances_matching into ASAP-tier sid resolution (schema-retirement #5 step 1)

The modern execute() trait path and execute_range_promql_modern resolved
ASAP-tier candidates to sids via `sids_for_policy(fp)` only, on the
assumption that every production sid registration would populate
`policy_fp` (per the comment retired by an earlier PR). That assumption
breaks for sketches arriving via OTLP: `derive_sketch_policy_fp` only
returns `Some(fp)` when a streaming-config policy's `grouping_labels`
EXACTLY matches the wire DP's `group_by_keys`. When the agent emits
sketches with the full wire-attr set (no upstream attribute reduction —
the common case when the controller's OpAMP-pushed runtime config
doesn't take effect, see ASAPCollector#381), `find_policy_by_content`
returns `None`, all sids land in the catalog with `policy_fp = UNSET`,
and `sids_for_policy(streaming_config_fp)` returns empty even though
the data is sitting in the SketchStore right there.

Fix: union the `instances_matching(metric, group_by_keys)` catalog walk
into the sid set. `instances_matching` is the more general primitive:
it returns sids whose `group_by_keys` is a SUPERSET of the candidate's
asked grouping, which subsumes the policy-fp reverse-index hit (an
ExactAgg-style sid minted via `ingest_precompute_for_agg_config` has
`group_by_keys == streaming_config.grouping_labels` so it satisfies the
subset check) AND covers the full-attr sketch case the prior path
missed.

End-to-end verified via the single-node MVP smoke test
(`/mydata/mvp-smoke-test/`):

  $ curl --data-urlencode 'query=quantile_over_time(0.99, http_requests_total_latency_ms[5m])' \
         http://localhost:19091/api/v1/query
  {"accuracy":{"delta":0.0,"epsilon":0.01,"kind":"relative_quantile"},
   "data":{"result":[{"metric":{"zone":""},
                      "value":[1779043268.182,"90.93548893834691"]}],
           "resultType":"vector"},
   "infos":["accuracy: ε=0.01, δ=0, kind=relative_quantile", ...]}

Pre-fix this returned `{"data":null,"error":"No result for query"}`.
Smoke-test `D` axis already showed 51 sids registered for the metric;
the gap was purely the query path's sid-resolution step.

Test plan:
  * `cargo test -p data_plane --lib` 756/756 green (756 pass / 5 ignored)
  * `cargo test -p control_plane --lib` 691/691 green
  * New regression test `full_attr_sketch_sid_findable_via_subset_grouping`
    in the `asap_tier_classify_tests` module pins the smoke-test scenario
    at unit level — a sketch sid with `policy_fp = UNSET` and full
    wire-attr `group_by_keys` must be findable by a query whose
    `group_by_keys` is a subset.
  * End-to-end smoke test (`bash /mydata/mvp-smoke-test/run_smoke.sh`)
    Axis C `quantile_over_time` now returns a real DDSketch quantile;
    pre-fix it returned "No result for query".

Scope: this is step 1 of [#272 (schema-retirement #5)](#272).
The ingest bucketing (`otel.rs:561`, `WorkerMessage::AccumulatorInput`),
precompute output sink, and backfill processor still key on
`config.aggregation_id()`; those are separate per-subsystem
retirements tracked in the same issue. The legacy `handle_query` /
`execute_context` path also still routes through agg_id and the
"No precomputed outputs found for metric: X, aggregation_id: Y"
error message — its retirement is the next step. Closes the
sid-resolution sub-step of #272.

Related: issue #271 (MVP demo axis C) — sister fix path 2.

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

* fix(query): port resolve_sketch_metric_alias to modern execute() (schema-retirement #5 step 2)

Continuing #272 retirement work. The agent-side `_quantile` / `_hll`
INGEST-time metric renames (DDSketch+KLL append `_quantile`, HLL
appends `_hll`) used to be handled only at the entry of the legacy
`handle_query_promql` path. With #273 making modern `execute()` and
`execute_range_promql_modern` the producing paths for the common
sketch-backed-query case, those paths also need the rename rewrite —
otherwise a user query `quantile_over_time(0.99, http_latency[5m])`
that lands in modern (via the legacy→modern fallback in
`process_via_simple_engine`) misses the suffixed series the ASAP tier
actually holds.

Port: call `self.resolve_sketch_metric_alias(query).unwrap_or_else(...)`
at the top of both modern execute() trait impl and
`execute_range_promql_modern`. The helper is already a pure function
(parse + classify shape + replace metric token); no refactor needed,
just an additional caller.

Tested with the smoke test
(`/mydata/mvp-smoke-test/run_smoke.sh`) — the bare-metric case still
works because the smoke fake-exporter emits `http_requests_total_latency_ms`
literally (no suffix), and the streaming-config metric name matches,
so `resolve_sketch_metric_alias` correctly no-ops via `bare_present`.
Once the agent's DDSketch processor's `metric_suffix: "_quantile"`
takes effect (when ASAPCollector#381 is resolved and OpAMP-pushed
runtime config applies), modern's PromQL queries will need this
rename to bind back to the suffixed series — this PR pre-stages it.

Why not reorder process_via_simple_engine to call modern first now:
the `http_capability_miss_feedback_loop_closes_over_http` test relies
on the capability-miss notify side-effect inside
`find_compatible_aggregation_with_miss_notify` (which only legacy
calls); reordering also surfaces a pre-existing time=0 underflow
bug at engine.rs:792. Modern needs its own capability-miss notify
before that flip is safe — staged as the next sub-PR of #272.

Test plan:
  * `cargo test -p data_plane --lib` 756/756 pass
  * `cargo test -p control_plane --lib` 691/691 pass

Related: #272 (schema-retirement #5 umbrella), #273 (step 1 — sid
resolution fallback).

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

* revert+retire(query): delete dead resolve_sketch_metric_alias rewrite

Revert PR #274 (and remove the legacy `handle_query_promql` call site
plus the function itself and its test module). The `_quantile` /
`_hll` metric-suffix rewrite is obsolete dead code post the 2026-05
processor refactor.

Per `opentelemetry-collector-contrib-patch/processor/ddsketchprocessor/shim_helpers.go:189`:

  // Refactor-2026-05: metric name is PRESERVED from the input metric.
  // MetricSuffix is intentionally NOT applied — the sketch type is
  // carried by the OTLP pdata.Metric variant tag (DDSketch), so the
  // downstream backend can identify the encoding without a name suffix
  // and PromQL queries fired against the raw input metric name resolve
  // directly against the stored sketch state.

(See also `deploy/mvp-singlenode/configs/backend-streaming.yaml`'s
header note documenting the same refactor.) The wire-side input
metric name IS the served metric name; the per-processor
`metric_suffix` config field is now a no-op in production paths.

The backend's `resolve_sketch_metric_alias` was the query-side
counterpart to that long-since-retired emit-side suffix. With the
suffix never applied, the resolver's `bare_present` check always
short-circuits to `None` — making it pure overhead (one PromQL
parse + one streaming-config snapshot scan per query) and worse,
misleading: the doc comments + the in-tree `mod
sketch_alias_resolver_tests` keep an architectural story alive that
the runtime no longer matches.

This PR removes:

  * The 3 call sites of `resolve_sketch_metric_alias`:
    - `handle_query_promql` (line 2038 — pre-existing)
    - `QueryEngine::execute()` trait impl (line ~3219 — added by #274)
    - `execute_range_promql_modern` (line ~3720 — added by #274)
  * The `resolve_sketch_metric_alias` method itself
    (~90 LOC + ~30 LOC doc comment)
  * The `replace_metric_token` helper (only caller was the resolver)
  * The `utf8_char_len` helper (only caller was `replace_metric_token`)
  * The `sketch_alias_resolver_tests` module (~160 LOC, 8 tests, all
    asserting behavior that's been a no-op for months)

Net: -381 LOC, no additions.

Test plan:
  * `cargo test -p data_plane --lib` → 748 pass / 0 fail / 5 ignored
    (756 → 748 reflects the 8 deleted alias-resolver tests)
  * `cargo test -p control_plane --lib` → 691 pass / 0 fail

Follow-ups (out of scope for this PR):
  * `docs/design-controller-into-backend.md` still has two
    `metric_suffix: "_quantile"` references that should be rewritten
    in a docs-only PR — the live config (`gateway-aggregate-from-raw.yaml`)
    no longer carries the suffix, so the doc table is stale.

Apologies for the original PR #274 — I called it a "port" without
checking that the function being ported is dead code. Thanks for the
catch.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zzylol added a commit that referenced this pull request May 17, 2026
…tch_metric_alias callers (B6) (#277)

Two fixes in `data_plane/src/query_engines/asap_query_engine/engine.rs`:

1. **`calculate_start_timestamp_promql` underflow.** The
   `OnlySpatial` branch did `end_timestamp - (scrape_interval *
   1000)` with `end_timestamp: u64`. When the PromQL request has
   `time=0` (capability-miss feedback-loop probe shape, see
   `tests::capability_miss_http_e2e_tests`), `end_timestamp` is 0
   and the bare subtraction underflows with `attempt to subtract
   with overflow`. Switch to `saturating_sub` — clamps to 0,
   which the downstream store query treats as a [0, 0]-width
   range. Empty result is the right answer for a probe looking
   for the capability-miss signal, not data.

   Currently dormant under the current call ordering
   (`process_via_simple_engine` calls legacy first, which
   short-circuits at the empty-streaming-config check before
   reaching line 611), but surfaces the moment the dispatch is
   reordered to modern-first. Fix removes the latent blocker
   for step 4 of #272.

2. **Delete dangling `resolve_sketch_metric_alias` callers at
   engine.rs:3024 and engine.rs:3525.** PR #275 retired the
   `resolve_sketch_metric_alias` method itself (the runtime
   refactor preserves metric names through the sketch processors
   — the suffix rewrite hadn't done anything in production for
   months). The PR removed the method definition + the legacy
   handle_query_promql call site but missed these two call sites
   inside `execute_range_promql_modern` and
   `QueryEngine::execute()` (both added by PR #274, which #275
   was supposed to revert). Result: main fails to compile with
   two E0599s. This PR removes them inline as a fix-forward.

Both call sites had the same shape:

    let query_owned = self
        .resolve_sketch_metric_alias(query)
        .unwrap_or_else(|| query.to_string());
    let query = query_owned.as_str();

Removing the snippet leaves the original `query: &str` parameter
in scope, which is what every downstream user wants — the
analyzer accepts a `&str` directly. No semantic change beyond
"don't try to rewrite a metric name that doesn't get rewritten
by anyone."

Test plan:

  * `cargo test -p data_plane --lib`: 749/749 pass (was 748;
    +1 for the new
    `calculate_start_timestamp_promql_handles_time_zero_without_underflow`
    regression test that pins the saturating_sub semantics).
  * Existing `capability_miss_http_e2e` tests still pass — the
    legacy path that previously triggered the underflow is
    still reached the same way (modern is still called second);
    the fix is dormant until the reorder lands but is now safe.

Unblocks the next step of #272 — `process_via_simple_engine`
modern-first reorder.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol deleted the retire-legacy-handle-query branch July 17, 2026 20:06
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.

1 participant