Skip to content

refactor: relocate controller into asap-query-backend + delete asap-common + scaffolding for centralized series_id - #120

Merged
zzylol merged 1 commit into
mainfrom
refactor/controller-into-backend
May 10, 2026
Merged

zzylol merged 1 commit into
mainfrom
refactor/controller-into-backend

Conversation

@zzylol

@zzylol zzylol commented May 10, 2026

Copy link
Copy Markdown
Contributor

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 8asap-common/ deleted; 4 crates relocated to crates/:

    • crates/asap_types, crates/asap_otel_proto, crates/promql_utilities, crates/datafusion_summary_library.
    • Audit (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; no asap-wire-types crate was created.
    • Workspace Cargo.toml updated; asap-query-engine/Cargo.toml switches asap_otel_proto to .workspace = true. Dockerfile COPY asap-commonCOPY crates.
  • Phase 9controller/ moved verbatim from ASAPCollector. New controller/src/lib.rs exposes the crate as a library (was bin-only); main.rs rewritten from mod foo to use controller::foo so binary entrypoint and library surface share modules. asap-query-engine path-deps controller. (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's attributesFingerprint bit-exactly so sender and resolver hash identically.
    • Unit tests for idempotency, distinctness, fingerprint key-sort independence.
    • otel.rs::Export now populates ExportMetricsServiceResponse.unknown_series_ids: Vec::new() (placeholder; Phase 4 finish populates on cache miss).
  • Phase 5 (scaffolding)asap-query-engine/src/stores/sketch_index.rs:

    • Capability enum: QuantileApprox(SketchKindHandle), CardinalityApprox, FrequencyTopk(SketchKindHandle). Replaces aggregation_id integer key.
    • SketchKindHandle enum mirrors controller::sketch_algebra::params::SketchKind for in-store lookup independence.
    • SketchConfig enum carries per-instance config (relative_accuracy / k / precision / rows×cols).
    • AccuracyBound::from_config derives (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_ids recovery).
    • Unit tests for ghost-sid classification, hit-after-append, accuracy bound derivation.

Deferred / out-of-scope (follow-up PRs)

  • Phase 4 finishResolveSeriesIDs gRPC service definition + receive-path lookup that classifies incoming sids and either replies with SeriesAssignments or signals unknown_series_ids.
  • Phase 5 finish — legacy SimpleMapStore call-site migration to the new SketchIndex. The legacy aggregation_id-keyed path stays in-tree until migration is complete; new types live alongside.
  • Phase 7 — end-to-end empirical verification of centralized TSID + ghost-sid routing.

Caveats

  • controller crate has 21 dead-code warnings — orthogonal cleanup, deferred.
  • ASAP arm doesn't yet use the new types in the runtime path — they're plumbed and tested at unit level, awaiting wire-in.

Build / test plan

  • cargo build --release clean for: workspace root, controller (lib + bin), query_engine_rust
  • Companion ASAPCollector Rust crates (asap-precompute-rs, asap-gorilla-rust) build clean per Phase 8 audit
  • Unit tests for SeriesIdResolver, SketchIndex pass
  • Apply Phase 4 finishing PR; verify ResolveSeriesIDs RPC end-to-end
  • Apply Phase 5 finishing PR; verify raw-name PromQL routes through capability lookup
  • Apply Phase 7 verification PR; tcpdump shows compact sid-mode emission post-cache

🤖 Generated with Claude Code

…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>
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