Skip to content

backend-plan: serving-time cutover -- prefer plan over SketchStore reconstruction (Phase 4) - #439

Closed
zzylol wants to merge 5 commits into
mainfrom
feat/backend-plan-serving-cutover
Closed

zzylol wants to merge 5 commits into
mainfrom
feat/backend-plan-serving-cutover

Conversation

@zzylol

@zzylol zzylol commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Stacked on #438 (Phase 2+3) and #437 (Phase 1). Final phase of the BackendPlan cutover (control_plane/docs/design-backend-plan-wire-format.md): serving time (l4_lowering.rs) now reads planning's real family/params decision directly off an installed BackendPlan when it covers the query's metric, instead of always reconstructing it from SketchStore metadata via ObservedFamilyCostModel.

  • observed_family_for_metric_from_plan — reads Materialization.kind/.params directly off the plan; no AggregationConfig reconstruction needed (they already are the pair ObservedFamilyCostModel wants).
  • lower_promql_to_l4node tries the plan first, falls back to the existing SketchStore-reconstruction path when no plan is installed or it doesn't cover the metric — additive, not a replacement.
  • ASAPQueryEngine gets a HotReloadBackendPlan handle, shared with the HTTP server in main.rs (same sharing contract as hot_reload_config), so a POST /api/v1/backend-plan is observable by the next query.
  • ObservedFamilyCostModel's doc updated: fallback status, not dead code.

Test plan

  • New unit tests in l4_lowering.rs::tests::backend_plan_cutover — the load-bearing one (a_plan_materialization_wins_over_sketchstore_reconstruction) registers a Kll sid in SketchStore but installs a plan declaring DDSketch for the same metric, and asserts the bound L4Node uses DDSketch (inspected via the SummaryEstimate{SummaryAgg{summary,..}} tree shape). Two companion tests confirm the no-plan and plan-doesn't-cover-this-metric fallback paths are unchanged.
  • cargo test --release --lib -p control_plane — 732/732.
  • cargo test --release --lib -p data_plane — 915/915 (2 pre-existing ignores).
  • cargo build --release --workspace — clean.
  • e2e_controller_plans_and_backend_serves — same 2 pre-existing *_topk failures as main (unrelated, confirmed earlier in this stack).

zzylol and others added 5 commits July 29, 2026 22:09
Phase 1 of the BackendPlan end-to-end wiring (control_plane emit ->
data_plane serving). Pure, additive: a new from_stage_config() function
builds a typed BackendPlan from the same BackendStageConfig input
emit_backend_streaming_config_json already turns into the legacy JSON
wire format. No wire/push change yet -- that's Phase 2/3.

Fingerprint parity with the legacy StreamingConfig path is the critical
invariant: Materialization.fingerprint is derived by round-tripping
through build_backend_aggregation_json + AggregationConfig::from_yaml_data
(the exact JSON shape and parser data_plane's real
POST /api/v1/streaming-config handler uses), not a re-derived field
mapping, so the two identity spaces can never drift apart.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Combines the wire push and endpoint pieces of the BackendPlan cutover
(control_plane/docs/design-backend-plan-wire-format.md):

control_plane side:
- BackendClient::post_backend_plan_typed posts encoded BackendPlan bytes
  to POST /api/v1/backend-plan (sibling endpoint derived from the
  configured streaming-config URL, same convention as storage_routing).
- emit::backend_push::push_cumulative_entries now also builds a
  BackendPlan from the same cumulative_be snapshot used for the legacy
  streaming-config/storage-routing documents (via
  backend_plan::from_stage_config from the prior PR) and fires a
  best-effort, single-attempt push alongside the existing coupled push.
  This is the real plan-time call site (Phase 3) -- the only place that
  builds BackendStageConfig for a live push.
- The plan push's outcome never affects PushOutcome, which existing
  callers key real (legacy-path) behavior on -- nothing consumes
  BackendPlan yet, so its failure must stay invisible to them. No
  in-function retry loop (unlike the coupled documents): the next
  replan cycle is the retry backstop, matching push_or_log's existing
  fire-and-forget contract for the legacy YAML path.

