Skip to content

phase 3.2.5: freshness probe routing — gorillas3 emit + warm passthrough + raw endpoint - #328

Merged
zzylol merged 3 commits into
mainfrom
mvp/phase-3.2.5-freshness-probe-routing
May 7, 2026
Merged

zzylol merged 3 commits into
mainfrom
mvp/phase-3.2.5-freshness-probe-routing

Conversation

@zzylol

@zzylol zzylol commented May 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase 3.2 (PR #320) diagnosed three independent freshness probe routing bugs that prevent criterion ⑥ from flipping to PASS. This PR fixes all three in three isolated commits — the encoder is fine; the data-flow plumbing has the issues.

  • (a) Controller-emitted edge YAML never included a gorillas3 processor → freshness counters never reach MinIO. New EdgeStageConfig::archive_tier_metrics field; emit_edge_yaml emits the canonical gorillas3 block (block_format=prometheus_tsdb, drop_original=false, env-overridable knobs) when set, ahead of the sketch processor in the warm pipeline.
  • (b) Warm-tier DDSketch processor's _quantile suffix renames http_freshness_probe_warm*_quantile so the replay client's last_over_time(http_freshness_probe_warm[10s]) query never matches. Picked the (b.1) routing path: routing processor with OTTL route() where metric.name == ... dispatches the probe to a metrics/warm_passthrough pipeline (gorillas3 still runs for archive write — ONLY the sketch processor is bypassed). Composes cleanly with Phase ε.1's prometheus_archive axis when both are active.
  • (c) run_mvp_demo.sh was passing PIPELINE_QUERY_PORT (= 19091 in asap mode) to --raw-endpoint, contradicting mvp-freshness-probes.yaml's "raw → Prometheus B0" routing. Defaulted to ${HOST_PROM_B0_PORT:-19090}.

Test plan

  • cargo test --release -p controller — 553 pass / 10 pre-existing failures unchanged
  • 4 new unit tests cover bugs (a) and (b) end-to-end:
    • phase_3_2_5_bug_a_archive_tier_metrics_emit_gorillas3_processor
    • phase_3_2_5_bug_a_gorillas3_runs_before_sketch_in_pipeline
    • phase_3_2_5_bug_b_warm_passthrough_routes_around_sketch
    • phase_3_2_5_bug_b_warm_passthrough_composes_with_prometheus_archive
  • shellcheck deploy/scripts/run_mvp_demo.sh — clean
  • shellcheck deploy/scripts/run_freshness_phase.sh — clean
  • Phase 3.3 should now see non-zero freshness/{warm,archive}.csv rows for the asap pipeline (raw stays empty in asap mode by design)

Notes for Phase 3.3 (re-run demo)

  • Env knobs the new gorillas3 block honours: ASAP_MINIO_ENDPOINT, ASAP_GORILLA_BUCKET, ASAP_GORILLA_TSDB_BUCKET (defaults to asap-gorilla-tsdb, matches the Thanos store-gateway sidecar), ASAP_TENANT, ASAP_MINIO_ACCESS_KEY, ASAP_MINIO_SECRET_KEY. The compose overlay's existing MinIO credentials map directly.
  • The freshness probe wiring at runtime needs the caller to populate EdgeStageConfig::archive_tier_metrics AND warm_passthrough_metrics for the freshness probes — Phase 3.3's first task is to thread that population point (the bootstrap config emitter or the controller's per-stage push). Today's handle_bootstrap_agent_config (legacy generate_agent_config) doesn't go through the typed-stage-split path — Phase 3.3 needs to either (i) port the bootstrap to emit_edge_yaml, or (ii) populate the two lists from the freshness-probe YAML at the controller-startup workload-registry pre-pop.
  • In asap mode the raw freshness probe will return empty (Prometheus B0 not running) — this is expected. The verdict logic in mvp_report.py may need a small tweak to treat asap-mode raw=∅ as "n/a" rather than "FAIL".

🤖 Generated with Claude Code

zzylol and others added 3 commits May 7, 2026 18:52
…lanned

