Skip to content

docs(control_plane): design doc for BackendPlan wire format - #433

Merged
zzylol merged 1 commit into
mainfrom
design/backend-plan-wire-format-v2
Jul 29, 2026
Merged

zzylol merged 1 commit into
mainfrom
design/backend-plan-wire-format-v2

Conversation

@zzylol

@zzylol zzylol commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Supersedes #389. That PR's design doc was written as a migration narrative (motivation citing the now-retired analyzer-parity-matrix.md, phase sequencing, "what's broken today") from a point in time before this repo adopted ASAPController's real crates directly, before SummaryExecutor became the live serving path (#427), and before sketch_reducer.rs/shadow_compare.rs were retired (#430). Rather than patch that narrative to catch up, this is a clean rewrite of the design itself, written against where the codebase actually stands today, with no reference to what's implemented vs. not.

Key differences from #389's version, driven by things learned since:

  • Materialization's payload is just (SummaryKind, SummaryParams) — no separate Sketch/ExactAggregate split. ASAPController#170 already unified exact accumulators and approximate sketches into one SummaryKind vocabulary (is_exact() tells them apart); the wire format shouldn't reintroduce a distinction the canonical type has already closed.
  • RoutingIndex is framed around two genuinely different match precisions, tied to two real callers: family-level Capability match for whole-query/miss-detection routing, and exact (SummaryKind, SummaryParams) match for SummaryExecutor::find_candidates (which must return every match for merge_states to fold, not one ranked winner).
  • A new §5 ties this directly to something concrete already in the codebase: data_plane's ObservedFamilyCostModel (landed in retire(data_plane): remove shadow_compare.rs and sketch_reducer.rs #430) reconstructs a metric's planned (SummaryKind, SummaryParams) by inspecting registered sid metadata at query time — a working stopgap for the absence of exactly this lookup. Once RoutingIndex exists, serving-time lowering reads the plan directly instead of reconstructing it, and CostModel becomes planning-time-only.
  • Open questions trimmed to what's still actually open (rollup algebra, exact top-k has no SummaryKind to name yet — tracked separately at Exact TopK (accuracy: Exact) has no warm-tier materialization -- always falls to archive #432RoutingIndex performance).

Test plan

  • No code changes — docs only.
  • Discuss the open questions in §7 before any implementation PR follows.

🤖 Generated with Claude Code

Clean design for the typed control-plane -> data-plane wire contract,
written against this deployment's current target architecture
(control_plane depends on ASAPController's real crates; SummaryExecutor
is the live serving path) rather than as a migration narrative.

Key design points:
- Materialization's payload is (SummaryKind, SummaryParams) directly --
  no separate ExactAggregate variant, since asap_sketch::SummaryKind
  already unifies exact accumulators and approximate sketches
  (ASAPController#170).
- RoutingIndex has two read modes at different match precisions:
  family-level Capability match for whole-query/miss-detection routing,
  exact (SummaryKind, SummaryParams) match for SummaryExecutor's
  find_candidates.
- Once RoutingIndex exists, serving-time L4 lowering reads the plan
  control_plane already decided directly, instead of reconstructing it
  from registered sid metadata the way ObservedFamilyCostModel does
  today -- CostModel becomes planning-time-only, never re-invoked per
  query.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@zzylol
zzylol merged commit f843616 into main Jul 29, 2026
@zzylol
zzylol deleted the design/backend-plan-wire-format-v2 branch July 29, 2026 22:24
zzylol added a commit that referenced this pull request Jul 30, 2026
Implements the typed control-plane -> data-plane wire contract from
control_plane/docs/design-backend-plan-wire-format.md (#433) §3: the
BackendPlan/Materialization/RoutingEntry Rust types, a proto schema
(proto/backend_plan.proto), and round-trip conversions between them.

Reuses this deployment's canonical vocabulary directly rather than
re-encoding it: asap_sketch::{SummaryKind, SummaryParams} for the
materialization payload (one wire shape for both exact and approximate
families, matching ASAPController#170's unification -- no separate
ExactAggregate variant), asap_ir/control_plane::intent_algebra's
Source/ColumnRef/WindowKind for the L3 IR fragments,
control_plane::sketch_algebra::capability::Capability for routing, and
asap_types::{PolicyFingerprint, MonitorSpec} (already shared with
data_plane for this exact cross-crate reason).

StorageBackend/RetentionPolicy are new, deployment-local types --
control_plane cannot depend on data_plane's existing StorageBackend
(the dependency runs the other way), so this mirrors it rather than
reusing it directly, same constraint asap_types::MonitorSpec's own doc
already documents for that type.

Scope: types + round-trip tests only, no behavior change. Not wired
into emit/, main.rs's planning path, or any data_plane consumer --
RoutingIndex (design doc §4), which is what actually reads this at
query time, is separate follow-on work.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
zzylol added a commit that referenced this pull request Jul 30, 2026
Implements the Tier-2 half of control_plane/docs/design-backend-plan-wire-format.md
(#433) §4's RoutingIndex, sourced from PolicyRegistry (StreamingConfig's
content-addressed view -- genuinely "what control_plane planned" today,
not reconstructed from ingest side effects) rather than the not-yet-built
BackendPlan wire format. Tier 1 (exact PolicyFingerprint lookup) is
already PolicyRegistry::get, unchanged.

find_matching_policies (control_plane::asap_tier_analysis) used to scan
every policy in the registry for every candidate, checking each one's
metric name first -- paying for every OTHER metric's policies on every
lookup. RoutingIndex buckets by metric once at construction, so a lookup
only touches policies for the relevant metric. Same match predicate,
same observable behavior (verified: all ~18 existing find_matching_policies
tests pass unchanged, only their PolicyRegistry construction is now
wrapped in RoutingIndex::build).

engine.rs's two candidate-resolution loops (range-query, instant-query)
now build a RoutingIndex once per query snapshot instead of a raw
PolicyRegistry, matching the design doc's stated caller pattern.

Building RoutingIndex once per StreamingConfig hot-reload swap instead of
once per query (StreamingConfig::policy_registry's own doc comment
already flags this as a further, larger optimization "if it shows up in
profiles") is not done here -- would require threading a cached derived
value through HotReloadStreamingConfig's swap path, a separate, larger
change.

Co-authored-by: Claude Sonnet 5 <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