data_plane side:
- HotReloadBackendPlan (storage_engines/types/hot_reload_config.rs):
  same ArcSwap-snapshot/swap shape as HotReloadStreamingConfig, applied
  to control_plane::backend_plan::BackendPlan.
- GET/POST /api/v1/backend-plan handlers, registered alongside (not
  replacing) /api/v1/streaming-config. POST decodes protobuf bytes and
  atomically swaps; does not touch the sid catalog or SketchStore
  reconciliation.
- main.rs installs an empty handle at startup so the endpoints don't
  503 before the control plane's first push lands, mirroring the
  existing backend-storage-routing bootstrap pattern.

Nothing consumes the installed BackendPlan yet -- that's Phase 4
(serving-time cutover), which also retires ObservedFamilyCostModel's
SketchStore-reconstruction to a fallback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace forward references to "Phase 4"/"nothing consumes this yet"
(stale now that the serving-time consumer exists) with descriptions of
the actual current architecture: BackendPlan is read by ASAPQueryEngine's
serving-time lookup, sits alongside (not in place of) the legacy
streaming-config path, and a dropped push just falls back to SketchStore
reconstruction until the next replan cycle.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ore reconstruction (Phase 4)

Completes the BackendPlan wire-format cutover
(control_plane/docs/design-backend-plan-wire-format.md). Serving time
now reads planning's real family/params decision directly off an
installed BackendPlan when one covers the query's metric, instead of
always reconstructing it from SketchStore metadata.

- l4_lowering.rs: new observed_family_for_metric_from_plan() reads
  Materialization.kind/.params directly (no AggregationConfig
  reconstruction needed -- they already are the (SummaryKind,
  SummaryParams) pair this needs). lower_promql_to_l4node() now takes
  an Option<&BackendPlan> and tries the plan first, falling back to the
  existing SketchStore-reconstruction path (observed_family_for_metric /
  ObservedFamilyCostModel) when no plan is installed or it doesn't cover
  the metric. Three new unit tests prove: (1) SketchStore reconstruction
  still works with no plan, (2) a plan materialization WINS over a
  disagreeing SketchStore registration for the same metric -- the
  load-bearing proof for this cutover, (3) a plan that doesn't cover the
  metric falls through to SketchStore reconstruction rather than
  silently failing to observe anything.
- l4_readout.rs / live_serve.rs: thread the same Option<&BackendPlan>
  parameter through unchanged otherwise.
- engine.rs: ASAPQueryEngine gains a HotReloadBackendPlan handle
  (with_hot_reload_backend_plan) and a backend_plan_snapshot() helper;
  both live-serving call sites (range + instant query) pass the current
  snapshot through.
- main.rs: the HotReloadBackendPlan handle is now shared between the
  query engine and the HTTP server (previously only the latter), so a
  POST /api/v1/backend-plan is observable by the next query, same
  sharing contract as hot_reload_config.
- cost_model.rs: ObservedFamilyCostModel's doc updated to reflect its
  new fallback status -- not dead code, just no longer the first thing
  consulted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ehavior

Drop "this cutover"/"the load-bearing proof for this cutover"-style
narration in favor of stating what the code actually does now: prefer
BackendPlan's materializations, fall back to SketchStore reconstruction.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@zzylol
zzylol force-pushed the feat/backend-plan-serving-cutover branch from 62ba731 to f84452b Compare July 30, 2026 04:56
@zzylol
zzylol changed the base branch from feat/backend-plan-wire-push to main July 30, 2026 04:57
@zzylol

zzylol commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #441 (fresh branch rebased directly onto main) -- this PR's base-branch chain got tangled after #438 was recreated as #440. Same content.

@zzylol zzylol closed this Jul 30, 2026
@zzylol
zzylol deleted the feat/backend-plan-serving-cutover branch July 30, 2026 05:01
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