Skip to content

fix: warm engine resolves DDSketch _quantile suffix at query time - #110

Merged
zzylol merged 1 commit into
mainfrom
fix/warm-engine-resolves-quantile-suffix
May 8, 2026
Merged

zzylol merged 1 commit into
mainfrom
fix/warm-engine-resolves-quantile-suffix

Conversation

@zzylol

@zzylol zzylol commented May 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes the metric-name mismatch between the agent's DDSketch processor (renames http_latency_mshttp_latency_ms_quantile on the wire by design) and the warm engine's lookup. PromQL callers reference the conceptual unsuffixed name; the warm engine looks it up directly and misses → status=error.

Picked Option 1 (backend self-heals) over alternatives — no controller change needed, wire-side rename convention preserved.

New helper resolve_quantile_metric_alias in SimpleEngine rewrites <metric><metric>_quantile only when:

  1. Query shape is Quantile (only shape whose data lives behind the rename)
  2. Bare metric is NOT registered locally but suffixed variant IS
  3. Rewrite re-parses cleanly

Identifier-aware substitution via [A-Za-z0-9_:] boundary so http_latency_ms_total elsewhere is left alone.

Test plan

  • cargo test -p query_engine_rust --lib — all targeted tests pass; pre-existing failures unchanged
  • New quantile_over_time_resolves_unsuffixed_metric_to_quantile_state reproduces the production failure
  • New non_quantile_query_does_not_rewrite_metric pins shape gating
  • New quantile_query_without_ingest_rename_passes_through pins backwards-compat for deployments without rename

Refs: ProjectASAP/ASAPCollector#46, builds on PR #102 + PR #108.

🤖 Generated with Claude Code

…PromQL queries

