Skip to content

refactor(stage3): constraint (K) per execution and metric; sign-conditioned accuracy rules (#1207 PR 8) - #1238

Draft
zzylol wants to merge 1 commit into
refactor/1207-contextsfrom
refactor/1207-accuracy
Draft

zzylol wants to merge 1 commit into
refactor/1207-contextsfrom
refactor/1207-accuracy

Conversation

@zzylol

@zzylol zzylol commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #1237 (base refactor/1207-contexts). Part of #1207 (PR 8 of the restack plan). One commit, 8a376d382. Rebuilt on the restacked PR 7 (3b831f97f); the previous head 55a138469 is kept as backup/1207-accuracy-55a138469. Its design (read error on edges, (K) switched off by the model or evidence, the post-solve check, AccuracyCheckInputs::query_roots, string-encoded distinct bounds) was replaced by the 2026-11-08 accuracy redesign and is not carried over; PR 0–4 already landed the redesign's types (RequiredAccuracy, AccuracyEvidence::measured_guarantee, theoretical_guarantee, target_limit, MemoQuery { root, accuracy_target, values_per_evaluation }, readout requirement levels, ignore mode in the facade).

Why

After PR 7, Stage 3's constraint (K) was todo!(): no plan checked a query's composed accuracy at its root. A query whose readouts each met their own requirement could still miss its target (B = p99 / p50 at relative 0.03 read through two readouts at 0.02 each: 1.111 × 0.04 = 0.044), and a readout in the wrong metric could answer a query (PR 6/7 pins: DDSketch chosen for rank targets, HLL for a distinct count whose failure probability is unknown). The sign facts decided on 2026-11-08 (DDSketch's quantile guarantee; relative contracts of sum and avg) were not read yet.

What

  • Stage 3, constraint (K) (concepts doc §4 "Query composition and root checks"), per execution and per metric: $E_{\kappa,c,m}$, $D_{\kappa,c}$ with big-M rows on the selected edges, the metric rule on the selected path, and root checks per query with an accuracy target ($\delta_q$ per value from $n_q$). Only executions a targeted query reaches get rows; ignore mode (all targets None) gives none.
  • A readout of an approximate state without a requirement (no target reaches it in its metric) has no error bound: it may answer only queries without a target (Pending 1).
  • Sign conditions: α^log of a DDSketch quantile readout holds only when the state's values have one sign (column_sign NonNegative/NonPositive); sum and avg (and the exact sum accumulator) carry a relative error with β = 1 over values of one sign (E1(c), H2).
  • readout_metric no longer depends on the failure probability (HLL's distinct count reads a relative error; it was None, so HLL readouts were treated as exact).
  • Dedups: one per-value failure probability (AccuracyTarget::failure_probability_per_value, Pass 2 and Stage 3), one state-column sign (GroupProperties::state_column_sign, Stage 2's Count-Min rule and α^log), one target_limit for (K)'s limits.
  • Deleted: the string-encoded distinct-count guarantees of HLL and UnivMon (dedup 11; nothing reads them under the redesign, see Pending 3).

How

  1. ErrorModel::new (Stage 3, before any variable): per execution, whether a targeted query reaches its group, its own error (a readout's RequiredAccuracy), its contract per metric (LogicalRequirements::error_propagation), whether it is a readout without a requirement, and its selectable input edges.
  2. ErrorModel::set_ceilings: the big-M per execution and metric, $M_{c,m} = \varepsilon_c[m=m_c] + \sum_i \beta_{c,i,m}\max_u M_{u,m}$, $M^D_c = \delta_c + \sum_i \max_u M^D_u$, and "may be unbounded", in rounds until none grows (at most one round per execution: an acyclic selection, (E), has no longer path).
  3. accuracy_variables: $E_{\kappa,c,m}$ where $M_{c,m} > 0$, $D_{\kappa,c}$ where $M^D_c > 0$, $U_{\kappa,c} \in [0,1]$ where it may be unbounded (fixed 1 on such a readout).
  4. accuracy_constraints: $E_c \ge \varepsilon_c + \sum_i \beta F_{c,i}$, $F_{c,i} \ge E_u - M_u(1-y)$; $E_u \le M_u(1-y)$ into a consumer without a contract in that metric; $D_c \ge \delta_c + \sum_i H_{c,i}$, $H_{c,i} \ge D_u - M^D_u(1-y)$; $U_c \ge U_u - (1-y)$ unless the consumer reads that input with factor 0 in every metric; per query $q$ with a target and execution $v$ answering it, $E_{v,m} \le \mathrm{target_limit}(\varepsilon_q)[m\ \text{accepted}] + M(1-z)$, $D_v \le \mathrm{target_limit}(\delta_q/n_q) + M^D(1-z)$, $U_v \le 1-z$. A scalar root's operands accept no error (Pass 2 gives their readouts no requirement).
  5. solve_selection adds (K) to (A)–(F), the one-consumer rule and (R); with (K) rows, HiGHS's primal and MIP feasibility tolerances are 1e-10 (errors of 1e-3 and failure probabilities of 1e-7 are below the default 1e-7).
  6. Pass 2 / asap-accuracy: producer_satisfies_logical_requirements reads the state group's state_column_sign and applies sign_condition_holds to the theoretical guarantee; error_propagation gives sum/avg (and SummaryAgg { ExactAggregate(Sum) }) a relative contract when values_of_one_sign.

Key code interfaces added / modified

Permalinks at 8a376d382.

types/src/ir/properties/accuracy.rs

impl AccuracyTarget {
    /// The target's failure probability per value: δ split over the values its scope covers when one
    /// evaluation returns `values_per_evaluation` (n_q); 0 when not bound; None without a δ.
    /// Pass 2 (readout levels) and Stage 3 ((K) root rows) both read it.
    pub fn failure_probability_per_value(&self, values_per_evaluation: Option<u64>) -> Option<f64>; // new
}

types/src/stages/memo.rs

impl GroupProperties {
    /// The column_sign of a state group's state column (first non-plain field): the sign of the values
    /// its states were updated with; None without a state column. Stage 2's Count-Min rule and α^log read it.
    pub fn state_column_sign(&self) -> Option<ValueSign>; // new
}

types/src/stages/candidates.rs

impl LogicalRequirements {
    /// Signature unchanged; new contracts: an Aggregate's Sum/Avg and a SummaryAgg/chain of
    /// ExactAggregate(Sum) carry a relative error with β = 1 when the input column's column_sign is
    /// NonNegative or NonPositive (was: no contract).
    pub fn error_propagation(memo: &Memo, consumer: &Operator<MemoGroupId>, metric: AccuracyMetric)
        -> Option<ErrorPropagation>;
}

accuracy/src/model.rs

/// Signature unchanged; a readout's theoretical guarantee now also needs its sign condition.
pub fn producer_satisfies_logical_requirements(memo: &Memo, producer: CandidateForMemoGroupId,
    consumer: CandidateForMemoGroupId, input_index_of_consumer: usize, evidence: &dyn AccuracyEvidence)
    -> Result<(), RootPruneReason>;
/// Signature unchanged; the metric of the first listed θ's guarantee at any failure probability
/// (was: at δ = 1, so a guarantee with an unknown δ, HLL's, gave None).
pub fn readout_metric(summary_type: &FieldDataType, statistic: &SketchStatistic) -> Option<AccuracyMetric>;
/// Whether the theoretical guarantee holds for state values of sign `sign`: a DDSketch quantile needs one sign.
fn sign_condition_holds(summary_type: &FieldDataType, statistic: &SketchStatistic, sign: Option<ValueSign>) -> bool; // new, private

accuracy/src/estimators/hll.rs, univmon.rs

pub fn classic_relative_error(precision: u8, relative_error: Option<f64>) -> f64;                 // removed
pub fn classic_failure_statistic(precision: u8, relative_error: f64, source: &str) -> String;     // removed
pub fn parse_classic_failure_statistic(statistic: &str) -> Option<(u8, f64, &str)>;               // removed
pub(crate) fn classic_guarantee(precision: u8, relative_error: Option<f64>, source: &str)
    -> Option<AccuracyGuarantee>;                                                                 // removed
pub(crate) fn incomplete_top_layer_statistic(heap_size: u32, layers: u8, source: &str) -> String; // removed
pub fn parse_incomplete_top_layer_statistic(statistic: &str) -> Option<(u32, u8, &str)>;          // removed
pub(crate) fn distinct_guarantee(params: &SketchParams, delta: f64, source: &str)
    -> Option<AccuracyGuarantee>;                                                                 // removed
/// Kept (the formulas a distinct bound would bind, Pending 3):
pub fn classic_failure_probability(precision: u8, relative_error: f64, max_distinct: u64) -> f64; // unchanged
pub fn incomplete_top_layer_probability(heap_size: u32, layers: u8, max_distinct: u64) -> f64;    // unchanged

stage3-plan-selection/src/milp/model.rs (crate-private)

/// What (K) is built over, per execution (by position in ExecutionContexts::executions). // new
struct ErrorModel {
    /// Whether a query with an accuracy target reaches the execution's group; (K) has rows only for these. // new
    targeted: Vec<bool>,
    /// (ε_c, δ_c) in m_c: a readout's requirement, its own error; None for any other candidate. // new
    own_error: Vec<Option<RequiredAccuracy>>,
    /// Per metric of AccuracyMetric::ALL, error_propagation; None = no contract (the metric rule). // new
    contract: Vec<[Option<ErrorPropagation>; METRIC_COUNT]>,
    /// A readout of an approximate state without a requirement: its error has no bound. // new
    unbounded_readout: Vec<bool>,
    /// Selectable edges into each execution: (input, position in ExecutionContexts::edges, producer). // new
    inputs: Vec<Vec<(usize, usize, usize)>>,
    /// M per metric: the largest E_{c,m} any selection gives. // new
    error_bound_ceiling: Vec<[f64; METRIC_COUNT]>,
    /// The largest D_c any selection gives. // new
    failure_probability_ceiling: Vec<f64>,
    /// Whether some selection gives the execution an unbounded error. // new
    may_be_unbounded: Vec<bool>,
}
/// The accuracy variables of (K), all continuous, by execution position. // was by PhysicalCandidateId, #[allow(dead_code)]
struct AccuracyVariables {
    /// E_{κ,c,m}, by (execution, metric position). // was (PhysicalCandidateId, AccuracyMetric)
    error_bound: HashMap<(usize, usize), Variable>,
    /// D_{κ,c}. // was Vec<Variable> by candidate
    failure_probability: HashMap<usize, Variable>,
    /// U_{κ,c} ∈ [0, 1]: 1 when the output may carry an unbounded error (Pending 1). // new
    unbounded_error: HashMap<usize, Variable>,
    // removed: error_bound_of_input F and failure_probability_of_input H (added per (execution, input) in accuracy_constraints)
}
fn accuracy_variables(model: &ErrorModel, problem: &mut ProblemVariables) -> AccuracyVariables; // new
fn accuracy_constraints(inputs: &SelectionInputs<'_>, selection: &SelectionVariables, model: &ErrorModel,
    accuracy: &AccuracyVariables, problem: &mut ProblemVariables) -> Vec<Constraint>; // was (physical, selection, accuracy) + todo!()

Usage (integration test accuracy_at_query_roots::each_query_meets_its_target_at_its_root, passing):

// #1207's problem example over `latency` (1,000 series, 1 sample/s, ingested, values declared
// non-negative), both queries every 10 s; the deployment keeps no raw data.
let queries = [
    // A: rank error 0.01.
    ("quantile_over_time(0.95, latency[1h])", AccuracyTarget::max_error(0.01).accuracy_metric(AccuracyMetric::Rank)),
    // B: relative error 0.03 on a ratio of two quantiles.
    ("quantile_over_time(0.99, latency[1h]) / quantile_over_time(0.5, latency[1h])",
     AccuracyTarget::max_error(0.03).accuracy_metric(AccuracyMetric::Relative)),
];
// Stage 0 → Pass 1 → Pass 2: A's KLL readout gets the level (rank 0.01, δ 1); its DDSketch readout gets
// none (no rank target reaches a relative readout); B's DDSketch readouts get the levels 0.02 … 0.001
// (cap 0.027), each build connecting to the levels it meets (latency is non-negative: sign condition holds).
// Stage 2 → Stage 3: ErrorModel marks A's DDSketch readout unbounded; (K) gives B's root
// E_rel ≤ 0.03 + M(1 − z), so the readout pair (0.02, 0.02) (1.111 × 0.04 = 0.044) is infeasible.
let plan = plan(&queries, false);
// Asserted: a KLL with k ≥ 269 (A) and no smaller one; DDSketch builds whose smallest and largest alpha
// meet (α_min + α_max) / 0.9 ≤ 0.03 (B). Selected here: [DDSketch { alpha: 0.001 }, Kll { k: 269 }].
// With ignore_accuracy = true (test ignoring_accuracy_plans_without_k): [DDSketch { alpha: 0.001 }] only,
// one DDSketch answering A's p95 too.

Major algorithm changes

Constraint (K). Problem: PR 7 had no root check: on the example above with accuracy enforced, A's p95 could read a DDSketch readout (no rank requirement, treated as exact), and B could read two readouts at 0.02 each. Before: (K) todo!(); only per-edge α^log. After: How 1–5. Correctness: the rows are the doc's (K) per execution (§5) plus $U$; a selected path's error is at most $E$ at its root (each $F$ equals its chosen producer's $E$ since (A) selects one producer per input; $M$ bounds every unselected producer). Tests: k_allocates_a_ratio_s_error_among_its_readouts (the doc's allocation table: only the 0.01 + 0.01 pair meets 0.03), k_accepts_error_only_in_the_target_s_metric, a_readout_without_a_requirement_answers_only_queries_without_a_target, k_divides_a_whole_result_failure_probability_over_the_values (1e-3 per value meets 0.01 over 10 values, not over 100 or an unknown count), a_scalar_root_with_a_target_reads_its_operands_exactly; end to end the three accuracy_at_query_roots tests. Complexity: variables ≤ executions × (5 + 2) + inputs × 6; rows ≤ selectable edges × 7 + Σ|G_q| × 7; the ceilings take rounds × edges × 5 (one round per acyclic path step; on a cycle, up to one per execution). Only executions a targeted query reaches count; ignore mode adds nothing. Limits: a shared producer reached by two paths counts twice, and failure probabilities add by a union bound (both conservative, doc §4).

Sign conditions. Before: a DDSketch build met a quantile readout's requirement whatever the values' sign; sum/avg had no relative contract. After: How 6. Tests: Pass 2 ddsketch_quantiles_over_values_of_unknown_sign_have_no_guarantee, h2_avg_of_readouts_under_a_relative_target_needs_values_of_one_sign (was ..._reads_exact_only: unknown sign → exact only; declared non-negative → the DDSketch readout feeds the avg), types sums_and_averages_carry_a_relative_error_over_values_of_one_sign.

Deleted

Item Why
accuracy_constraints's todo!(), AccuracyVariables' #[allow(dead_code)] (K) implemented
hll::{classic_relative_error, classic_failure_statistic, parse_classic_failure_statistic, classic_guarantee}, univmon::{incomplete_top_layer_statistic, parse_incomplete_top_layer_statistic, distinct_guarantee} and their consts and round-trip tests dedup 11: a δ encoded as a string and parsed back; nothing reads them after the redesign (Pass 2 has no distinct bound; Pending 3)
Pass 2's inline per-value failure probability AccuracyTarget::failure_probability_per_value
Stage 2's inline state-column sign lookup GroupProperties::state_column_sign
Already gone in PR 0–7 (not re-done): AccuracyModel, satisfies, (K)-disable, post-solve check, SelectedPlanFailedCheck, read error on edges, θ floors, AggIntent accuracy, AccuracyCheckInputs::query_roots —

Pending approval (new items not in the doc)

  1. $U_{\kappa,c}$, the unbounded-error indicator (a (K) variable the doc does not have). Doc §2: a readout's input has no requirement "when no accuracy target reaches it" — in its metric. Such a readout is still reachable by a targeted query (a DDSketch p95 readout under A's rank target; HLL under a relative target before its δ is known), and with $E = 0$ (K) would accept it as exact. Rows: $U = 1$ on such a readout; $U_c \ge U_u - (1-y)$ on every edge whose input error reaches the output (not into a count, factor 0 in every metric); $U_v \le 1 - z_{q,v}$ for every $q$ with a target. Example: A = quantile_over_time(0.95, latency[1h]), rank 0.01; its result group has the exact aggregate and Projects over the KLL readout (level rank 0.01) and over the DDSketch readout (no level). $U$(DDSketch readout) = 1 forces $z$ = 0 on it; A reads KLL. Proposed doc line (§4, after the (K) block): "A readout without a requirement has no error bound: $U_r = 1$, $U_c \ge U_u - (1 - y_{u,c,i})$, and $U_v \le 1 - z_{q,v}$ for every q with a target." Alternative: Pass 2 gives a root group's off-metric readouts no edge (prune them) — does not cover readouts below value-preserving operators.
  2. AccuracyTarget::failure_probability_per_value(&self, Option<u64>) -> Option<f64> and GroupProperties::state_column_sign(&self) -> Option<ValueSign> (dedup helpers, definitions above). Examples: max_error(0.01).failure_probability(0.01) (whole result) with n_q = 1,000 → Some(1e-5), with n_q unknown → Some(0.0), without δ → None; a DDSketch state group over latency declared non-negative → Some(NonNegative), a scan → None.
  3. Distinct counts over a source's distinct bound. HLL's distinct-count guarantee has an unknown δ (its classic bound needs max_distinct), so no HLL readout meets a requirement and HLL is never selected under a target; UnivMon's distinct count has no guarantee. Binding the bound in Pass 2 needs it in the memo, e.g. Memo { declared_max_distinct: BTreeMap<String /* source */, u64>, .. } from SourceFacts::max_distinct_per_evaluation, read by α^log for a Cardinality readout over a state built directly on that source's rows. Example: COUNT(DISTINCT user_id) over http (max_distinct 1,000,000), relative 0.05 at δ 0.05: HLL p = 18 → classic_failure_probability(18, 0.05, 1e6) = 1 (out of the contract), so KMV is chosen either way; with max_distinct 9,636, p = 18 meets δ 0.01. Not done here (new interface).

Spec questions (code kept independent where possible)

  • K1 Scalar roots. Pass 2 passes no cap through a scalar root (PR 4), so their readouts have no requirement; (K) accepts no error at a scalar root's operands (exact only), which is what an unbounded readout implies. Alternative: Pass 2 caps scalar(v) / scalar subquery operands with the target (value-preserving).
  • K2 Edge metric rule only for targeted consumers. Like Pass 2's α^log (PR 4 Q2), (K)'s rows exist only for executions a targeted query reaches.
  • K3 Measured guarantees and the sign condition. The sign condition applies to theoretical_guarantee only; a deployment's measurement is taken as is.
  • Lower PRs, found here: (a) PR 4 Q1 (merges carry θ): the rollup tests (production_three_sql_groupings_share_kll, production_sql_distinct_region_service_rollup, rollup_controlled::production_distinct_rollup_finds_shared_kmv) now select the right family (KLL, KMV; were DDSketch, HLL) but no rollup; (b) PromQL per-series readouts through e2e_plan without series identity: renaming_project refuses an open schema, so Pass 1 adds the readout groups without connecting them to the result group (the e2e test therefore lowers with promql_rows::with_series_identity, as the other integration tests do).

Before this PR

No root check; readouts without a requirement and readouts in another metric could answer a targeted query; DDSketch quantile guarantees held for values of any sign; sum and avg carried no relative error.

After this PR

Every selected plan meets each target at its root per (K): composed error per metric, per-value failure probability, no unbounded readout. Ignore mode plans on cost alone (no rows).

Verification

cargo test per crate (--all-features --no-fail-fast), cargo fmt --check, cargo clippy --workspace --all-features --all-targets -D warnings clean, crate_graph passes. Wall-clock tests stay ignored.

Crate PR 7 (3b831f9) PR 8 (8a376d3)
asap-types 205/0/0 206/0/0
asap-accuracy 24/0/0 21/0/0
asap-stage1-pass2-reuse-candidates 21/0/0 22/0/0
asap-stage3-plan-selection 9/0/0 14/0/0
asap-planner 27/3/1 27/3/1
asap-devtools 37/56/3 37/56/3
asap-integration-tests 135/15/4 138/15/4
others unchanged unchanged
Total 1301/74/9 1308/74/9

By test name vs PR 7: 0 newly failing; 13 new tests pass (Stage 3: k_allocates_a_ratio_s_error_among_its_readouts, k_accepts_error_only_in_the_target_s_metric, a_readout_without_a_requirement_answers_only_queries_without_a_target, k_divides_a_whole_result_failure_probability_over_the_values, a_scalar_root_with_a_target_reads_its_operands_exactly; integration accuracy_at_query_roots::{each_query_meets_its_target_at_its_root, ignoring_accuracy_plans_without_k, a_whole_result_failure_probability_is_divided_over_the_values}; Pass 2 ddsketch_quantiles_over_values_of_unknown_sign_have_no_guarantee, h2_avg_of_readouts_under_a_relative_target_needs_values_of_one_sign (replaces ..._reads_exact_only); types sums_and_averages_carry_a_relative_error_over_values_of_one_sign; accuracy readout_metric_is_the_guarantee_s_metric, incomplete_top_layer_probability_needs_enough_layers (replaces the two distinct_guarantee_* tests)); deleted with the string-encoded guarantees: classic_failure_statistics_round_trip, classic_guarantee_binds_the_distinct_bound (its out-of-contract check moved to largest_certified_bounds_by_precision), incomplete_top_layer_statistics_round_trip. The 74 failures are PR 7's; of the accuracy pins, production_three_sql_groupings_share_kll now selects KLL (was DDSketch) and production_sql_distinct_region_service_rollup KMV (was HLL), both still failing on the missing rollup (PR 4 Q1).

Self-review: no shims or aliases; the composition rule lives once per role (ceilings in set_ceilings, rows in accuracy_constraints, both over ErrorModel::contract and edges_on); the per-value δ, state sign and target_limit have one definition each; the string-encoded distinct guarantees are deleted, the formulas they wrapped kept for Pending 3. Over-abstraction check: ErrorModel is the precomputation (K) needs once (contracts, ceilings); $F$/$H$ are created where used instead of stored. New beyond the doc: $U$ (Pending 1) and two helpers (Pending 2).

@zzylol zzylol added accuracy Accuracy requirements, guarantees, and propagation optimizer Workload optimization and plan selection refactor labels Nov 7, 2026
@zzylol
zzylol force-pushed the refactor/1207-contexts branch from 8a466a4 to 3b831f9 Compare November 9, 2026 09:35
…tioned DDSketch guarantee and sum/avg relative contracts (#1207 PR 8)
@zzylol
zzylol force-pushed the refactor/1207-accuracy branch from 55a1384 to 8a376d3 Compare November 9, 2026 10:09
@zzylol zzylol changed the title refactor(stage3): constraint (K), accuracy at each query root (#1207 PR 8) refactor(stage3): constraint (K) per execution and metric; sign-conditioned accuracy rules (#1207 PR 8) Nov 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

accuracy Accuracy requirements, guarantees, and propagation optimizer Workload optimization and plan selection refactor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant