refactor: relocate controller into asap-query-backend + delete asap-common + scaffolding for centralized series_id - #120
Merged
Conversation
…ommon + scaffolding for centralized series_id
Backend half of the controller-into-backend refactor. Companion PR in
ASAPCollector lands the agent / wire-format / OTel-Go side.
Design doc: docs/design-controller-into-backend.md (covers all phases:
target architecture, capability model, OTLP metadata, centralized
sid namespace with idempotency + ghost-sid handling + fault-tolerance,
phase order).
Phase 8 — delete asap-common; redistribute types
Audit (docs/phase8-asap-common-audit.md) found that the collector-side
Rust crates do not actually depend on any asap-common type — only
prose doc comments referenced paths. Bucket (b) "wire-shared" was
empty; no asap-wire-types crate was created. The 4 crates moved
verbatim into ASAPQuery-backend/crates/ preserving identity:
- crates/asap_types/
- crates/asap_otel_proto/
- crates/promql_utilities/
- crates/datafusion_summary_library/
Workspace Cargo.toml updated. asap-query-engine/Cargo.toml switches
asap_otel_proto to .workspace = true (others were already on the
workspace dep). Dockerfile.backend's COPY pointer updated from
asap-common to crates/. Build verification (release): ASAPQuery-backend
4m24s, all 4 collector Rust crates also build.
Phase 9 — relocate controller from ASAPCollector
controller/ moved verbatim from ASAPCollector/controller/ →
ASAPQuery-backend/controller/. Workspace member added; asap-query-engine
path-deps controller. Added controller/src/lib.rs to expose the crate
as a library (it was previously bin-only); main.rs converted from
`mod foo` to `use controller::foo` so the binary entrypoint and the
library surface are the same module set. Documented module purpose
in lib.rs (sketch_algebra, intent_algebra, planner, opamp, etc.) so
asap-query-engine can consume the controller's capability map and
L1-L5 pipeline in-process.
Companion ASAPCollector PR removes the standalone controller container
+ Dockerfile.controller; OpAMP server now originates from the backend
host (agents reach ws://backend:4320/v1/opamp). 11 agent YAMLs in
ASAPCollector were updated.
Phase 4 (scaffolding) — centralized series_id resolver
Wire-format addition (in companion ASAPCollector PR's proto patch):
ExportMetricsServiceResponse.unknown_series_ids — the universal
recovery primitive. Sender evicts listed sids from cache; subsequent
emits re-attach attributes; receiver re-resolves.
Backend additions in this PR:
- asap-query-engine/src/drivers/ingest/series_resolver.rs
SeriesIdResolver with content-addressable + atomic compute-or-mint
cache (DashMap-backed). Idempotent: same (metric, attrs) input
always produces the same sid for the cache lifetime — this is the
invariant that makes attribute-fallback recovery work (recovered
agent re-emits with attributes and gets back the SAME sid as
before its crash, so sketch state under that sid stays coherent).
Also exports canonical_attrs_fingerprint() that mirrors the
patched OTel-Go exporter's fingerprint algorithm bit-exactly so
sender and receiver hash the same way.
- asap-query-engine/src/drivers/ingest/otel.rs:
ExportMetricsServiceResponse construction now also populates
`unknown_series_ids: Vec::new()` (empty for now; Phase 4 final
wiring populates it on cache misses).
Not yet wired: the gRPC service definition for ResolveSeriesIDs and
the receive-path lookup that classifies incoming sids into
Hit/Ghost/Unknown and either replies with SeriesAssignments or
requests an attribute-fallback re-emit. Tracked in design doc Phase 4
finishing work.
Phase 5 (scaffolding) — SketchStore reindex types
- asap-query-engine/src/stores/sketch_index.rs
Two-level sketch index types (per design doc §4.6). The new
SimpleStore lookup key path:
sid → SketchInstanceMetadata { metric_name, group_by_keys,
capability, sketch_kind,
sketch_config, accuracy }
sid → Vec<SketchTimeSeries { series_label_values, samples
(window_end → state) }>
Capability enum represents what the controller's plan covers
for a given (metric, group_by) tuple — QuantileApprox(SketchKind),
CardinalityApprox, FrequencyTopk(SketchKind). Replaces the
legacy aggregation_id integer key.
SidLookup classification (Hit / Ghost / Unknown) drives the new
query routing decision: Hit evaluates the warm sketch; Ghost
(registered identity, no state ever arrived because gateway
merged it away upstream) falls through to Thanos for the raw
archive; Unknown signals the sender's cache is stale.
AccuracyBound::from_config derives (epsilon, confidence) per
sketch family so PromQL responses can carry the warm-tier
accuracy envelope without re-deriving from sketch parameters
each time.
Includes #[cfg(test)] unit tests for the cache idempotency,
the ghost-sid classification, and the AccuracyBound derivation.
Not yet wired: legacy SimpleStore call sites still use the
aggregation_id-keyed path; Phase 5 final wiring migrates those to
the new SketchIndex. The new types live alongside the legacy
SimpleStore until the migration completes.
Phase 6 (config) — universal warm-miss → archive fallthrough
Lives in the companion ASAPCollector PR's
deploy/configs/backend-storage-routing.yaml: removed the per-shape
allow-list; all query shapes now route to gorilla_s3_archive when
the warm tier returns SidLookup::Ghost or SidLookup::Unknown.
Runtime SidLookup classification (this PR's sketch_index.rs) is the
routing oracle.
Open / deferred:
- Phase 4 finish: ResolveSeriesIDs gRPC service (SidResolver wiring
into the OTLP receiver path); gateway transparent-forwarder mode
in opentelemetry-collector-patch.
- Phase 5 finish: legacy SimpleStore call-site migration to
SketchIndex.
- Phase 7: end-to-end empirical verification after Phase 4/5 wire.
- controller crate has 21 dead-code warnings — orthogonal cleanup.
Build verification:
cargo build --release succeeds for both controller (lib + bin) and
query_engine_rust. asap-precompute-rs, asap-gorilla-rust on the
ASAPCollector side also build clean per Phase 8 audit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 10, 2026
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
Backend half of the controller-into-backend refactor. Companion PR in ASAPCollector: ProjectASAP/ASAPCollector#372.
Design doc:
docs/design-controller-into-backend.md(target architecture, capability model, OTLP metadata + store layout, centralized series_id namespace with idempotency + ghost-sid handling + fault-tolerance, phase order).What landed (per phase)
Phase 8 —
asap-common/deleted; 4 crates relocated tocrates/:crates/asap_types,crates/asap_otel_proto,crates/promql_utilities,crates/datafusion_summary_library.docs/phase8-asap-common-audit.md) found the collector-side Rust crates don't actually depend on any asap-common type — only prose comments. Bucket "wire-shared" was empty; noasap-wire-typescrate was created.Cargo.tomlupdated;asap-query-engine/Cargo.tomlswitchesasap_otel_prototo.workspace = true.DockerfileCOPY asap-common→COPY crates.Phase 9 —
controller/moved verbatim from ASAPCollector. Newcontroller/src/lib.rsexposes the crate as a library (was bin-only);main.rsrewritten frommod footouse controller::fooso binary entrypoint and library surface share modules.asap-query-enginepath-depscontroller. (Companion PR removes the standalone controller container; OpAMP server now originates from backend host.)Phase 4 (scaffolding) —
asap-query-engine/src/drivers/ingest/series_resolver.rs:SeriesIdResolver— content-addressable + atomic compute-or-mint cache (DashMap-backed). Idempotent: same(metric, attrs)always returns the same sid for cache lifetime — the invariant that makes attribute-fallback recovery work.canonical_attrs_fingerprint(attrs)— mirrors the patched OTel-Go exporter'sattributesFingerprintbit-exactly so sender and resolver hash identically.otel.rs::Exportnow populatesExportMetricsServiceResponse.unknown_series_ids: Vec::new()(placeholder; Phase 4 finish populates on cache miss).Phase 5 (scaffolding) —
asap-query-engine/src/stores/sketch_index.rs:Capabilityenum:QuantileApprox(SketchKindHandle),CardinalityApprox,FrequencyTopk(SketchKindHandle). Replaces aggregation_id integer key.SketchKindHandleenum mirrorscontroller::sketch_algebra::params::SketchKindfor in-store lookup independence.SketchConfigenum carries per-instance config (relative_accuracy / k / precision / rows×cols).AccuracyBound::from_configderives(epsilon, confidence)per sketch family for response metadata.SketchInstanceMetadata(sid → metadata) +SketchTimeSeries(per-series time-windowed state) two-level structure.SidLookup::{Hit, Ghost, Unknown}drives the new query routing decision: warm-tier hit, ghost (registered identity, state never arrived because gateway merged it away upstream — fall through to Thanos), unknown (sender's cache stale —unknown_series_idsrecovery).Deferred / out-of-scope (follow-up PRs)
ResolveSeriesIDsgRPC service definition + receive-path lookup that classifies incoming sids and either replies withSeriesAssignmentsor signalsunknown_series_ids.SimpleMapStorecall-site migration to the newSketchIndex. The legacy aggregation_id-keyed path stays in-tree until migration is complete; new types live alongside.Caveats
controllercrate has 21 dead-code warnings — orthogonal cleanup, deferred.Build / test plan
cargo build --releaseclean for: workspace root,controller(lib + bin),query_engine_rustasap-precompute-rs,asap-gorilla-rust) build clean per Phase 8 auditSeriesIdResolver,SketchIndexpass🤖 Generated with Claude Code