feat(sketch-db): Phase 5e — real backfill execution with bit-identical determinism - #32
Merged
Merged
Conversation
…l determinism
Turns backfill from shadow-mode into real work: a queued job now
actually reads raw samples from the exact DB, rebuilds the sketch
bit-identically to what live ingest would have produced, and writes
the per-window precomputes to the store.
## What's landed
### Core modules (all under `src/stores/sketch_db/`)
* `backfill_window_builder.rs` — pure function
`build_backfilled_accumulator(config, samples) -> Box<dyn AggregateCore>`.
Rebuilds one window's accumulator from samples in ingest order.
Honours both SingleSubpopulation and MultipleSubpopulation
dispatch (update_single / update_keyed).
* `backfill_processor.rs` — `BackfillWindowProcessor` implementing
the Phase 5c `WindowProcessor` trait. Per window: looks up
AggregationConfig by agg_id, groups samples by grouping_labels
(same partitioning as live ingest), builds one accumulator per
group, batch-writes `(PrecomputedOutput, accumulator)` tuples
via `Store::insert_precomputed_output_batch`, then records
`(agg_id, window_range)` in the registry for Phase 5f coverage.
* `backfill_service.rs` — long-running tokio task that polls the
registry for Queued jobs and runs them through
`BackfillWorker::run_job`. One job at a time in v1 (multi-worker
+ priority is §11.4 follow-up). Config-driven `ReaderFactory`
abstraction so deployments can plug Prometheus / S3 / ClickHouse
readers without touching the service code.
### Registry enhancements (`backfill.rs`)
* `BackfillRegistry::create_checked(schemas, agg_id, range, ...)`
enforces §10.5 time-disjoint invariant: rejects `end_ms >
schema.created_at_ms`. Live ingest owns `[created_at, ∞)`;
backfill owns `[0, created_at)`. Disjoint by construction means
no locks needed between live and backfill writers on the same
`(agg_id, window)`.
* `record_window_written` / `windows_written_by` — per-job
provenance list so Phase 5f's coverage tracker can distinguish
`Backfilled { job_id }` from `Missing` without a new on-disk
field.
* `CreateError` enum with `UnknownAgg` / `Overlap` variants for
HTTP-friendly error rendering.
* `WrittenWindow` type alias to satisfy clippy + document intent.
### main.rs wiring
* New CLI flag `--enable-backfill-worker`. Off by default
(preserves Phase 5d shadow-mode semantics). On, spawns
`BackfillService` with `noop_reader_factory()` in v1.
* Graceful shutdown via `BackfillServiceHandle::shutdown()`.
* When the flag is set but precompute isn't enabled, logs a warn
and skips the service (needs the schema registry).
## Determinism decision
Audit concluded that sketch-core's Rust-side live ingest path is
fully deterministic:
- CMS / CountSketch / HLL / HydraKLL use xxhash seeded by row
index (not process-local RNG).
- Sample order is preserved through SeriesRouter → Worker into
the per-(agg_id, group_key) accumulator.
- KLL / DDSketch sampling / bucketing is deterministic by design.
- No order-sensitive FP reductions.
Open question: sketchlib-go (DC-side) cross-language determinism.
For deployments where live goes through DC's modified-OTLP path,
the backend receives pre-built sketches. Getting backfill to
produce byte-identical outputs in that case requires a separate
Go-vs-Rust audit — tracked as follow-up. For raw-ingest
deployments (Prometheus remote write), backfill is bit-identical
today; a test in `backfill_processor.rs` locks that invariant:
same sample stream → live path's SumAccumulator and backfill's
build_backfilled_accumulator produce identical serialised bytes.
## Separation decision
User direction: "backfill functions should all be separate, not
reusing the live path." The interpretation split two notions of
"live path":
1. **Worker pipeline** (PrecomputeEngine → SeriesRouter → Worker
→ active_panes → output_sink): fully NOT reused. Backfill has
its own service + worker + processor + output call chain.
Zero shared state, zero shared async runtime context.
2. **`create_accumulator_updater` pure factory**: IS reused. A
60-line stateless match statement that backfill calls through
the `build_backfilled_accumulator` facade. The alternative
(copy-paste the match) trades drift risk for isolation without
any latency-isolation benefit.
Module docs document this choice. If stricter duplication is
required, `build_backfilled_accumulator` is one match-statement
swap away. Bit-identical parity test would still pass either way.
## Test plan
- [x] 2 new tests in `backfill_window_builder`: sum aggregation
correctness, empty-samples edge case.
- [x] 8 new tests in `backfill_processor`: happy path (one-write-
per-window), unknown agg_id handling, empty samples, end-to-end
via BackfillWorker across 4 windows,
**bit-identical parity test** (live-built vs backfill-built
SumAccumulator serialise to identical bytes), and
`create_checked`'s three branches (overlap rejection,
boundary-at-created_at acceptance, unknown-agg rejection).
- [x] 4 new tests in `backfill_service`: drain queued job to
Complete, reader factory failure marks job Failed, multiple
jobs processed in job_id order, shutdown terminates loop.
- [x] 647 lib tests pass (up from 633).
- [x] `cargo clippy --workspace --all-targets --tests -- -D warnings` clean.
- [x] `cargo fmt -- --check` clean.
## What's still open
* **5f**: query path consults `windows_written_by` to distinguish
`Backfilled` vs `Missing` coverage at query-segment level.
* **5h**: real `PrometheusReader` / `S3GorillaReader` /
`ClickHouseReader` implementations wired into a production
`ReaderFactory`.
* **sketchlib-go determinism audit**: separate cross-repo work to
extend the bit-identical guarantee to DC-ingest deployments.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zzylol
added a commit
that referenced
this pull request
Apr 18, 2026
The sketch DB has two structurally-layered pieces that have been living as sibling modules: - `src/stores/sketch_db/` (logical layer: schemas, timelines, backfill) - `src/stores/simple_map_store/` (physical layer: per-key storage backend) This sibling layout hid the dependency direction: `sketch_db` depends on the `Store` trait implemented by `SimpleMapStore`, not vice versa. The physical store is one concrete implementation of the sketch DB's storage abstraction, not a peer concept. Move `simple_map_store/` INTO `sketch_db/` as a submodule so the module tree reflects the layering: everything sketch-DB-related, including its physical backend, lives under one umbrella. ## What changed - `git mv asap-query-engine/src/stores/simple_map_store → asap-query-engine/src/stores/sketch_db/simple_map_store` - `stores::simple_map_store::*` → `stores::sketch_db::simple_map_store::*` across 20 files (16 backend + 1 bench + 3 internal self-references inside the moved directory). - `stores/mod.rs` drops the `pub mod simple_map_store` declaration but keeps `pub use ... SimpleMapStore` re-export so `crate::stores::SimpleMapStore` still works for all callers. - `sketch_db/mod.rs` adds `pub mod simple_map_store` + `pub use simple_map_store::SimpleMapStore`. ## What didn't change - `crate::stores::SimpleMapStore` public path — callers that import via the top-level `stores::` continue to work unchanged. - `Store` trait still lives at `stores::traits` — it's the neutral interface both the logical layer and the physical impl share. - No code logic changed. No tests added or removed. ## Test plan - [x] Pure rename, zero logic delta. - [x] 647 lib tests pass (same count as PR #32). - [x] `cargo clippy --workspace --all-targets --tests -- -D warnings` clean. - [x] `cargo fmt -- --check` clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
zzylol
added a commit
that referenced
this pull request
Apr 18, 2026
#33) The sketch DB has two structurally-layered pieces that have been living as sibling modules: - `src/stores/sketch_db/` (logical layer: schemas, timelines, backfill) - `src/stores/simple_map_store/` (physical layer: per-key storage backend) This sibling layout hid the dependency direction: `sketch_db` depends on the `Store` trait implemented by `SimpleMapStore`, not vice versa. The physical store is one concrete implementation of the sketch DB's storage abstraction, not a peer concept. Move `simple_map_store/` INTO `sketch_db/` as a submodule so the module tree reflects the layering: everything sketch-DB-related, including its physical backend, lives under one umbrella. ## What changed - `git mv asap-query-engine/src/stores/simple_map_store → asap-query-engine/src/stores/sketch_db/simple_map_store` - `stores::simple_map_store::*` → `stores::sketch_db::simple_map_store::*` across 20 files (16 backend + 1 bench + 3 internal self-references inside the moved directory). - `stores/mod.rs` drops the `pub mod simple_map_store` declaration but keeps `pub use ... SimpleMapStore` re-export so `crate::stores::SimpleMapStore` still works for all callers. - `sketch_db/mod.rs` adds `pub mod simple_map_store` + `pub use simple_map_store::SimpleMapStore`. ## What didn't change - `crate::stores::SimpleMapStore` public path — callers that import via the top-level `stores::` continue to work unchanged. - `Store` trait still lives at `stores::traits` — it's the neutral interface both the logical layer and the physical impl share. - No code logic changed. No tests added or removed. ## Test plan - [x] Pure rename, zero logic delta. - [x] 647 lib tests pass (same count as PR #32). - [x] `cargo clippy --workspace --all-targets --tests -- -D warnings` clean. - [x] `cargo fmt -- --check` clean. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zzylol
added a commit
that referenced
this pull request
Apr 18, 2026
…r backfill)
First real `RawSampleReader` in-tree. Turns the `BackfillSource::Prometheus
{ url }` variant from "factory returns no-reader error" into an actual
working read path, so `--enable-backfill-worker` against a Prometheus-
backed deployment now produces real backfills instead of shadow-mode
Failed jobs.
## What's landed
`src/stores/sketch_db/prometheus_reader.rs`:
* `PrometheusReader::new(base_url) + with_step(d) + with_timeout(d)` —
HTTP client wrapper over `/api/v1/query_range`. Step defaults to 15s
(typical Prometheus scrape interval); overridable per-deployment.
* `impl RawSampleReader` that:
1. Translates `LabelFilter { metric, equality }` into a PromQL
matcher `metric{k1="v1",k2="v2"}` with stable key ordering and
proper escape for quotes/backslashes/newlines.
2. Issues a `GET /api/v1/query_range` with `query`, `start`, `end`,
`step` params.
3. Parses the `{status: "success", data: {resultType: "matrix",
result: [{metric, values}]}}` JSON shape into `Vec<RawSample>`.
4. Maps every failure into `RawSampleReaderError::{InvalidRange,
Upstream, Decode}` so the worker can surface the root cause on
`BackfillJob::error_message`.
* `ms_to_fractional_seconds` / `fractional_seconds_to_ms` helpers for
the ms ↔ Prometheus-fractional-seconds conversion.
* `render_series_key` rebuilds `metric{labels...}` from Prometheus's
response metric map so downstream grouping and `extract_group_key`
see the same shape they see for live samples.
`backfill_service::default_reader_factory()`:
* Routes `BackfillSource::Prometheus { url }` to `PrometheusReader::new`.
* Returns a clear "reader for <source> not yet implemented" error for
`S3Gorilla` / `ClickHouse` / `OtherSketch` so the controller sees
exactly which reader is missing.
`main.rs` now uses `default_reader_factory()` under
`--enable-backfill-worker` instead of `noop_reader_factory`, so the
service actually does work against Prometheus targets.
## On determinism (§10.5)
`/api/v1/query_range` with step = scrape_interval returns one point
per step per series. For deployments whose live ingest is Prometheus
remote write (backend-side sketch-core construction), this gives the
same sample sequence live saw, so the bit-identical parity test from
PR #32 still holds. For deployments whose live ingest goes through
DC's OTLP sketch-building path, approximate equivalence holds; exact
bit-identical would need `/api/v1/read` + sketchlib-go parity work.
Module doc flags that `/api/v1/read` (the protobuf remote-read
protocol) is the upgrade path if we ever need sub-scrape-interval
resolution; trait contract is unchanged.
## Test plan
- [x] 8 unit tests: `build_promql` ordering + escape + bare-metric,
`render_series_key` ordering + bare + fallback, fractional-seconds
round-trip, inverted-range → `InvalidRange`.
- [x] 8 integration tests against a spawned axum mock Prometheus:
happy-path multi-series parse + param propagation, PromQL matcher
synthesis, HTTP 5xx → Upstream, Prometheus `status: error` →
Upstream, malformed JSON → Decode, ingest-order preservation
per series, empty result, wrong `resultType` → Decode.
- [x] 677 lib tests pass (up from 661).
- [x] clippy `--workspace --all-targets --tests -- -D warnings` clean.
- [x] `cargo fmt -- --check` clean.
## What this unblocks
A Prometheus-backed deployment can now:
1. Create a backfill job via `POST /api/v1/db/backfill` with
`source: {Prometheus: {url: "http://prom:9090"}}`.
2. Start the backend with `--enable-backfill-worker`.
3. Watch the job transition `Queued → Running → Complete` in
`/api/v1/db/backfill/jobs/<id>`, with per-window precomputes
written to the store bit-identically to what live ingest would
have produced.
`S3Gorilla` and `ClickHouse` readers are the remaining Phase 5h-2 /
5h-3 work; `OtherSketch` (lossless widening from an existing sketch)
is Phase 5i.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
zzylol
added a commit
that referenced
this pull request
Apr 18, 2026
…r backfill) (#35) First real `RawSampleReader` in-tree. Turns the `BackfillSource::Prometheus { url }` variant from "factory returns no-reader error" into an actual working read path, so `--enable-backfill-worker` against a Prometheus- backed deployment now produces real backfills instead of shadow-mode Failed jobs. ## What's landed `src/stores/sketch_db/prometheus_reader.rs`: * `PrometheusReader::new(base_url) + with_step(d) + with_timeout(d)` — HTTP client wrapper over `/api/v1/query_range`. Step defaults to 15s (typical Prometheus scrape interval); overridable per-deployment. * `impl RawSampleReader` that: 1. Translates `LabelFilter { metric, equality }` into a PromQL matcher `metric{k1="v1",k2="v2"}` with stable key ordering and proper escape for quotes/backslashes/newlines. 2. Issues a `GET /api/v1/query_range` with `query`, `start`, `end`, `step` params. 3. Parses the `{status: "success", data: {resultType: "matrix", result: [{metric, values}]}}` JSON shape into `Vec<RawSample>`. 4. Maps every failure into `RawSampleReaderError::{InvalidRange, Upstream, Decode}` so the worker can surface the root cause on `BackfillJob::error_message`. * `ms_to_fractional_seconds` / `fractional_seconds_to_ms` helpers for the ms ↔ Prometheus-fractional-seconds conversion. * `render_series_key` rebuilds `metric{labels...}` from Prometheus's response metric map so downstream grouping and `extract_group_key` see the same shape they see for live samples. `backfill_service::default_reader_factory()`: * Routes `BackfillSource::Prometheus { url }` to `PrometheusReader::new`. * Returns a clear "reader for <source> not yet implemented" error for `S3Gorilla` / `ClickHouse` / `OtherSketch` so the controller sees exactly which reader is missing. `main.rs` now uses `default_reader_factory()` under `--enable-backfill-worker` instead of `noop_reader_factory`, so the service actually does work against Prometheus targets. ## On determinism (§10.5) `/api/v1/query_range` with step = scrape_interval returns one point per step per series. For deployments whose live ingest is Prometheus remote write (backend-side sketch-core construction), this gives the same sample sequence live saw, so the bit-identical parity test from PR #32 still holds. For deployments whose live ingest goes through DC's OTLP sketch-building path, approximate equivalence holds; exact bit-identical would need `/api/v1/read` + sketchlib-go parity work. Module doc flags that `/api/v1/read` (the protobuf remote-read protocol) is the upgrade path if we ever need sub-scrape-interval resolution; trait contract is unchanged. ## Test plan - [x] 8 unit tests: `build_promql` ordering + escape + bare-metric, `render_series_key` ordering + bare + fallback, fractional-seconds round-trip, inverted-range → `InvalidRange`. - [x] 8 integration tests against a spawned axum mock Prometheus: happy-path multi-series parse + param propagation, PromQL matcher synthesis, HTTP 5xx → Upstream, Prometheus `status: error` → Upstream, malformed JSON → Decode, ingest-order preservation per series, empty result, wrong `resultType` → Decode. - [x] 677 lib tests pass (up from 661). - [x] clippy `--workspace --all-targets --tests -- -D warnings` clean. - [x] `cargo fmt -- --check` clean. ## What this unblocks A Prometheus-backed deployment can now: 1. Create a backfill job via `POST /api/v1/db/backfill` with `source: {Prometheus: {url: "http://prom:9090"}}`. 2. Start the backend with `--enable-backfill-worker`. 3. Watch the job transition `Queued → Running → Complete` in `/api/v1/db/backfill/jobs/<id>`, with per-window precomputes written to the store bit-identically to what live ingest would have produced. `S3Gorilla` and `ClickHouse` readers are the remaining Phase 5h-2 / 5h-3 work; `OtherSketch` (lossless widening from an existing sketch) is Phase 5i. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zzylol
added a commit
that referenced
this pull request
May 11, 2026
…/+analyzer.rs (#130) Per design.md §5 target layout. Restructure controller/src/ from the historic layout (algebra/ + planner/ + config/ as ill-defined catch-alls mixed with stage_split.rs + analyzer.rs scattered top-level files) into the design.md modular split: query_parser/ (L1) → language_logical_plan/ (L2) → intent_algebra/ (L3) → sketch_algebra/ (L4 IR) → optimizer/ (L4 framework) → physical/ (L5 framework) → emit/ (L5 emitters) → pipeline.rs (L1→L5 driver). This is Step 2b/c/d/e/f from the architectural cleanup chain that started with #129 (Step 2a — capability consolidation). ## Old → new path mapping | Old | New | |---|---| | `algebra/expr.rs` | `intent_algebra/legacy_expr.rs` (kept; 91 consumers — unification is a follow-up) | | `algebra/lower.rs` | `intent_algebra/legacy_lower.rs` | | `algebra/{directory,physical,allocator,plan}.rs` | `physical/{sketch_catalog,planner,allocator,plan}.rs` | | `algebra/optimizer.rs` | `optimizer/engine.rs` | | `planner/{cost_model,delta,online,pareto,tco,wire_cost}.rs` | `optimizer/cost/{mod,delta,online,pareto,tco,wire}.rs` | | `planner/rules.rs` | `optimizer/rules/mod.rs` | | `planner/baseline_planner.rs` | `optimizer/baseline.rs` | | `planner/stage_split.rs` | `physical/stage_split.rs` | | `analyzer.rs` | `pipeline.rs` | | `stage_split/` | `physical/colored_dag/` (with colored_dag.rs → dag.rs) | | `query_language/` | `query_parser/language/` | | `config/workloads.rs` | `workload.rs` | | `config/{agent,backend,asapquery_backend,precompute,stage_config,stage_config_otap,stage_config_telegraf}.rs` | `emit/{agent,backend,asapquery_backend,precompute,stage_config,otap,telegraf}.rs` | | (new) | `physical/topology.rs`, `emit/trait_def.rs`, `optimizer/trait_def.rs`, `deployment_model.rs` | ## Pragmatic concessions (documented TODOs) - **`algebra/expr.rs` (1,230 lines) → `intent_algebra/legacy_expr.rs`**: the brief assumed only one stray consumer of `algebra::expr::AggIntent` existed, but reality is ~91 sites across query_parser, language_logical_plan, planner, algebra, and config use the legacy `QueryExpr` / `AggIntent` / `WindowSpec` IR. Treating these as "duplicates to delete" would have required rewriting every consumer in one PR. Moved the file with a doc-comment marking it as legacy IR pending unification with the canonical `intent_algebra::{agg_intent,query_expr,schema}` types. Same for `algebra/lower.rs` → `intent_algebra/legacy_lower.rs`. - **`config/stage_config.rs` (3,020 lines)** moved whole as `emit/stage_config.rs` rather than split into the design.md-prescribed `emit/{opamp,streaming_config,inference_config}.rs`. The monolith mixes OTel-collector YAML emit, ASAPQuery-backend JSON emit, storage-routing JSON emit, and many shared internals; a clean split needs ownership reorganisation, not file renames. - **Back-compat module aliases** in `lib.rs` (`pub use intent_algebra:: legacy_expr as algebra;` etc.) — `controller/src/main.rs` references `crate::stage_split::*`, `crate::algebra::*`, `crate::planner::*`, `crate::config::*`, `crate::analyzer::*` (the bin crate, not the lib). Keeping shims in `lib.rs` lets main.rs continue compiling without per-line edits. Remove the shims once main.rs migrates. - **`hll_accuracy()` / `countmin_accuracy()`** moved from `legacy_expr.rs` → `sketch_algebra/capability.rs` (their structural home — sketch-family error bounds). `legacy_expr.rs` retains thin `pub use` re-exports so in-file callers (e.g. `AggIntent::default_cardinality`) keep compiling. ## docs/design.md updates - §3 layer table — rows rewritten with new `controller/src/*` paths + italicised `*Refactor 2026-05 absorbed ...*` notes. - §5 Target repo layout — prepended blockquote: multi-crate split deferred until ≥2 deployment models ship; lookup table mapping `crates/core/*` targets to current single-crate paths. - §6 Core crate details — prepended blockquote: 16-row lookup table. - **§16 NEW** — ADR section. §16.1 documents PR #129's capability consolidation; §16.2 documents this PR's retirement plan with the full mapping table and four TODOs. ## Build + test - `cargo build --release -p controller` → clean (4 warnings, pre-existing) - `cargo build --release -p query_engine_rust` → clean (3 warnings) - `cargo test -p controller --lib` → **633/633 pass** (unchanged from #129) - `cargo test -p query_engine_rust --lib -- engines::warm_tier` → **13/13 pass** ## Diff: 55 files, +568 / -309 (file renames + import-path rewires) ## Follow-ups 1. Migrate consumers of `intent_algebra::legacy_expr::*` onto canonical `intent_algebra::{query_expr,agg_intent,schema}` types; delete the `legacy_*.rs` files. 2. Split `emit/stage_config.rs` (3,020 lines) per design.md §5. 3. Implement the placeholder traits (`OptimizerRule`, `PlanEmitter`, `DeploymentModelRegistry`). Existing free-function emitters + rule loops keep behaviour stable. 4. Migrate `controller/src/main.rs` off the back-compat module aliases in `lib.rs` (`crate::algebra::*` etc.); remove the shims. 5. (Separate task #32) `sketch_algebra/` touch-up: drop `AggIntent::HistogramQuantile` from semantic IR; map Min/Max to QuantileApprox; consolidate SketchCapability vs SketchCapabilities; rename params.rs → sketch_params.rs; add `FrequencyEstimate` capability variant + `CountSketchWithHeap` handle. Co-authored-by: zz_y <zz_y@node0.zz-y-304941.softmeasure-pg0.clemson.cloudlab.us> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zzylol
added a commit
that referenced
this pull request
May 11, 2026
…DO 3+4) (#132) Two of the four PR #130 follow-up TODOs land here. The other two are blocked by architectural reality (see below). ## TODO 3 — Placeholder traits made functional ✅ ### `OptimizerRule` trait - `controller/src/optimizer/trait_def.rs`: extended trait with `name()`, `category()`, `apply()`. Added two new `RuleCategory` variants (`Cse`, `Decorrelate`) beyond the original four. - `controller/src/optimizer/engine.rs`: per-rule `OptimizerRule` impls for all 12 engine rules (delegating to the existing `RewriteRule` free fns). New `default_rules_as_optimizer_rules()` helper returns `Vec<Box<dyn OptimizerRule>>` for callers that want the trait surface. - `controller/src/optimizer/mod.rs`: blanket impl for every `sketch_algebra::rules::Rule` (lives outside sketch_algebra/ to respect Step #32's territory boundary). ### `PlanEmitter` trait - `controller/src/emit/trait_def.rs`: kept the associated-type shape; added 4 concrete impls — `OpampEmitter`, `OpampGatewayEmitter`, `StreamingConfigEmitter`, `InferenceConfigEmitter` — each wraps the existing free-function emitter. `InferenceConfigInput<'a>` bundles per-tenant routing inputs (tenant id + metric plans + Mode-3 metrics). `emit_borrowed` variant avoids forcing `'static` on the trait-method path. ### `DeploymentModelRegistry` - `controller/src/deployment_model.rs`: extended from stub `HashMap<DeploymentModelId, ()>` to a real `HashMap<…, DeploymentModel>` where `DeploymentModel` owns `(id, rules: Vec<Box<dyn OptimizerRule>>, emitters: EmitterSet)`. `Default::default()` registers `asaplifecycle` with the engine's 12-rule library + the 4 demo emitter names so `pipeline::run_pipeline` doesn't need manual registration. ## TODO 4 — main.rs migrated off back-compat aliases ✅ Rewrote all 52 reference sites in `controller/src/main.rs`: - `algebra::*` → `optimizer::engine::*` / `physical::*` / `intent_algebra::legacy_expr::*` (depending on context) - `analyzer::*` → `pipeline::*` - `config::*` → `emit::*` / `workload::*` - `planner::*` → `optimizer::*` / `physical::stage_split` / `optimizer::cost::online as online_cost_model` - `stage_split::*` → `physical::colored_dag::*` Removed all back-compat shims from `lib.rs`: - `pub use emit as config` - `pub use pipeline as analyzer` - `pub mod algebra { … }` - `pub use physical::colored_dag as stage_split` - `pub mod planner { … }` One leftover internal ref (`crate::planner::rules::bind_workload_typed` inside `emit/mod.rs::collect_metric_to_family`) retargeted to `crate::optimizer::rules::bind_workload_typed`. `lib.rs` shrank from 184 → 95 lines. ## TODO 1 — legacy_expr migration: BLOCKED 🚫 The canonical `intent_algebra::{query_expr,agg_intent,schema}` is NOT a structural superset of the legacy `intent_algebra::legacy_expr`. The canonical `QueryExpr` has 5 variants (`Scan`, `Window`, `Aggregate`, `LetBinding`, `Ref`); legacy has ~26 (`Source`, `Filter`, `Project`, `Aggregate`, `Window`, `SketchAgg`, `WindowedAgg`, `Partition`, `Dedup`, `TopK`, `Merge`, `Join`, `JoinSketch`, `SetOp`, `Sort`, `Limit`, `Subquery`, `LetBinding`, `WindowFunc`, `HistogramQuantile`, `PromQLSubquery`, `BinaryOp`, `Ref`) plus a full `ScalarExpr` IR (Column, Literal, BinaryOp, UnaryOp, FunctionCall, ScalarSubquery, InList, InSubquery, Between, IsNull, Case, Cast, VectorBinaryOp). The canonical IR's own module docs flag this: *"the full design.md list is larger; they are deferred to follow-up phases as the planner grows consumers for them"*. Migrating consumers requires first GROWING the canonical IR to absorb these variants — a substantial separate refactor, not a follow-up cleanup. 50+ consumer sites in query_parser/, language_logical_plan/, optimizer/engine.rs, physical/{allocator,planner,plan,stage_split}.rs depend on the legacy variant set. Recommend retitling the follow-up as "grow canonical L3 to absorb legacy variants" rather than "migrate consumers". The legacy_expr module stays in place pending that work. ## TODO 2 — stage_config.rs split: DEFERRED 🟡 The 3,020-line `emit/stage_config.rs` monolith mixes OTel YAML emit, backend JSON emit, and storage-routing emit with intricately shared helpers (`sketch_kind_to_processor_name`, `build_otlp_exporter`, `sketch_kind_tag`, …) and 1,580 lines of tightly-interleaved tests (line 1441 onward). The wrapper structs from TODO 3 above already provide the polymorphic `PlanEmitter` surface (`OpampEmitter` / `StreamingConfigEmitter` / `InferenceConfigEmitter`), so the file-level split is no longer load-bearing for the trait work. Recommend addressing as a separate PR with dedicated time budget if/when the monolith becomes a review-bottleneck. ## Build + test - `cargo build --release -p controller` — clean - `cargo build --release -p query_engine_rust` — clean - `cargo test --release -p controller --lib` — **646 passed** (was 633; +13 trait tests) - `cargo test --release -p controller --bin controller` — **27 passed** - `cargo test --release -p query_engine_rust --lib -- engines::warm_tier` — **13 passed** ## Caveat — `controller/src/store/` PR #130 left `lib.rs` declaring `pub mod store;` but `controller/src/store/` is in `.gitignore` (line ~10 of repo root .gitignore — the broad `store/` pattern). The directory is untracked; this needs a separate decision on whether it should be tracked or remain a runtime artifact. The build works because the directory exists locally on the dev box; fresh clones would fail. Not in scope for this PR; flagging as follow-up. ## Diff: 8 files, +785 / -207 Co-authored-by: zz_y <zz_y@node0.zz-y-304941.softmeasure-pg0.clemson.cloudlab.us> 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
Turns backfill from shadow-mode into real work. A queued job now reads raw samples, rebuilds the sketch bit-identically to live, and writes per-window precomputes. 1,515 LoC across 3 new modules + wiring.
backfill_window_builder.rs— purebuild_backfilled_accumulator(config, samples)rebuild primitive.backfill_processor.rs—BackfillWindowProcessorimplementing Phase 5c'sWindowProcessortrait: groups samples by grouping_labels, batch-writes(PrecomputedOutput, accumulator)pairs, records provenance.backfill_service.rs— tokio drain task with aReaderFactoryabstraction so Phase 5h can plug Prometheus / S3 / ClickHouse readers without touching the service.BackfillRegistry::create_checkedenforces §10.5 time-disjoint —end_ms <= schema.created_at_ms.record_window_written/windows_written_bygive Phase 5f a provenance list without an on-disk format change.--enable-backfill-workerCLI flag, off by default (preserves shadow-mode); on, usesnoop_reader_factoryuntil real readers ship in 5h.Determinism
Pre-implementation audit found sketch-core's Rust live path is fully deterministic (xxhash seeded by row index, preserved sample order, no order-sensitive FP). For raw-ingest deployments, backfill is bit-identical — locked by a test that builds the same accumulator via live and backfill paths and asserts
serialize_to_bytes()equality.For DC-ingest deployments (backend receives pre-built sketches from DC's Go sketchlib), bit-identicalness requires a cross-language audit — tracked as separate follow-up work.
Separation
Worker pipeline (PrecomputeEngine → SeriesRouter → Worker → active_panes → output_sink) is NOT reused. The pure stateless
create_accumulator_updaterfactory IS reused (via thebuild_backfilled_accumulatorfacade). Module docs explain the choice; swapping to a copy-pasted factory is a one-line change if strict duplication is required.Test plan
Open
🤖 Generated with Claude Code