feat: physical planner — plan() builds PhysicalNode tree with Placement + Exchange (Layer 5) - #117
Merged
Merged
Conversation
Refactors the sketch algebra to cleanly separate the logical plan
(Layer 3) from physical implementation details (Layer 5).
Core changes:
- Rename SketchAggOp → AggIntent with implementation-independent variants:
DDSketch{quantiles,epsilon} → Quantile{quantiles,accuracy}
HLL{registers} → Cardinality{accuracy}
CountMin/CountSketch{w,d} → Frequency{accuracy}
ExactMinMax{min,max} → Extrema{min,max}
Hydra{inner,partition_keys} → PerPartition{inner,keys}
- Add WindowSpec{kind,time_col} and WindowKind enum
(Tumbling, Sliding, Unbounded, Landmark, Session)
- Add QueryExpr::WindowedAgg{agg,window,col,input} — bundles
window and sketch agg because the window defines sketch lifecycle
- Keep SketchAggOp as backward-compat type alias for AggIntent
- Update directory, allocator, optimizer, stage_split, parsers
- Update docs/query-to-sketch-translation.md with 5-layer design
The logical plan now expresses WHAT to compute (quantile at φ=0.99
with ≤1% error) not HOW (DDSketch vs KLL vs PromSketch EHKLL).
Physical implementation selection moves to the physical planner.
279 tests pass, no business logic changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…cal.rs Phase 1 — Layer 3 cleanup: - Remove SketchAggOp type alias and all backward-compat helpers (default_ddsketch, default_hll, default_count_min, default_count_sketch) - All callers now use implementation-independent names: default_quantile, default_cardinality, default_frequency - Zero physical sketch names (DDSketch, HLL, CountMin) remain in Layer 3 Phase 2 — Layer 5 formalization: - New algebra/physical.rs with PhysicalAggOp struct and resolve() function - PhysicalAggOp bundles: intent + sketch_type + sketch_params + memory estimate - resolve() is the single Layer 3→5 boundary: AggIntent → physical implementation - stage_split.rs and allocator.rs now call physical::resolve() instead of inline directory::sketch_type_for_op/sketch_params_for_op Phase 3 — Layer documentation: - promql.rs header documents Layers 1→3 lowering - algebra/mod.rs documents the full 5-layer module layout - docs/query-to-sketch-translation.md updated with 5-layer architecture 283 tests pass (279 + 4 new physical.rs tests). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both parsers now emit Layer 2 relational operators (Aggregate + AggFunc), and a shared lowering pass (algebra/lower.rs) converts to Layer 3 sketch algebra (SketchAgg + AggIntent). Layer compliance: - Layer 1-2 (parsers): zero AggIntent in code — only Aggregate + AggFunc - Layer 2→3 (lower.rs): shared lower_to_sketch_algebra() for both languages - Layer 3 (expr.rs): AggIntent, zero physical sketch names in code - Layer 4 (optimizer.rs): matches AggIntent, zero SketchType references - Layer 5 (physical.rs): resolve() maps AggIntent → SketchType + SketchParams Changes: - promql.rs: walk_call_to_op returns AggFunc (not AggIntent); build_qe_sketched → build_qe_aggregate emitting Aggregate + Window nodes - lower.rs: new shared lowering pass (18 tests) — walks all 25 QueryExpr variants, converts single-agg Aggregate → SketchAgg where sketchable - mod.rs: parse_query_expr calls lower_to_sketch_algebra after parsing 301 tests pass (283 + 18 new lowering tests). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…, L4 is deployment-aware - Layers 1-3: query-language-independent and workload-independent (same AggIntent whether from PromQL, SQL, DataFusion, or ElasticDSL) - Layer 4: deployment-constraint-aware optimizer (memory budgets, network topology, available backends) - Layer 5: deployment-specific physical plan Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. WindowedAgg emission: lower.rs fuses Window + Aggregate → WindowedAgg
when the inner Aggregate is sketchable (2 new tests)
2. SQL TUMBLE/HOP/time_bucket: SQL parser detects TUMBLE(ts, INTERVAL),
HOP(ts, slide, size), and time_bucket('5 minutes', ts) in GROUP BY
and emits Window nodes (4 new tests)
3. PhysicalPlan tree: physical.rs expanded with PhysicalOp enum
(OtelSketchBuild, PromSketchBuild, SketchMerge, SketchEval, Exchange),
Placement enum, PhysicalNode tree, PhysicalCost, resolve_window()
(4 new tests)
4. Optimizer deployment constraints: DeploymentConstraints struct with
agent/backend memory budgets, promsketch_available, agent_supports_sliding,
bandwidth. QueryOptimizer::with_constraints() builder. DefaultCostModel
penalises sketches exceeding agent budget.
5. Doc examples: all 4 concrete examples rewritten to show all 5 layers
(Layer 1 AST → Layer 2 Aggregate+AggFunc → Layer 3 AggIntent →
Layer 4 optimizer → Layer 5 physical resolve + config emit)
311 tests pass (301 + 10 new).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Fix label_matchers YAML format: controller now emits structured
[{key: "env", value: "prod"}] matching the Go LabelMatcher struct,
not flat ["env=prod"] strings.
2. Add bootstrap config endpoints for collectors:
- GET /api/v1/collector-config/agent → default agent YAML
- GET /api/v1/collector-config/backend → default backend YAML
Collectors can start with:
./sketchcol --config "http://controller:8080/api/v1/collector-config/agent"
3. Add sketchcol unified collector builder config with ALL sketch
processors (DDSketch, HLL, KLL, CountSketch, CountMinSketch +
merge variants) in a single binary.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 task
…ectStore Replace OTel/PromSketch-specific names with deployment-agnostic terms: - edge processors (sketch build at data source) - backend sketchDB (merge + approximate query) - backend original DB (exact computation) - object store (raw data backup) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…owering Section 3 now clearly separates: - 3.1 Layer 1→2: language AST → language logical plan (Aggregate + AggFunc) Both PromQL and SQL produce the same relational operators, no sketch names. - 3.2 Layer 2→3: shared lowering algorithm converts AggFunc → AggIntent Includes the full mapping table and Window→WindowedAgg fusion rule. Removes all physical sketch names (DDSketch, HLL, CountMin) from the parsing section — those belong only to Layer 5. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The PromQL parser now puts partition keys from `topk by (dims)` into
the inner Aggregate's `keys` field instead of wrapping with a separate
Partition node. This fixes two bugs:
1. Count inside topk was not lowered to Frequency — the lowering pass
saw Count-without-GROUP-BY and skipped it. Now it sees
Count-with-GROUP-BY → Frequency.
2. Double Partition wrapping — topk added Partition, and
apply_qe_partition added another. Now the lowering pass creates
the single Partition from the Aggregate's keys.
Example 5 now produces:
TopK { 10, by: ["service"],
Partition { ["service"],
WindowedAgg { Frequency, Tumbling(1m), Filter(Source) } } }
Doc updated: fixed PromQL syntax (topk by (service) (10, ...)),
added Layer 1 + Layer 2 sections, corrected Layer 3 output.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nt, inserts Exchange Replaces the placeholder physical.rs with a working physical planner: physical::plan(expr, config) → PhysicalNode tree The planner walks the optimized QueryExpr bottom-up and: 1. Assigns each node to a Placement (AgentCollector, BackendCollector, QueryEngine, Database) based on the operator type and memory budgets 2. Resolves AggIntent → concrete SketchType + SketchParams via resolve() 3. Resolves WindowSpec → PhysicalWindow via resolve_window() 4. Inserts Exchange nodes at stage boundaries (Agent→Backend, Backend→QueryEngine) 5. Defers sketches to Backend/QueryEngine when agent budget is exceeded Placement rules: - Source, Filter, SketchAgg, WindowedAgg → AgentCollector - Partition, Merge, Dedup → BackendCollector - TopK, HistogramQuantile, BinaryOp, PromQLSubquery → QueryEngine - Aggregate (non-sketch exact) → Database - Sort, Limit, Project → inherit child placement PhysicalNode provides: node_count(), placements(), exchange_count() 8 new tests verify placement, exchange insertion, budget deferral, and full multi-stage pipeline (Agent→Backend→QueryEngine). 323 total tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Main had the placeholder physical.rs (from PR #116 merge). This branch has the real physical planner with plan(), Placement, Exchange insertion. Keep ours. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rees All four examples now show the actual PhysicalNode tree output from physical::plan(), including: - Placement annotations ([AgentCollector], [BackendCollector], etc.) - Exchange nodes at stage boundaries - Concrete sketch types and window implementations Example 4: single-stage (all Agent, no Exchange) Example 5: 3-stage (Agent→Backend→QueryEngine, 2 Exchanges) Example 6: Agent→Database (raw passthrough for non-mergeable AVG) Example 7: 3-stage with HLL (Agent→Backend→QueryEngine) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Completes Layer 5: main.rs now calls physical_plan_to_staged() instead
of split_expr_by_stage().
New in physical.rs:
- PhysicalNode::to_staged_plan() — walks the tree and extracts a flat
StagedPlan (AgentSubPlan, BackendSubPlan, PrecomputeSubPlan, DbSubPlan)
from the PhysicalNode tree. Bridges the physical planner to existing
config generators.
- physical_plan_to_staged(expr, budgets) → (StagedPlan, PhysicalNode) —
single entry point for main.rs: runs plan() then to_staged_plan().
Changed in main.rs:
- Replaced `use planner::stage_split::split_expr_by_stage` with
`use algebra::physical::physical_plan_to_staged`
- handle_plan now calls physical_plan_to_staged(&opt_qe, &budgets)
The data flow is now:
parse → lower → optimize → physical::plan() → PhysicalNode tree
→ to_staged_plan() → StagedPlan → config generators → YAML
3 new tests: staged_plan_simple_sketch, staged_plan_topk_multi_stage,
staged_plan_exact_agg_at_db. 326 total pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
zzylol
added a commit
that referenced
this pull request
Apr 4, 2026
Rebased on latest main. Dropped stale controller changes (already on main via PRs #96, #116, #117, #119). Only benchmark tooling remains: - replay.py: replay DEBS dataset through OTel collector - run.py: orchestrate benchmark runs - scrape.py: collect Prometheus metrics during run - analyze.py: parse benchmark results - compare.py: compare sketch results to ground truth - summarize.py: generate summary tables - ground_truth/: ground truth computation for accuracy checking Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
zzylol
added a commit
that referenced
this pull request
Apr 4, 2026
Rebased on latest main. Dropped stale controller changes (already on main via PRs #96, #116, #117, #119). Only benchmark tooling remains: - replay.py: replay DEBS dataset through OTel collector - run.py: orchestrate benchmark runs - scrape.py: collect Prometheus metrics during run - analyze.py: parse benchmark results - compare.py: compare sketch results to ground truth - summarize.py: generate summary tables - ground_truth/: ground truth computation for accuracy checking Co-authored-by: zz_y <zeyingz@umd.edu> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SieDeta
pushed a commit
that referenced
this pull request
Apr 17, 2026
…nt + Exchange (Layer 5) (#117) * feat: 5-layer architecture — AggIntent, WindowSpec, WindowedAgg Refactors the sketch algebra to cleanly separate the logical plan (Layer 3) from physical implementation details (Layer 5). Core changes: - Rename SketchAggOp → AggIntent with implementation-independent variants: DDSketch{quantiles,epsilon} → Quantile{quantiles,accuracy} HLL{registers} → Cardinality{accuracy} CountMin/CountSketch{w,d} → Frequency{accuracy} ExactMinMax{min,max} → Extrema{min,max} Hydra{inner,partition_keys} → PerPartition{inner,keys} - Add WindowSpec{kind,time_col} and WindowKind enum (Tumbling, Sliding, Unbounded, Landmark, Session) - Add QueryExpr::WindowedAgg{agg,window,col,input} — bundles window and sketch agg because the window defines sketch lifecycle - Keep SketchAggOp as backward-compat type alias for AggIntent - Update directory, allocator, optimizer, stage_split, parsers - Update docs/query-to-sketch-translation.md with 5-layer design The logical plan now expresses WHAT to compute (quantile at φ=0.99 with ≤1% error) not HOW (DDSketch vs KLL vs PromSketch EHKLL). Physical implementation selection moves to the physical planner. 279 tests pass, no business logic changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: complete 5-layer implementation — remove SketchAggOp, add physical.rs Phase 1 — Layer 3 cleanup: - Remove SketchAggOp type alias and all backward-compat helpers (default_ddsketch, default_hll, default_count_min, default_count_sketch) - All callers now use implementation-independent names: default_quantile, default_cardinality, default_frequency - Zero physical sketch names (DDSketch, HLL, CountMin) remain in Layer 3 Phase 2 — Layer 5 formalization: - New algebra/physical.rs with PhysicalAggOp struct and resolve() function - PhysicalAggOp bundles: intent + sketch_type + sketch_params + memory estimate - resolve() is the single Layer 3→5 boundary: AggIntent → physical implementation - stage_split.rs and allocator.rs now call physical::resolve() instead of inline directory::sketch_type_for_op/sketch_params_for_op Phase 3 — Layer documentation: - promql.rs header documents Layers 1→3 lowering - algebra/mod.rs documents the full 5-layer module layout - docs/query-to-sketch-translation.md updated with 5-layer architecture 283 tests pass (279 + 4 new physical.rs tests). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: both PromQL and SQL now follow 5-layer architecture Both parsers now emit Layer 2 relational operators (Aggregate + AggFunc), and a shared lowering pass (algebra/lower.rs) converts to Layer 3 sketch algebra (SketchAgg + AggIntent). Layer compliance: - Layer 1-2 (parsers): zero AggIntent in code — only Aggregate + AggFunc - Layer 2→3 (lower.rs): shared lower_to_sketch_algebra() for both languages - Layer 3 (expr.rs): AggIntent, zero physical sketch names in code - Layer 4 (optimizer.rs): matches AggIntent, zero SketchType references - Layer 5 (physical.rs): resolve() maps AggIntent → SketchType + SketchParams Changes: - promql.rs: walk_call_to_op returns AggFunc (not AggIntent); build_qe_sketched → build_qe_aggregate emitting Aggregate + Window nodes - lower.rs: new shared lowering pass (18 tests) — walks all 25 QueryExpr variants, converts single-agg Aggregate → SketchAgg where sketchable - mod.rs: parse_query_expr calls lower_to_sketch_algebra after parsing 301 tests pass (283 + 18 new lowering tests). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: fix layer descriptions — L1-3 are language/workload-independent, L4 is deployment-aware - Layers 1-3: query-language-independent and workload-independent (same AggIntent whether from PromQL, SQL, DataFusion, or ElasticDSL) - Layer 4: deployment-constraint-aware optimizer (memory budgets, network topology, available backends) - Layer 5: deployment-specific physical plan Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: complete 5-layer implementation — all gaps fixed 1. WindowedAgg emission: lower.rs fuses Window + Aggregate → WindowedAgg when the inner Aggregate is sketchable (2 new tests) 2. SQL TUMBLE/HOP/time_bucket: SQL parser detects TUMBLE(ts, INTERVAL), HOP(ts, slide, size), and time_bucket('5 minutes', ts) in GROUP BY and emits Window nodes (4 new tests) 3. PhysicalPlan tree: physical.rs expanded with PhysicalOp enum (OtelSketchBuild, PromSketchBuild, SketchMerge, SketchEval, Exchange), Placement enum, PhysicalNode tree, PhysicalCost, resolve_window() (4 new tests) 4. Optimizer deployment constraints: DeploymentConstraints struct with agent/backend memory budgets, promsketch_available, agent_supports_sliding, bandwidth. QueryOptimizer::with_constraints() builder. DefaultCostModel penalises sketches exceeding agent budget. 5. Doc examples: all 4 concrete examples rewritten to show all 5 layers (Layer 1 AST → Layer 2 Aggregate+AggFunc → Layer 3 AggIntent → Layer 4 optimizer → Layer 5 physical resolve + config emit) 311 tests pass (301 + 10 new). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: fix end-to-end OTel integration gaps 1. Fix label_matchers YAML format: controller now emits structured [{key: "env", value: "prod"}] matching the Go LabelMatcher struct, not flat ["env=prod"] strings. 2. Add bootstrap config endpoints for collectors: - GET /api/v1/collector-config/agent → default agent YAML - GET /api/v1/collector-config/backend → default backend YAML Collectors can start with: ./sketchcol --config "http://controller:8080/api/v1/collector-config/agent" 3. Add sketchcol unified collector builder config with ALL sketch processors (DDSketch, HLL, KLL, CountSketch, CountMinSketch + merge variants) in a single binary. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: update physical plan terminology — edge/sketchDB/originalDB/objectStore Replace OTel/PromSketch-specific names with deployment-agnostic terms: - edge processors (sketch build at data source) - backend sketchDB (merge + approximate query) - backend original DB (exact computation) - object store (raw data backup) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: rewrite §3 as two clear steps — Layer 1→2 parsing + Layer 2→3 lowering Section 3 now clearly separates: - 3.1 Layer 1→2: language AST → language logical plan (Aggregate + AggFunc) Both PromQL and SQL produce the same relational operators, no sketch names. - 3.2 Layer 2→3: shared lowering algorithm converts AggFunc → AggIntent Includes the full mapping table and Window→WindowedAgg fusion rule. Removes all physical sketch names (DDSketch, HLL, CountMin) from the parsing section — those belong only to Layer 5. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: propagate topk partition keys into inner Aggregate GROUP BY The PromQL parser now puts partition keys from `topk by (dims)` into the inner Aggregate's `keys` field instead of wrapping with a separate Partition node. This fixes two bugs: 1. Count inside topk was not lowered to Frequency — the lowering pass saw Count-without-GROUP-BY and skipped it. Now it sees Count-with-GROUP-BY → Frequency. 2. Double Partition wrapping — topk added Partition, and apply_qe_partition added another. Now the lowering pass creates the single Partition from the Aggregate's keys. Example 5 now produces: TopK { 10, by: ["service"], Partition { ["service"], WindowedAgg { Frequency, Tumbling(1m), Filter(Source) } } } Doc updated: fixed PromQL syntax (topk by (service) (10, ...)), added Layer 1 + Layer 2 sections, corrected Layer 3 output. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: real physical planner — plan() walks QueryExpr, assigns Placement, inserts Exchange Replaces the placeholder physical.rs with a working physical planner: physical::plan(expr, config) → PhysicalNode tree The planner walks the optimized QueryExpr bottom-up and: 1. Assigns each node to a Placement (AgentCollector, BackendCollector, QueryEngine, Database) based on the operator type and memory budgets 2. Resolves AggIntent → concrete SketchType + SketchParams via resolve() 3. Resolves WindowSpec → PhysicalWindow via resolve_window() 4. Inserts Exchange nodes at stage boundaries (Agent→Backend, Backend→QueryEngine) 5. Defers sketches to Backend/QueryEngine when agent budget is exceeded Placement rules: - Source, Filter, SketchAgg, WindowedAgg → AgentCollector - Partition, Merge, Dedup → BackendCollector - TopK, HistogramQuantile, BinaryOp, PromQLSubquery → QueryEngine - Aggregate (non-sketch exact) → Database - Sort, Limit, Project → inherit child placement PhysicalNode provides: node_count(), placements(), exchange_count() 8 new tests verify placement, exchange insertion, budget deferral, and full multi-stage pipeline (Agent→Backend→QueryEngine). 323 total tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: update Layer 5 examples to show physical::plan() PhysicalNode trees All four examples now show the actual PhysicalNode tree output from physical::plan(), including: - Placement annotations ([AgentCollector], [BackendCollector], etc.) - Exchange nodes at stage boundaries - Concrete sketch types and window implementations Example 4: single-stage (all Agent, no Exchange) Example 5: 3-stage (Agent→Backend→QueryEngine, 2 Exchanges) Example 6: Agent→Database (raw passthrough for non-mergeable AVG) Example 7: 3-stage with HLL (Agent→Backend→QueryEngine) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: wire physical planner into main.rs — replaces split_expr_by_stage Completes Layer 5: main.rs now calls physical_plan_to_staged() instead of split_expr_by_stage(). New in physical.rs: - PhysicalNode::to_staged_plan() — walks the tree and extracts a flat StagedPlan (AgentSubPlan, BackendSubPlan, PrecomputeSubPlan, DbSubPlan) from the PhysicalNode tree. Bridges the physical planner to existing config generators. - physical_plan_to_staged(expr, budgets) → (StagedPlan, PhysicalNode) — single entry point for main.rs: runs plan() then to_staged_plan(). Changed in main.rs: - Replaced `use planner::stage_split::split_expr_by_stage` with `use algebra::physical::physical_plan_to_staged` - handle_plan now calls physical_plan_to_staged(&opt_qe, &budgets) The data flow is now: parse → lower → optimize → physical::plan() → PhysicalNode tree → to_staged_plan() → StagedPlan → config generators → YAML 3 new tests: staged_plan_simple_sketch, staged_plan_topk_multi_stage, staged_plan_exact_agg_at_db. 326 total pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SieDeta
pushed a commit
that referenced
this pull request
Apr 17, 2026
Rebased on latest main. Dropped stale controller changes (already on main via PRs #96, #116, #117, #119). Only benchmark tooling remains: - replay.py: replay DEBS dataset through OTel collector - run.py: orchestrate benchmark runs - scrape.py: collect Prometheus metrics during run - analyze.py: parse benchmark results - compare.py: compare sketch results to ground truth - summarize.py: generate summary tables - ground_truth/: ground truth computation for accuracy checking Co-authored-by: zz_y <zeyingz@umd.edu> Co-authored-by: Claude Opus 4.6 (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.
Files changed
controller/src/algebra/physical.rs(+471 lines)New function:
plan(expr: &QueryExpr, config: &PhysicalPlannerConfig) → PhysicalNodeWalks the optimized QueryExpr tree bottom-up and produces a PhysicalNode tree where each node has:
op: PhysicalOp— concrete operator (OtelSketchBuild, SketchMerge, TopK, DbQuery, Exchange, etc.)placement: Placement— which pipeline stage runs it (AgentCollector, BackendCollector, QueryEngine, Database)cost: PhysicalCost— estimated bandwidth, memory, CPUchildren: Vec<PhysicalNode>— child nodesNew function:
decide_sketch_placement(resolved, budgets) → PlacementAssigns sketch operations to Agent by default. Defers to BackendCollector when agent memory budget is exceeded, and to QueryEngine when both are exceeded.
New function:
insert_exchange_if_needed(node)When a parent node's placement differs from a child's, wraps the child in an Exchange node with the appropriate format:
ExchangeFormat::OtlpExchangeFormat::OtlpExchangeFormat::SketchBinaryExchangeFormat::RawSamplesNew struct:
PhysicalPlannerConfigHolds
StageResourceBudgetsfor budget-based deferral decisions.New methods on
PhysicalNode:node_count()— total nodes in treeplacements()— distinct placements in treeexchange_count()— number of Exchange boundariesPlacement rules implemented in
plan_node():controller/docs/query-to-sketch-translation.md(updated)Layer 5 sections for all four concrete examples rewritten to show actual
plan()output as PhysicalNode trees with Placement and Exchange annotations:Example 4 (PromQL quantile): single-stage, all AgentCollector, 0 Exchanges
Example 5 (PromQL topk): 3-stage, 2 Exchanges
Example 6 (SQL AVG): Agent→Database passthrough
Example 7 (SQL TUMBLE COUNT DISTINCT): 3-stage with HLL
Tests
8 new tests in
physical.rs:plan_simple_sketch_at_agent— SketchAgg placed at Agentplan_windowed_agg_has_window— WindowedAgg resolves physical windowplan_topk_at_query_engine— TopK placed at QueryEngineplan_topk_inserts_exchange— Exchange inserted between Agent and QueryEngineplan_partition_at_backend— Partition placed at Backendplan_aggregate_at_database— non-sketch Aggregate placed at Databaseplan_full_pipeline_has_multiple_stages— verifies 3 stages + ≥2 exchangesplan_budget_deferral— tiny agent budget defers sketch to Backend323 total tests pass.
🤖 Generated with Claude Code