Skip to content

SP-9: AST-aware hierarchical stage assignment — QueryExpr stage split + recursive PromQL serialiser - #94

Merged
zzylol merged 4 commits into
mainfrom
90-sp-9-ast-aware-hierarchical-stage-assignment-edge-collector-leaf-nodes-precompute-engine-upper-ast
Apr 1, 2026
Merged

zzylol merged 4 commits into
mainfrom
90-sp-9-ast-aware-hierarchical-stage-assignment-edge-collector-leaf-nodes-precompute-engine-upper-ast

Conversation

@zzylol

@zzylol zzylol commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Closes #90.

What this PR actually does

SP-9 introduces per-stage sub-plans produced by walking the full query algebra AST, replacing the flat SP-3 assignment that put everything on the Agent stage.


New types in types.rs

  • StageResourceBudgets — per-stage memory/CPU caps (agent_memory_bytes, agent_cpu_micros_per_sample, backend_memory_bytes, precompute_memory_bytes)
  • AgentSubPlan, BackendSubPlan, PrecomputeSubPlan, DbSubPlan — per-stage result structs
  • StagedPlan — wraps the four sub-plans + a deferral_log: Vec<String> for observability
  • CollectionPlan::staged_plan: Option<StagedPlan>Some when a query_string was supplied, None otherwise (SP-3 flat assignment remains the fallback)

planner/stage_split.rs (new, ~980 lines)

split_expr_by_stage(&QueryExpr, &StageResourceBudgets) → StagedPlan

The function takes a QueryExpr (from the general algebra, PR #108) and performs a bottom-up walk, assigning each node to a stage:

QueryExpr node Default stage
Source, Filter, Window, sketch Agg Agent OTel Collector
Partition, Merge, Dedup, Agg { Exact(Sum|Count|Min|Max) } Backend OTel Collector
HistogramQuantile, PromQLSubquery, BinaryOp, Sort+Limit (TopK) Precompute Engine
Agg { Exact(Avg) } DB-side (not mergeable across agents)

Budget deferral chain — when a sketch op's estimated memory exceeds the agent cap it is demoted to Backend; if it also exceeds the backend cap it moves to Precompute. Every demotion is logged in StagedPlan::deferral_log.

expr_to_promql(&QueryExpr) → String — recursive descent serialiser covering:

  • histogram_quantile(φ, rate(…[w])), quantile_over_time(φ, …[w])
  • expr[range:step] subqueries
  • Vector binary ops with on(…)/ignoring(…)/group_left/group_right
  • Aggregate GROUP BYop by (keys) (…)
  • Sort+Limittopk(n, …)

split_expr_by_stage takes &QueryExpr, not &SketchExpr

The initial commit used SketchExpr directly. A follow-up commit (Option B) ported everything to QueryExpr:

  • parse_query_sketch still returns SketchExpr (parsers unchanged)
  • handle_plan converts via QueryExpr::from_sketch_expr() before calling split_expr_by_stage
  • All node assignments and expr_to_promql operate purely on QueryExpr

config/backend.rs

generate_backend_config_staged() added: emits a dedup processor before the merge processor when BackendSubPlan::has_dedup is true.

config/precompute.rs

build_precompute_jobs() uses staged_plan.precompute.query_expr (the upper sub-tree serialised as PromQL) instead of the hardcoded quantile_over_time(0.99, …) template when the staged plan is present and active.

main.rs

handle_plan now runs:

parse_query_sketch(qs)
  → QueryExpr::from_sketch_expr()
  → QueryOptimizer::optimize()
  → split_expr_by_stage(&budgets)
  → plan.staged_plan = Some(StagedPlan)

SP-3 flat assignment is unchanged and remains the fallback when query_string is absent.


Test coverage — 16 new tests in planner/stage_split.rs (303 total)

Test What it covers
ddsketch_agg_goes_to_agent sketch Agg → Agent stage
hll_stays_at_agent_by_default HLL Agg → Agent stage
aggregate_sum_goes_to_backend Exact(Sum) → Backend
aggregate_without_group_by_avg_goes_to_db Exact(Avg) → DB (non-mergeable)
partition_group_by_goes_to_backend Partition node → Backend
topk_goes_to_precompute TopK / Sort+Limit → Precompute
histogram_quantile_activates_precompute HistogramQuantile → Precompute
binary_op_activates_precompute BinaryOp → Precompute
ddsketch_deferred_to_backend_when_agent_budget_exceeded budget deferral Agent→Backend
ddsketch_deferred_to_precompute_when_both_budgets_exceeded budget deferral Agent→Backend→Precompute
label_matchers_and_tree ScalarExpr AND-tree → AgentSubPlan::label_filters
promql_ddsketch_with_filter_and_window expr_to_promql for DDSketch + Window
promql_histogram_quantile expr_to_promql for histogram_quantile
promql_subquery_renders_range expr_to_promql for subquery [range:step]
promql_binary_op_renders_operator expr_to_promql for vector binary ops
promql_topk_wraps_inner expr_to_promql for Sort+Limit → topk

Backward compatibility

  • Analyzer::analyze() is unchanged; all existing callers compile without modification.
  • CollectionPlan::staged_plan defaults to None — no change to existing plan constructors.
  • SP-3 flat assignment is always the fallback when query_string is absent.

🤖 Generated with Claude Code

…leaf nodes, precompute engine → upper AST)

