Skip to content

fix(multinode): harness debt — queries-e2e ranges, b0/b1 query endpoint, aggregate_report - #396

Merged
zzylol merged 1 commit into
mainfrom
fix/multinode-harness-debt
May 18, 2026
Merged

zzylol merged 1 commit into
mainfrom
fix/multinode-harness-debt

Conversation

@zzylol

@zzylol zzylol commented May 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Three orthogonal fixes for the multinode harness, surfaced by a clean post-#395 all-arms run:

  • Fix 1 — queries-e2e.json [1m][5m] (3 entries). The agent window is 60s, backend tumbling window 30s, soak 90s — [1m] ranges fired early in the soak frequently land between open windows and return empty. [5m] matches the convention used everywhere else (smoke test, manual probes, PR fix(deploy): wire controller into multinode harness + replay typo + wave queries + stale bucket key #394 wave queries). quantile by (zone) (...) left alone — no range arg.
  • Fix 2 — b0/b1 query endpoint: point arm_measure at VictoriaMetrics :8428, not Prometheus :9090. The harness brings up asap-victoriametrics (not Prometheus) on node2 for b0/b1 (see backend_up), and the agents PRW to victoriametrics:8428/api/v1/write. :9090 was unreachable → 100% of 899 attempts failed with http_code: null (urllib URLError → status: timeout).
  • Fix 3 — deploy/mvp-multinode/scripts/aggregate_report.py (new). Fills the missing script run_demo.sh::all referenced. Pure stdlib; handles the multinode dir shape (per-arm subdir with replay.jsonl + edge-<node>.csv + stages-<node>.csv). Emits a summary table, per-query success/empty/error breakdown, edge bandwidth and stages aggregates. Idempotent; failure stays non-fatal.

Fix 2 — root cause + evidence

Live probe of a fresh b0 arm:

$ curl -sm 5 http://10.10.1.3:9090/-/healthy           # exit 7 (connection refused)
$ curl -sm 5 http://10.10.1.3:8428/health              # "OK"
$ ssh node2 'docker ps --format ...'
  asap-victoriametrics: Up ... (image=victoriametrics/victoria-metrics:v1.110.0)
$ curl -sm 5 -G http://10.10.1.3:8428/api/v1/query --data-urlencode 'query=count(http_requests_total)'
  {"status":"success","data":{"result":[{"value":[...,"10000"]}]}, ...}
$ curl -sm 5 -G http://10.10.1.3:8428/api/v1/query --data-urlencode 'query=sum by (zone) (rate(http_requests_total[5m]))'
  {"status":"success","data":{"result":[
    {"metric":{"zone":"z0"},"value":[...,"249990.58..."]},
    {"metric":{"zone":"z1"},"value":[...,"249990.57..."]},
    {"metric":{"zone":"z2"},"value":[...,"249990.06..."]},
    {"metric":{"zone":"z3"},"value":[...,"249989.40..."]}]}}

Quick 15s replay against :8428 returned 78/138 success — the 60 empties are the 3 *_latency_ms queries (no such metric in the b0 workload).

Validation — full all-arms run after fixes

/mydata/mvp-multinode/results/mvp-multinode-20260518-113420/MVP_REPORT.md:

Arm Total Success Empty Error
asap 413 332 0 81
b0 829 472 357 0
b1 835 476 359 0

Before this PR (same harness, same workload): asap 354/830, b0 0/899, b1 0/899.

asap arm — wave queries from #291/#292/#394 unchanged (no regression):

Query Total Success
sum by (zone) (http_requests_total) 59 59
sum by (zone) (rate(http_requests_total[5m])) 59 59
topk(5, sum by (zone) (rate(http_requests_total[5m]))) 59 59
sum_over_time(http_requests_total[5m]) 59 59

asap arm — [5m]-changed queries:

Query Total Success Was ([1m])
quantile_over_time(0.99, http_requests_total_latency_ms[5m]) 59 48 0/119
quantile_over_time(0.5, http_requests_total_latency_ms[5m]) 59 48 0/119
sum_over_time(http_requests_total[5m]) 59 59 0/119

The remaining error/200=81 on asap are 59 × quantile by (zone) (...) (unsupported PromQL syntax against asap-query-backend, pre-existing) + 11+11 quantile_over_time calls that landed in the first 30s before the first window closed.

b0/b1 arms — wave queries:

Query b0 success/total b1 success/total
sum by (zone) (http_requests_total) 118/118 119/119
sum by (zone) (rate(http_requests_total[5m])) 118/118 119/119
sum_over_time(http_requests_total[5m]) 118/118 119/119
topk(5, sum by (zone) (rate(http_requests_total[5m]))) 118/118 119/119

The 357/359 "empty" results on b0/b1 are the 3 *_latency_ms queries — the workload only emits http_requests_total, so latency_ms returns [] (success / 200 / empty vector, not an error). MVP_REPORT.md correctly classifies these as empty not error.

Test plan

🤖 Generated with Claude Code

…nt, aggregate_report

Three orthogonal fixes surfaced by a clean post-#395 all-arms run:

1. queries-e2e.json: change [1m] → [5m] for the three *_over_time
   entries (quantile_over_time x2, sum_over_time x1). The agent
   window is 60s, backend tumbling window 30s, multinode soak 90s
   — a [1m] range fired early in the soak frequently lands between
   open windows and returns empty. Convention used everywhere else
   (smoke test, manual probes, #394 wave queries) is [5m]. Leave
   `quantile by (zone) (...)` alone — no range arg.

2. run_demo.sh::arm_measure: point the b0/b1 PromQL replay endpoint
   at VictoriaMetrics :8428, not Prometheus :9090. Diagnosis: the
   harness brings up `asap-victoriametrics` (not Prometheus) on
   node2 for b0/b1 (see backend_up, line 104–109), and the agents
   PRW to `http://victoriametrics:8428/api/v1/write`. The query
   endpoint at :9090 was unreachable — `curl http://10.10.1.3:9090`
   → connection refused → urllib URLError → `status: timeout`
   → 100% of 899 attempts failed with http_code=null. Live probe
   of :8428 against a fresh b0 arm returns 10000 series of
   http_requests_total and resolves `sum by (zone) (rate(...[5m]))`
   into 4 zones with real values; a 15s replay yielded 78/138
   success (the 60 empties are the 3 *_latency_ms queries — that
   metric isn't in the b0 workload).

3. New `deploy/mvp-multinode/scripts/aggregate_report.py` to fill
   the missing script run_demo.sh::all referenced. Pure stdlib,
   handles the multinode dir shape (per-arm subdir containing
   replay.jsonl + edge-<node>.csv + stages-<node>.csv). Emits a
   summary table, per-query success/empty/error breakdown, edge
   bandwidth and stages aggregates. Idempotent. Failure stays
   non-fatal (run_demo.sh swallows it).

Validation (all-arms run after fixes):

  Arm   Total  Success  Empty  Error
  asap    413      332      0     81
  b0      829      472    357      0
  b1      835      476    359      0

- asap wave queries (sum-by-zone, sum-by-zone-rate, topk-of-rate,
  sum_over_time): 59/59 each → #395 wave-query gate not regressed.
- asap [5m] quantile_over_time: 48/59 (was 0/119 with [1m]) →
  Fix 1 verified.
- b0/b1: 472/829 + 476/835 (was 0/899 + 0/899) → Fix 2 verified.
  The 357/359 "empty" results are the 3 *_latency_ms queries —
  the b0/b1 workload's producer emits only http_requests_total,
  so latency_ms returns [] (not an error).
- MVP_REPORT.md emitted at run-dir root with full per-arm tables
  → Fix 3 verified.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol merged commit e6387c2 into main May 18, 2026
zzylol added a commit that referenced this pull request May 18, 2026
…ries-e2e wave queries (#397)

Three harness gaps closed (against asap arm post-#396 baseline 332/413):

1. WARMUP_S 30 → 60s in `topology.env`. The 30s warmup wasn't long enough
   for the agent's first 30s tumbling window to close + flush to backend
   before replay started. Result: 14 leading "error/200" on 4 queries
   per arm.

2. `deploy/mvp-multinode/configs/asap/mvp-workload.yaml` was missing
   `grouping_labels: [zone]` on all 8 entries. ASAPCollector PR #390
   added these to the singlenode workload but the multinode equivalent
   was never swept. Without grouping_labels, the controller's
   `attributes/keep` processor isn't injected, the agent emits full-attr
   sketches, the backend stores per-(zone,rack,node,pod) sids, and
   queries return per-series instead of per-zone.

3. Replaced broken `quantile by (zone) (0.99, http_requests_total_latency_ms)`
   in `queries-e2e.json` with
   `max by (zone) (quantile_over_time(0.99, http_requests_total_latency_ms[5m]))`.
   Original used PromQL's instant `quantile()` aggregator which expects
   scalar values across series — incompatible with DDSketch state.
   The replacement is valid PromQL on both raw and sketch tiers: inner
   computes per-series p99 over 5min (or per-zone p99 directly on asap
   thanks to the new grouping_labels), outer `max by (zone)` is the
   per-zone worst-case rollup (SLO-relevant tail).
   `histogram_quantile` was considered but doesn't apply — the
   fake-exporter emits a Float64Gauge per fake-exporter/main.go:446,
   not a Prometheus histogram with `_bucket{le=...}` series.

Results post-fix on asap arm: 6/7 wave queries now 100% success
(was 4/7 with 14-error leading pattern on the other 3). Baselines b0/b1
unchanged at 100%+empty on the queries the producer emits data for.

Known follow-up (asap engine limitation, not harness debt): the new
`max by (zone) (quantile_over_time(...))` query still errors on asap
because the engine doesn't compose outer aggregation operators on
function results — even when the inner already returns per-zone (which
makes the outer a no-op identity). The same query succeeds 118/118 on
b0/b1 via VictoriaMetrics. Worth a separate ASAPQuery-backend PR.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol deleted the fix/multinode-harness-debt branch July 17, 2026 20:08
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