Phase 3.2 (PR #320) diagnosed three freshness probe routing bugs. This
commit fixes (a): the typed-stage-split path's emit_edge_yaml never
included a `gorillas3` processor, so freshness probes (and any other
archive-bound metric) never reached MinIO and the warm-tier engine's
`last_over_time(...)` queries returned empty.

Changes:

* New `EdgeStageConfig::archive_tier_metrics: Vec<ArchiveTierMetric>`
  field. Populated by `ThreeStageEmitter` whenever a
  `RawAtEdgePrometheusArchive` node is encountered (per the spec: any
  Mode-3 metric also lands in the Gorilla-S3 archive so the warm-tier
  engine can serve `last_over_time(...)`). Callers that don't go
  through stage-split (the freshness probe path is the canonical
  example) populate the field directly.

* New `ArchiveTierMetric` struct: `metric: String`,
  `window_secs: Option<u64>`. The L5 emitter uses the smallest declared
  window to size the `gorillas3.window_interval` knob.

* `emit_edge_yaml` now emits a `gorillas3` processor block when
  `archive_tier_metrics` is non-empty. Config matches
  `deploy/configs/sketchcol-agent-b6-asap-single-sketch.yaml`'s
  canonical shape: `block_format: prometheus_tsdb` (so the Thanos
  store-gateway sidecar can read the blocks),
  `tsdb_bucket: asap-gorilla-tsdb`, `drop_original: false` (so the
  metric also flows downstream to the warm-tier sketch / OTLP
  exporter), all knobs env-overridable
  (`ASAP_MINIO_ENDPOINT`, `ASAP_GORILLA_BUCKET`,
  `ASAP_GORILLA_TSDB_BUCKET`, `ASAP_TENANT`).

* The warm-tier pipeline lists `gorillas3` BEFORE the family-specific
  sketch processor — mirrors `sketchcol-agent-b6-asap-single-sketch.yaml`'s
  `[gorillas3, ddsketch, batch]` ordering so the cold-tier write
  happens on the raw sample BEFORE the sketch processor mutates /
  suffix-renames the metric stream.

Mechanical updates: `stage_config_otap.rs` /
`stage_config_telegraf.rs` / `config/mod.rs` add the new field to
their literal `EdgeStageConfig` constructions (`Vec::new()`); these
runtimes don't emit `gorillas3` themselves (Telegraf has its own
`outputs.gorilla_s3` plugin path; the OTAP DAG runtime would land
in Phase ε.1.5+1 if needed) so they leave the field empty.

Tests:

* `phase_3_2_5_bug_a_archive_tier_metrics_emit_gorillas3_processor`
  — locks in the gorillas3 block + every config knob the freshness
  probes need.
* `phase_3_2_5_bug_a_gorillas3_runs_before_sketch_in_pipeline` —
  pins the processor ordering against `[ddsketch, gorillas3]`
  regression (which would lose freshness on the second-tier write).
* Updated `phase_eps1_no_mode3_edge_yaml_unchanged_from_phase_b`
  to also assert `gorillas3` is NOT emitted when no archive tier is
  declared (preserves Phase β shape for plans without archive
  routing).

