Skip to content

mvp: 5-sketch workload — fake-exporter emits 4 new metrics, replay covers all query classes - #338

Merged
zzylol merged 1 commit into
mainfrom
mvp/5-sketch-workload-and-replay
May 8, 2026
Merged

zzylol merged 1 commit into
mainfrom
mvp/5-sketch-workload-and-replay

Conversation

@zzylol

@zzylol zzylol commented May 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes the gap between today's DDSketch + Sum-only producer signal and the
full 5-sketch MVP demo (issue #46). Four new metrics are added to the
fake-exporter, gated by EXPORTER_FIVE_SKETCH=on/off (default on); the
controller's workload registry is extended with planner hints; and the
replay client's rotation now exercises all six query classes.

Six metrics × six query classes (verbatim contract from issue #46)

metric family query class example query
http_requests_total raw passthrough sum_rate sum by (zone) (rate(http_requests_total[5m]))
http_latency_ms (http_requests_total_latency_ms) DDSketch quantile quantile_over_time(0.99, http_requests_total_latency_ms[1m])
request_size_bytes KLL quantile (rank-err) quantile_over_time(0.99, request_size_bytes[1m])
unique_users_per_min HLL cardinality count(unique_users_per_min)
top_endpoint_qps CountSketch top-K topk(5, top_endpoint_qps)
endpoint_request_freq CountMinSketch frequency rate(endpoint_request_freq[5m])

The first two existed before this PR; the last four are new.

Producer changes (deploy/fake-exporter/)

  • five_sketch_workload.go — wires the four new instruments and starts
    per-outer-series goroutines reusing the same zone/rack/node/pod label
    schema as runSynthetic. Distribution shapes:
    • request_size_bytes: log-normal in [100B, 10KB] — heavy right tail
      so KLL's rank-error metric is the natural oracle.
    • unique_users_per_min: rotating user-id pool of 500–2000 (env
      EXPORTER_FIVE_SKETCH_USER_POOL), rotating every 60s by default
      (env EXPORTER_FIVE_SKETCH_USER_ROTATE) so the active set HLL
      estimates actually slides.
    • top_endpoint_qps / endpoint_request_freq: Zipfian (s=1.2)
      over 50 endpoints (env EXPORTER_FIVE_SKETCH_ENDPOINTS /
      EXPORTER_FIVE_SKETCH_ZIPF_S) so heavy hitters dominate.
  • main.go — calls startFiveSketchWorkload from runSynthetic
    alongside the existing counter + gauge so all six metrics ride the
    same SDK View / PeriodicReader pipeline.

EXPORTER_FIVE_SKETCH=off short-circuits all four; the existing
DDSketch + Sum signal continues unchanged.

Workload spec (deploy/configs/mvp-workload.yaml)

Adds four entries (entries 5–8) keyed by the new metric names with
sketch_family_override + target_path hints (KLL / HLL / CountSketch
/ CountMinSketch, all routed target_path: warm). The current
WorkloadEntry struct in controller/src/config/workloads.rs doesn't
read those two keys yet — serde ignores unknown fields by default,
so the YAML round-trips silently until the parallel
capability_matching + planner work picks them up. Pre-existing entries
(entries 1–4) are untouched.

Replay client (deploy/scripts/promql_replay.py)

  • QUERY_KINDS extended with frequency so rate(...[5m]) queries
    are validated rather than rejected.
  • Result-shape parsing is unchanged — the client logs result_type +
    result verbatim across vector / scalar / matrix shapes, so the
    reducer keys on kind to pick the right oracle.

MVP demo driver (deploy/scripts/run_mvp_demo.sh)

  • replay-queries.json rotation grows from 3 → 7 queries covering all
    six families.
  • Aggregate QPS bumped 5 → 8 (~1.14 QPS/class × 300s soak ≈ 343
    samples/class, ≥100 floor for the accuracy reducer).

Test coverage

  • go test ./deploy/fake-exporter/... — 3 new tests for the
    five-sketch workload (all-metrics-emit, EXPORTER_FIVE_SKETCH=off
    kill switch, env-string parsing) on top of the existing 4. All pass.
  • python3 -m pytest deploy/scripts/tests/test_promql_replay.py — 9
    new tests covering load_queries kind validation + run_query result-
    shape parsing for vector / scalar / topk-vector / rate-vector /
    sum-by-vector / json-error. All pass.

Test plan

  • go test ./deploy/fake-exporter/...
  • python3 -m pytest deploy/scripts/tests/test_promql_replay.py
  • Validate mvp-workload.yaml parses (yaml.safe_load → 8 entries)
  • Validate replay-queries.json parses (json.load → 7 queries)
  • Full MVP demo soak (out of scope per the task brief — 25 min run)
  • Cross-PR integration once the parallel capability_matching agent
    lands its sketch_family_override reader

Boundaries respected

This PR only touches deploy/fake-exporter/,
deploy/configs/mvp-workload.yaml, and the replay client +
demo-driver scripts. No controller / planner / mvp_report.py edits, so
no conflict with the three parallel agents.

🤖 Generated with Claude Code

…vers all query classes

Adds the four new metric families that close the gap between the
existing DDSketch + Sum coverage and the full 5-sketch MVP demo (issue
#46). All four are gated by EXPORTER_FIVE_SKETCH=on/off (default on)
and respect the existing PER_AGENT_CARDINALITY / EXPORTER_FREQ_HZ
producer knobs.

New metrics (deploy/fake-exporter/five_sketch_workload.go):
  - request_size_bytes      Gauge,   log-normal 100B–10KB   (KLL)
  - unique_users_per_min    Counter, rotating user-id pool  (HLL)
  - top_endpoint_qps        Counter, Zipfian endpoint pool  (CountSketch)
  - endpoint_request_freq   Counter, same Zipfian shape     (CountMinSketch)

Workload spec (deploy/configs/mvp-workload.yaml):
  Adds four entries with sketch_family_override + target_path hints
  for the controller's planner (round-tripped silently through the
  current WorkloadEntry struct so the parallel capability_matching
  agent's planner can pick them up without a schema rename).

Replay coverage (deploy/scripts/run_mvp_demo.sh, promql_replay.py):
  Bumps replay rotation from 3 to 7 queries (quantile, sum_rate,
  kll-quantile, count_unique, topk, frequency) and QPS from 5 to 8 so
  per-class samples ≥340 over the 300s soak (≥100 floor for accuracy
  reduction). Adds the `frequency` kind to the replay client's
  validator so `rate(...[5m])` results are accepted.

Tests:
  - go test ./deploy/fake-exporter/... — 7 tests pass (3 new for the
    five-sketch workload + 4 pre-existing).
  - python3 -m pytest deploy/scripts/tests/test_promql_replay.py — 9
    tests pass covering load_queries kind validation + the five
    PromQL result shapes (vector / scalar / topk-vector / rate-vector
    / sum-by-vector / json-error).

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

PR #338 added frequency to the replay client's QUERY_KINDS but the reducer
silently skipped frequency rows because parse_query had no `rate()` regex.
This adds:

- `_RATE_RE` matching `rate(metric[5m])` shape
- parse_query returns ("frequency", {"metric": ...})
- new `extract_per_series` helper: PromQL vector → {labels-key → value}
- frequency branch in reduce_cell_via_archive: pair warm vs archive
  per-series, compute mean absolute additive error normalised by truth
  total — comparable column with rel_err for other kinds
- frequency branch in archive_miss fallback path so warm_answer is still
  captured for diagnostics

Refs #46. Closes the gap PR #338 flagged.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zzylol added a commit that referenced this pull request May 8, 2026
…eries ε (#343)

PR #338 added frequency to the replay client's QUERY_KINDS but the reducer
silently skipped frequency rows because parse_query had no `rate()` regex.
This adds:

- `_RATE_RE` matching `rate(metric[5m])` shape
- parse_query returns ("frequency", {"metric": ...})
- new `extract_per_series` helper: PromQL vector → {labels-key → value}
- frequency branch in reduce_cell_via_archive: pair warm vs archive
  per-series, compute mean absolute additive error normalised by truth
  total — comparable column with rel_err for other kinds
- frequency branch in archive_miss fallback path so warm_answer is still
  captured for diagnostics

Refs #46. Closes the gap PR #338 flagged.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

The five-sketch workload's HLL inner-label fan-out (user_id × outer
label set) was the dominant SDK output contributor, pushing the
post-PR-#338 demo to ~2.46 MB/s and blowing up agent → gateway
bandwidth. Lower the two defaults that drive that fan-out:

- EXPORTER_FIVE_SKETCH_USER_POOL: 1000 -> 100 (floor 500 -> 50).
  HLL still has a non-trivial active-user cardinality to estimate
  (100 distinct ids), and the rotate window keeps the per-minute
  semantic intact.
- EXPORTER_CARDINALITY (binary default): 1000 -> 500 so direct-binary
  use also matches the demo script's PER_AGENT_CARDINALITY=500
  default. With N_PRODUCERS=10 the aggregate gateway cardinality
  lands at ~5K series (was 10K-25K+).

top_endpoint_qps / endpoint_request_freq stay at 50 endpoints
(already small); request_size_bytes is scalar.

PER_AGENT_CARDINALITY=500 in run_mvp_demo.sh and the
mvp-multi-stage compose env propagate already match the new
binary default; mvp-workload.yaml does not pin cardinality.

Verified with go test ./deploy/fake-exporter/... and a local
binary smoke (startup log shows cardinality=500 users=100
endpoints=50).

Refs #46.

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

Root cause: PR #353 spawned six gorillas3 instances (one per
per-family pipeline under the 5-sketch routing topology), each with
its own in-memory windowState. With the static placeholder's 60 s
flush interval and PR #338's fake-exporter cardinality, the agent
buffered the full per-pipeline working set in RAM BEFORE the first
flush ticker fired — peak crossed the 1.5 GiB per-agent memory
limit, the agent was OOM-killed, and zero TSDB blocks ever reached
MinIO. PR #354's flat-slice OOB-tolerance approach compounded the
problem at flush time (O(N_samples) flat slice + interleaved Head
chunk creation across every series) but the agent never lived long
enough for that path to fire.

Result in /tmp/asap-mvp-rerun-bug34/asap/measurements/accuracy.csv:
1713 archive_miss / 0 archive_ok (was 343 archive_ok pre-#354).

Two-part fix:

1) opentelemetry-collector-contrib-patch/processor/gorillas3processor:
   replace PR #354's flat-sort-and-skip path with a series-visit-
   ordered append. Each series' points are still sorted ascending
   locally, but series are now visited in ascending order of each
   series' EARLIEST sample timestamp. That guarantees the very first
   `app.Append(...)` carries the global minimum, anchoring the
   appender's `minValidTime = globalMin - chunkRange/2` so every
   other in-window sample passes the OOB check — without ever
   allocating an O(N_samples) flat slice or interleaving Head
   series creation. Memory footprint at flush is bounded by
   `max(series points, Head per-series state)` instead of total
   sample count. PR #354's defensive ErrOutOfBounds tolerance
   (`gorillas3_tsdb_oob_samples_dropped_total` counter + warn log,
   no rollback) is retained for genuinely late-arriving samples.

2) deploy/configs/asap-otel-agent-b6-asap-single-sketch.yaml:
   reduce gorillas3.window_interval from 60 s → 5 s. With six
   per-pipeline windowStates instead of one, the per-instance
   in-flight buffer is the dominant memory pressure. 5 s caps the
   per-pipeline window peak at ~150 MiB, putting the steady-state
   aggregate inside the agent's 1.5 GiB budget. tsdb_block_duration
   stays at 60 s so the on-S3 layout still matches the Thanos
   store-gateway sync interval; flushes just roll smaller sub-blocks
   that thanos-compact will merge.

Verification (90 s soak diagnose, repeated):
  before: mc ls myminio/asap-gorilla-tsdb/ → empty
          curl -s http://localhost:19092/api/v1/labels →
            {"data":["__name__"]} (no metric labels surfaced)
  after:  mc ls myminio/asap-gorilla-tsdb/ → 27 blocks across all
          six pipelines (countsketch_path, countminsketch_path,
          ddsketch_path, hll_path, kll_path, raw_passthrough)
          curl -s http://localhost:19092/api/v1/label/__name__/values →
            ["endpoint_request_freq","http_freshness_probe_archive",
             "http_freshness_probe_raw","http_freshness_probe_warm",
             "http_requests_total","http_requests_total_latency_ms",
             "request_size_bytes","top_endpoint_qps",
             "unique_users_per_min"]
          all 5 sketched metrics + raw + 3 freshness probes.

Coverage:
  TestTSDBBlockBuilder_HighCardinalityMemoryBound — 200-series /
  60-sample window with non-overlapping per-series time slices, so
  the global minimum lives on a different series from the global
  maximum. Asserts 0 OOB drops + every sample lands in the block.
  Pre-this-PR's algorithm would still have passed correctness but
  allocated the O(N) flat slice; this test pins the visit-order
  invariant going forward.

Existing TSDB OOB regressions (RotatingCardinalityNoOOB,
WideTimestampSpanNoOOB, FlushTSDB_OOBDoesNotCrashProcessor) all
still pass with the new visit-order path — same OOB-tolerance
contract, different memory footprint.

Closes #46.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol deleted the mvp/5-sketch-workload-and-replay 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