Implements SP-9 as described in docs/controller-optimization-problem.md.
The SketchExpr tree produced by the query parser is now threaded through to
the planner and split across pipeline stages instead of being discarded.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@zzylol
zzylol force-pushed the 90-sp-9-ast-aware-hierarchical-stage-assignment-edge-collector-leaf-nodes-precompute-engine-upper-ast branch from 5ea277c to 21c6ffa Compare April 1, 2026 16:21
zzylol and others added 2 commits April 1, 2026 11:40
…to_promql

- split_expr_by_stage now takes &QueryExpr instead of &SketchExpr.  All
  SketchExpr-era node assignments are preserved; new QueryExpr-only nodes
  (HistogramQuantile, PromQLSubquery, BinaryOp, Aggregate, Sort+Limit,
  Join, SetOp) are assigned to the correct stage.

- expr_to_promql replaced with a proper recursive descent over QueryExpr.
  Handles: histogram_quantile(φ, rate(…[w])), expr[range:step] subqueries,
  (lhs op rhs) vector binary ops with on(…)/ignoring(…)/group_left/group_right,
  Aggregate GROUP BY → by (keys), Sort+Limit → topk(n, …).

- Label-filter extraction ported from Predicate list to ScalarExpr AND-tree:
  col="val", col!="val", col=~"re", col!~"re".

- handle_plan unified to a single pipeline:
  parse_query_sketch → QueryExpr::from_sketch_expr → QueryOptimizer →
  split_expr_by_stage(&QueryExpr) → StagedPlan.
  SketchExpr path and analyze_with_sketch() removed from the hot path.

- StageResourceBudgets duplicate definition resolved (kept SP-9 variant).

- 12 new tests in stage_split covering all new node types, budget deferral,
  and expr_to_promql output.  Total: 303 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Merged algebra imports (QueryExpr + QueryOptimizer + SketchAllocator),
combined query_parser imports (parse_query_sketch for stage-split path,
parse_query_expr for plan_summary path), and kept wc_for_algebra from main.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@zzylol zzylol changed the title SP-9: AST-aware hierarchical stage assignment SP-9: AST-aware hierarchical stage assignment — QueryExpr stage split + recursive PromQL serialiser Apr 1, 2026
- Delete sketch_algebra.rs (721 lines) and sketch_rules.rs (683 lines)
- Move shared types (SourceSpec, PartitionKeys, ColumnRef, SketchAggOp,
  ExactAgg, Predicate, FilterOp, FilterVal) into algebra/expr.rs
- Parsers emit QueryExpr directly: parse_promql_expr / parse_sql_expr
  are now the only entry points; parse_query_sketch removed