The agent's DDSketch processor renames raw input metrics
(`http_latency_ms`) to a sketched-form wire name
(`http_latency_ms_quantile`) before emitting to the warm tier — by
design, so warm-tier routing distinguishes raw from sketched form.
But the replay client / PromQL caller still references the
conceptual unsuffixed metric in `quantile_over_time(q, X[range])`,
so the warm engine sees a query for `X` while its sketch store
only holds `X_quantile`. Result: the lookup misses and the engine
returns `status=error` to a query that is logically answerable
(matchers + capability dispatch already work given the right name,
per PR #108).

This change picks Option 1 from the design alternatives — backend
self-heals stale routing tables — because it requires no controller
change and keeps the wire-side rename convention intact:

* New helper `resolve_quantile_metric_alias` in `SimpleEngine`
  parses the query, classifies its shape, and rewrites
  `<metric>` → `<metric>_quantile` only when (a) shape is
  `Quantile` (only shapes whose data lives behind the rename),
  (b) the bare metric is NOT registered locally but the suffixed
  variant IS, and (c) the rewrite re-parses cleanly. Deployments
  without the rename pass through unchanged.
* `handle_query_promql` calls the resolver once at entry so every
  downstream stage (pattern match, `QueryConfig` lookup, capability
  matching, `StoreQueryParams.metric`, schema label lookup) sees
  the same name.
* `replace_metric_token` does identifier-aware substitution
  ([A-Za-z0-9_:] boundary check) so a hypothetical
  `http_latency_ms_total` elsewhere in the query is left alone.

Three new regression tests in `warm_engine_replay_regression_tests`
pin the contract:

1. `quantile_over_time_resolves_unsuffixed_metric_to_quantile_state` —
   the production failure case from ProjectASAP/ASAPCollector#46:
   query `quantile_over_time(0.99, http_latency_ms[1m])` against a
   store that only holds `http_latency_ms_quantile` returns the
   DDSketch quantile, not `status=error`.
2. `non_quantile_query_does_not_rewrite_metric` — `sum_over_time`
   against the same store does NOT rewrite (shape gating).
3. `quantile_query_without_ingest_rename_passes_through` — when the
   bare metric IS registered locally (deployment without rename),
   the resolver leaves the query untouched.

Refs: ProjectASAP/ASAPCollector#46
Builds on: PR #102 (DDSketch in `compatible_agg_types(Quantile)`),
PR #108 (warm-tier matcher coverage tests).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol force-pushed the fix/warm-engine-resolves-quantile-suffix branch from 1da79e7 to 159003b Compare May 8, 2026 18:46
@zzylol
zzylol merged commit 408f2b5 into main May 8, 2026
zzylol added a commit that referenced this pull request May 8, 2026
…s status=error) (#111)

The MVP demo's `replay.jsonl` showed the warm-tier engine returning
`status=error` for nearly every query shape — quantile_over_time
(686/686), instant sum (343/343), count-of-HLL (343/343), topk
(342/342), frequency (342/342). PR #110's `_quantile` alias resolver
was correct in isolation but the production deploy hits a downstream
mismatch this PR diagnoses and fixes.

Root cause: the production warm-tier launches with `--streaming-config`
only and no `--config`, so `inference_config.schema = PromQLSchema::new()`
(empty). Two engine paths blow up on this:

1. `build_query_requirements_promql` falls back to
   `KeyByLabelNames::empty()` for `req.grouping_labels` → strict
   `labels_compatible` mismatches every agg config's `[zone]` →
   capability miss → `handle_query_promql` returns `None` →
   `format_unsupported_query_response` writes `status=error`.

2. `build_promql_execution_context_tail` had its own schema lookup
   (`schema.get_labels(metric)`) that returned `None` for empty
   schemas — even after capability matching succeeded — and short-
   circuited the entire query.

Fixes:

- New `SimpleEngine::resolve_metric_labels(metric)` helper that
  prefers the user-supplied schema and falls back to a deterministic
  union of `grouping_labels` across every `StreamingConfig` agg
  matching the metric. Both call sites above now use it, so the
  schema-empty deploy auto-derives sane labels from the controller-
  pushed agg configs.

- `labels_compatible` relaxed from strict-eq to subset (`req ⊆
  config`). The TODO on this function asked for exactly this; the
  MVP demo's `count(metric)` / `topk(K, metric)` queries with no
  `by (...)` modifier translate to `req.grouping_labels = []` and
  must match a `[zone]` agg via the merge path. Direction is
  asymmetric: `req ⊃ config` is still rejected (engine cannot
  invent partitions). The pre-existing `label_strict_superset_rejected`
  test is replaced with `label_superset_config_accepts_subset_query`
  + `label_subset_config_rejects_superset_query` to pin both halves.

- `compatible_agg_types(Statistic::Count)` now lists `HLL`. The
  HLL accumulator answers `Statistic::Count` as a cardinality alias
  (`hll_sketch_accumulator.rs:220`); without HLL in the compat list
  every `count(<HLL-metric>)` capability-missed.

- `compatible_agg_types(Statistic::Topk)` now lists `CountSketch`.
  Symmetric with the Count fix — `count_sketch_accumulator.rs:284`
  answers `Statistic::Topk`. (Note: a standalone CountSketch agg
  still fails downstream because `is_multi_population_value_type`
  requires a paired SetAggregator. The right structural fix for
  `top_endpoint_qps` is for the controller to plan it as
  `CountMinSketchWithHeap`; PR #344 declares that capability on
  the controller side. The engine-side test is `#[ignore]`'d
  with a follow-up note.)

Tests:

- `production_conditions_quantile_over_time_does_not_error`
- `production_conditions_sum_by_zone_instant_does_not_error`
- `production_conditions_count_against_hll_does_not_error`
- `production_conditions_topk_against_count_sketch_does_not_error`
  (`#[ignore]`'d — follow-up; see comment)
- `production_conditions_rate_against_cms_does_not_error`
  (`#[ignore]`'d — CountMinSketchAccumulator has no
  `Statistic::Rate` answer yet)
- `label_superset_config_accepts_subset_query` /
  `label_subset_config_rejects_superset_query`

Each new test seeds `inference_config.schema = PromQLSchema::new()`
to mirror the production warm-tier deploy shape.

Refs ProjectASAP/ASAPCollector#46.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol deleted the fix/warm-engine-resolves-quantile-suffix branch May 9, 2026 18:00
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