fix: warm engine answers last_over_time(freshness_probe) — ⑥ freshness UNKNOWN → CAPTURED - #112
Merged
Merged
Conversation
…e (issue #46 ⑥) Root cause: the MVP demo's freshness criterion ⑥ polls `last_over_time(http_freshness_probe_warm[10s])` against the backend at 10 Hz over a 60 s window. The probe metric flows through the agent's `[gorillas3 → ddsketch → batch] → backend OTLP` pipeline; the cold tier (gorillas3 → 60 s TSDB block → MinIO → Thanos store-gateway 30 s sync) adds 60–90 s of flush latency before any sample is queryable. A 10 s lookback against Thanos therefore returns an empty vector for the entire run, even though the probe is being emitted at 1 Hz and reaching the backend's OTLP receiver in real time. The replay client logged `attempted=600 got=0` for both warm and archive paths, pinning ⑥ at UNKNOWN. Fix: capture every `http_freshness_probe_*` data point in a small RAM-resident cache off the OTLP ingest path, and intercept matching `last_over_time(<probe>[<range>])` queries in the HTTP query handler to answer from RAM instead of falling through to the cold archive. - New `asap-query-engine/src/routing/freshness_probe_cache.rs` — metric-name-prefixed `(ts_ms, value)` cache. Ignores non-probe metrics with a cheap string-prefix rejection. Returns `None` on stale samples (outside the lookback window) so the dispatch falls through to the routing table for long-window queries (≥1 m) the cold archive still answers correctly. - `drivers/ingest/otel.rs` — new `with_probe_cache` builder on `OtlpReceiver`; `capture_freshness_probe_samples` records every probe data point on both gRPC and HTTP receive paths before the precompute / sketch routing. - `drivers/query/servers/http.rs` — new `try_answer_freshness_probe` short-circuit in `process_query_request`. Parses `last_over_time(<metric>[<range>])`, checks the probe-name prefix, looks up the cache against the request's instant time, and returns a Prometheus instant vector tagged `data_source: sketch_warm` on hit. Cache miss → `None` → normal dispatch. - `main.rs` — allocates one shared `Arc<FreshnessProbeCache>` and hands it to both the OTLP receiver (write path) and the HTTP server (read path). Tests: - `freshness_probe_cache::tests` — 8 unit tests pinning record / lookup window semantics, name-prefix filter, monotonic-ts contract. - `http::tests::freshness_probe_*` — 5 server-level tests pinning the end-to-end intercept: in-window cache hit returns the recorded counter value via the Prometheus adapter, stale samples fall through, non-probe metrics bypass the cache, and the parser recognises only canonical `last_over_time(probe[range])` shapes. Live verification (synthetic OTLP HTTP injection): - Before: `last_over_time(http_freshness_probe_warm[10s])` → `result: []` with `data_source: gorilla_archive` (cold-tier hit, empty because the latest sample is >10 s old). - After: same query → `result: [{value: ["1778276648.824", "1778276646445"]}]` with `data_source: sketch_warm` (RAM cache hit, ts ≈ now, value = unix_ms of last emission). Backend log line `freshness-probe cache updated updated_probes=1 cache_size=1` confirms the OTLP write hook. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
last_over_time(http_freshness_probe_warm[10s])and gotattempted=600 got=0. The probe metric reaches the backend's OTLP receiver at 1 Hz, but the cold-tier path (gorillas3 → 60 s TSDB block → MinIO → Thanos sync) adds 60–90 s of flush latency, so a 10 s lookback against Thanos always returns empty even though probes are arriving in real time.FreshnessProbeCachekeyed by metric name (prefixhttp_freshness_probe_), fed by the OTLP receiver's ingest path, and read by a new HTTP-handler short-circuit that interceptslast_over_time(<probe>[<range>])and answers from RAM with sub-second freshness.Root cause (which of a/b/c/d the controller asked about)
(b) — but not "gorillas3 filters probes" or "metric write path drops counters". The agent pipeline DOES write probes to TSDB and DOES forward them to backend OTLP. The actual issue is that the cold tier's flush latency (60 s gorillas3 block + 30 s Thanos sync) is fundamentally incompatible with the replay client's 10 s lookback window. Bare-metric
http_freshness_probe_warmqueries return data fine; only the[10s]lookback misses, because the latest sample in TSDB is always ≥60 s old.The routing table (a) DOES carry the probe entries, the routing-shape classifier (c) handles
last_over_timecorrectly, and the producer (d) is emitting probes — those three were red herrings the diagnosis prompt listed but ruled out by curl/log inspection.File + lines changed
asap-query-engine/src/routing/freshness_probe_cache.rsasap-query-engine/src/routing/mod.rsasap-query-engine/src/drivers/ingest/otel.rswith_probe_cachebuilderasap-query-engine/src/drivers/query/servers/http.rstry_answer_freshness_probeshort-circuit +parse_last_over_time_probe+ 5 testsasap-query-engine/src/main.rsArc<FreshnessProbeCache>and share it across the receiver and HTTP serverLive curl evidence
Before (cold-tier dispatch, latest TSDB sample older than 10 s):
After (cache hit, sub-second freshness):
Backend log line on cache write:
(The synthetic OTLP injection sidesteps an unrelated 4 MB-grpc-cap issue between the producer and agent in the local stack — the agent's OTLP receiver doesn't override
max_recv_msg_size_mib. That blocks the producer→agent→gateway→backend pipeline in this snapshot but is out of scope for this PR; this fix is verified live by direct OTLP injection at the backend.)Test plan
cargo test --release --lib -p query_engine_rust freshness_probe— 11 / 11 passdata_source: sketch_warmfreshness-probe cache updatedappears on every probe writeHonest report
cardinality=500exceed the cap and the agent rejects the upload, so no probe ever reaches the backend in the local stack snapshot. That's a separate fix (likely amax_recv_msg_size_mib: 64on the agent's OTLP receiver, mirroring the gateway's). I demonstrated the fix works by injecting probes directly into backend's OTLP/HTTP endpoint with a real protobuf payload — this is a faithful reproduction of what the agent would forward in steady state.data_source: sketch_warmis what the response carries on a cache hit. The routing table comment inbackend-storage-routing.yamlalready describes the warm tier as the right home for_warmprobes; this PR delivers that semantically without requiringSimpleEngineto learnlast_over_timematching for raw counters (deferred per the YAML comment's footnote).Issue: #46