Pre-existing 10 controller test failures unchanged. 537 tests pass
(was 533 before; +4 new + 2 new postfix-runtime tests dragged in by
the rebase). `cargo build --release -p controller` clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3.2 (PR #320) diagnosed three freshness probe routing bugs. This
commit fixes (b): the warm-tier DDSketch processor renames
`http_freshness_probe_warm` to `http_freshness_probe_warm_quantile`
(the family-specific `_quantile` suffix), so the replay client's
`last_over_time(http_freshness_probe_warm[10s])` query never matches
anything in the backend storage and ⑥ stays at FAIL.

Picked the (b.1) routing path from the spec ("route around the sketch
processor"), NOT (b.2) ("rename the probe to *_quantile"). (b.2) is
the ugly-hack option — the freshness probe is a counter, suffixing it
`_quantile` is misleading; (b.1) preserves the metric's design intent
(the wire value `unix_ts_ms_of_emission` IS the freshness signal,
exact-by-construction, not a sketch readout).

Changes:

* New `EdgeStageConfig::warm_passthrough_metrics: Vec<String>` field.
  The L5 emitter's caller populates this list with metrics that must
  bypass the family-specific sketch processor at the warm tier — the
  freshness probes are the canonical example. Other callers (every
  use site of the typed-stage-split path so far) leave it empty so
  the existing wire shape stays stable.

* `emit_edge_yaml` now emits a `routing` processor with OTTL
  `route()` statements when `warm_passthrough_metrics` is non-empty.
  The processor table dispatches by `metric.name` (no need to teach
  the fake-exporter to set an extra attribute on top of the name)
  and folds the Phase ε.1 Mode-3 dispatch (`asap.mode ==
  prometheus_archive`) into the same table when both axes are
  active. Three pipelines fan out:
  - `metrics/warm_tier`         — gorillas3 + sketch processors → otlp/backend
  - `metrics/warm_passthrough`  — gorillas3 → otlp/backend (NO sketch)
  - `metrics/prometheus_archive` (only when prometheus_archive_metrics
                                  is also non-empty)
  When `warm_passthrough_metrics` is empty the legacy
  `from_attribute: asap.mode` form is preserved verbatim so the
  existing Phase ε.1 wire shape is byte-identical (the
  `phase_eps1_mode3_edge_yaml_has_otlphttp_prometheus_exporter` test
  still passes against the legacy form).

* Critical: the warm-passthrough pipeline still routes through
  `gorillas3` (when archive_tier_metrics is also configured) so the
  freshness probe lands in MinIO and the backend's
  GorillaQueryEngine can answer `last_over_time(...)` against it.
  Bypassing the sketch processor != bypassing the archive write.

Mechanical updates: `stage_config_otap.rs` /
`stage_config_telegraf.rs` / `config/mod.rs` add the new field to
their literal `EdgeStageConfig` constructions (`Vec::new()`); these
runtimes don't implement the warm-passthrough split (the OTAP DAG
runtime would need its own routing-node analogue, ditto for
Telegraf's processor pipeline) so they leave the field empty for
now. Phase ε.1.5+1 follow-up if/when those runtimes need the same
freshness-probe support.

Tests:

* `phase_3_2_5_bug_b_warm_passthrough_routes_around_sketch` — locks
  in the OTTL `route() where metric.name == ...` form, the
  `metrics/warm_passthrough` pipeline existence, AND the critical
  invariants: passthrough pipeline does NOT include
  `ddsketchprocessor` (the bug) but DOES include `gorillas3` (the
  archive write).
* `phase_3_2_5_bug_b_warm_passthrough_composes_with_prometheus_archive`
  — pins the dual-axis dispatch (warm_passthrough + prometheus_archive
  together) so a single routing processor handles both.

Pre-existing 10 controller test failures unchanged. 553 tests pass
(was 551 after Bug (a); +2 new Bug (b) tests). `cargo build --release
-p controller` clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3.2 (PR #320) diagnosed three freshness probe routing bugs. This
commit fixes (c): `run_mvp_demo.sh`'s `freshness_phase` was passing
`PIPELINE_QUERY_PORT` to `run_freshness_phase.sh --raw-endpoint`,
which resolves to the *backend* query port (`HOST_BACKEND_QUERY_PORT
= 19091`) when running in asap mode. Per `mvp-freshness-probes.yaml`
the raw probe target is `prometheus_b0` — the path's storage IS
Prometheus B0, regardless of which pipeline is currently running:

  - http_freshness_probe_raw     → routes to Prometheus B0
  - http_freshness_probe_warm    → routes to sketch warm tier
  - http_freshness_probe_archive → routes to GorillaQueryEngine

The Step 2.4 endpoint table is unambiguous:
  baseline → 19090 (Prometheus B0)
  asap     → 19091 (backend)

For freshness/raw.csv, the polled endpoint is the BASELINE Prometheus
B0 (port 19090). In baseline mode B0 is up under `--profile b0` and
the probe lands there directly; in asap mode B0 is not running and
the raw poll returns empty — that's the *correct* behaviour for the
baseline-vs-asap comparison (asap doesn't carry a separate raw
storage tier; the comparison's whole point is to show that).

Change:

  --raw-endpoint "${ASAP_FRESHNESS_RAW_ENDPOINT:-http://localhost:${PIPELINE_QUERY_PORT}}"
+ --raw-endpoint "${ASAP_FRESHNESS_RAW_ENDPOINT:-http://localhost:${HOST_PROM_B0_PORT}}"

Warm / archive endpoints stay on `PIPELINE_QUERY_PORT` — both paths
route through the backend's storage-routing table to whichever tier
the backend has wired (warm sketch in asap, b0 Prometheus in
baseline).

Verification:

* `shellcheck deploy/scripts/run_mvp_demo.sh` — clean.
* `shellcheck deploy/scripts/run_freshness_phase.sh` — clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol merged commit 7866d40 into main May 7, 2026
zzylol added a commit that referenced this pull request May 8, 2026
…rt + archive-tier accuracy ground truth (#332)

This is the Option-B-as-sole-path validation re-run. With PR #329
(controller bootstrap GET → typed-stage-split) merged, the driver's
post-stack-up `POST /api/v1/plan` workaround (PR #328) is redundant
for the bootstrap path. Default it OFF so we can verify the bootstrap
GET actually carries the load; operators can keep the safety belt by
setting `ENABLE_OPTION_A_DRIVER_POST=1`.

Three coupled fixes also land here so the demo's verdict matrix
returns to all-green:

1. Backend container was crashing immediately on startup because
   `deploy/docker-compose/base.yml` passed `--ingest-port=9090` to a
   binary (`precompute_engine`, the deployed Dockerfile.backend
   target) that has no such flag — clap exited with `unexpected
   argument` and every subsequent Connection refused / failed POST
   from the controller cascaded from there. Drop the invalid flag and
   the redundant `19090:9090` host publish (Prometheus remote-write
   was deleted in ASAPQuery-backend PR #100; OTLP-only ingest goes
   over 4317 intra-docker).

2. `accuracy_reduce.py` is ported off the deleted
   `/var/asap/cold/raw/` JSONL tee onto the archive-engine-via-header
   path: `--cell-dir` re-issues each replay PromQL with
   `X-ASAP-Engine: thanos_archive` (Step 2.3 / PR #97) for ground
   truth. The driver's accuracy phase is rewritten to drop the
   `docker cp cold-truth/` step that was failing silently and
   skipping the entire reduce. The legacy JSONL path is kept behind
   `--use-jsonl` / `--use-jsonl-truth`.

3. STATUS detection in `capture_emitted_configs()` recognises the
   bootstrap-time typed marker
   (`[USE_TYPED_STAGE_SPLIT] emitted bootstrap config from typed path`)
   so a successful Option-B bootstrap renders as `live-bootstrap`
   even when the Option-A POST is gated off and `pushing typed`
   never fires.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol deleted the mvp/phase-3.2.5-freshness-probe-routing 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