- query_parser/mod.rs: QeCollector walks QueryExpr for ParsedQuery
- analyzer.rs: analyze_with_sketch() removed
- main.rs: uses parse_query_expr() directly (no from_sketch_expr bridge)
- All 264 tests pass; 39 SketchExpr-only tests removed with the IR

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@zzylol
zzylol merged commit fb00997 into main Apr 1, 2026
@zzylol
zzylol deleted the 90-sp-9-ast-aware-hierarchical-stage-assignment-edge-collector-leaf-nodes-precompute-engine-upper-ast branch April 1, 2026 18:35
SieDeta pushed a commit that referenced this pull request Apr 17, 2026
… + recursive PromQL serialiser (#94)

* feat: SP-9 AST-aware hierarchical stage assignment (edge collector → leaf nodes, precompute engine → upper AST)

Implements SP-9 as described in docs/controller-optimization-problem.md.
The SketchExpr tree produced by the query parser is now threaded through to
the planner and split across pipeline stages instead of being discarded.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(SP-9): Option B — port stage_split to QueryExpr, recursive expr_to_promql

- split_expr_by_stage now takes &QueryExpr instead of &SketchExpr.  All
  SketchExpr-era node assignments are preserved; new QueryExpr-only nodes
  (HistogramQuantile, PromQLSubquery, BinaryOp, Aggregate, Sort+Limit,
  Join, SetOp) are assigned to the correct stage.

- expr_to_promql replaced with a proper recursive descent over QueryExpr.
  Handles: histogram_quantile(φ, rate(…[w])), expr[range:step] subqueries,
  (lhs op rhs) vector binary ops with on(…)/ignoring(…)/group_left/group_right,
  Aggregate GROUP BY → by (keys), Sort+Limit → topk(n, …).

- Label-filter extraction ported from Predicate list to ScalarExpr AND-tree:
  col="val", col!="val", col=~"re", col!~"re".

- handle_plan unified to a single pipeline:
  parse_query_sketch → QueryExpr::from_sketch_expr → QueryOptimizer →
  split_expr_by_stage(&QueryExpr) → StagedPlan.
  SketchExpr path and analyze_with_sketch() removed from the hot path.

- StageResourceBudgets duplicate definition resolved (kept SP-9 variant).

- 12 new tests in stage_split covering all new node types, budget deferral,
  and expr_to_promql output.  Total: 303 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: delete SketchExpr — QueryExpr is the sole IR

- Delete sketch_algebra.rs (721 lines) and sketch_rules.rs (683 lines)
- Move shared types (SourceSpec, PartitionKeys, ColumnRef, SketchAggOp,
  ExactAgg, Predicate, FilterOp, FilterVal) into algebra/expr.rs
- Parsers emit QueryExpr directly: parse_promql_expr / parse_sql_expr
  are now the only entry points; parse_query_sketch removed
- query_parser/mod.rs: QeCollector walks QueryExpr for ParsedQuery
- analyzer.rs: analyze_with_sketch() removed
- main.rs: uses parse_query_expr() directly (no from_sketch_expr bridge)
- All 264 tests pass; 39 SketchExpr-only tests removed with the IR

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
zzylol added a commit that referenced this pull request May 7, 2026
…ath (#329)

Phase 3.3 (#94) worked around the bootstrap-vs-replan-push divergence
by adding a driver-side POST to /api/v1/plan after stack-up. This is
the deep fix: handle_bootstrap_agent_config now mirrors handle_plan's
typed L5 pipeline under USE_TYPED_STAGE_SPLIT=1 — bind_workload_typed
→ split_typed_three_stage → emit_for_runtime, dispatched by the
X-Agent-Runtime header (defaults to Sketchcollector for legacy
agents). Pinned-plan lookup via X-Agent-ID + the replanner's
agent_to_metric mapping avoids drift between bootstrap and the first
OpAMP push.

A fresh agent fetching its initial config now sees the Phase 3.2.5
gorillas3 archive emit + warm-passthrough routing processor and the
Phase ε.1.5 per-runtime dispatch without requiring the demo driver's
explicit POST. The driver POSTs stay as a safety belt (they still
exercise the typed-backend JSON push that the bootstrap GET doesn't
fire).

Backwards-compat: when USE_TYPED_STAGE_SPLIT is unset or the typed
path can't resolve a workload (empty registry, unsupported topology)
the handler falls back to the legacy generate_agent_config emit.
Bootstrap never returns a 500 just because the typed path hit a gap.

Tests:
- bootstrap_legacy_path_when_env_unset — env unset → legacy ddsketch:
- bootstrap_typed_path_when_env_set — env set + workload → typed
  ddsketchprocessor:
- bootstrap_typed_path_sketchotap_runtime_dispatch — X-Agent-Runtime:
  sketchotap → otel_dataflow/v1 DAG YAML
- bootstrap_typed_path_sketchtelegraf_runtime_dispatch →
  Telegraf TOML
- bootstrap_typed_path_falls_back_to_legacy_when_no_workload — typed
  gate on but empty registry → legacy fallback (no 500)

cargo test --release: 558 passed, 10 pre-existing failures unchanged.

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.

SP-9: AST-aware hierarchical stage assignment (edge collector → leaf nodes, precompute engine → upper AST)

1 participant