Skip to content

refactor!: AccuracyTarget builders, AccuracyMetric naming, simpler Stage 3 interfaces and unread workload fields removed - #722

Merged
zzylol merged 5 commits into
mainfrom
refactor/accuracy-target-api
Oct 15, 2026
Merged

zzylol merged 5 commits into
mainfrom
refactor/accuracy-target-api

Conversation

@zzylol

@zzylol zzylol commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Why

Review of #690, #712 and #717 found several interfaces that are harder to read than they need to be, and several fields that no stage reads:

  • AccuracyTarget::epsilon_delta(ε, δ).with_metric(m) names its builders after Greek letters and a with_ prefix that the fields do not have. target_in and for_quantile are planner sizing helpers, but they sit on the public type, and every frontend calls them.
  • ErrorMetric and the metric / scope fields of a target do not say that they are about accuracy.
  • Budget splitting has two functions where one is enough (equal_shares, equal_share). whole_result_guarantee takes a fractional count of values. When resizing one candidate for its failure probability fails, the whole select_plan call fails.
  • TimeSelection (with QueryTimeScope) is set on every workload entry, but no stage reads it: the window comes from the query text. DataWorkload.ingestion_volume, DataWorkload.distribution and PropagationStats.data_distribution are also never read.

What

  • AccuracyTarget is built with exact() or max_error(ε), plus the optional builders .failure_probability(δ), .accuracy_metric(m) and .accuracy_scope(s). epsilon, epsilon_delta, with_metric, with_scope, target_in and for_quantile are removed.
  • ErrorMetric is renamed AccuracyMetric everywhere. On AccuracyTarget and AccuracySpec, the fields metric and scope become accuracy_metric and accuracy_scope; the JSON keys change with them.
  • The frontends store the query's target on each intent exactly as the user wrote it. Stage 1 restates it in the intent's own metric when it sizes a summary.
  • per_input_target replaces equal_shares and equal_share. The method AccuracyGuarantee::over_whole_result(values_per_result: Option<u64>) replaces whole_result_guarantee.
  • If a candidate's summaries cannot be rebuilt after Stage 3 resizes them for a failure probability, Stage 3 rejects that candidate with the reason. It no longer fails the whole selection, so SizingError and SelectionError::Sizing are gone. Selection.accepted holds one AcceptedAccuracy per query, with no Option.
  • Renames:
    • sized_roots → resize_for_failure_probability
    • summary_source → distinct_bound_source
    • hll::precision_for → hll::min_precision
  • Deleted because nothing outside tests reads them:
    • TimeSelection, QueryTimeScope, and the time_selection field of QueryWorkloadEntry, BatchEntry, RepeatingEntry and ComparisonScope
    • DataWorkload.ingestion_volume, DataWorkload.distribution and the DataDistribution type
    • PropagationStats.data_distribution
  • Stale comments from feat(planner)!: Stage 2 materialization, the latency check and simplified query requirements (Example 4) #717 are fixed: the Selection.accepted doc, the summary_sharing.rs module doc, "ImplicitExact", and "null without a target" in stages.js.

Out of scope: the Pass 2 *Candidate renames, the FamilyCapability changes and the cost model.

How

  • Targets. realization::accuracy_target(intent) now returns an owned target. It restates the intent's raw target in the intent's own metric through a private target_in helper:

    • an L1-scaled absolute error for Count and TopK;
    • a relative error for Cardinality and FrequencyL2;
    • an absolute error for FrequencyEntropy;
    • for Quantile, the set metric, or Rank when none is set.

    This is what the frontends used to stamp onto each intent. Every Stage 1 sizing and per-summary check already went through this accessor. Stage 3 still checks the query's own target at the root.

    An equality check that compares intents must restate both sides the same way. The legacy strictest_sibling_accuracy compared a restated target with a written one, and is fixed to compare both intents under the same target.

  • Budget split. per_input_target is the one-share case of the old equal_shares. Only the legacy core used shares > 1, for a nested sub-DAG that holds n − 1 of n layers, and that case cannot be written as a per-input split. It stays there as the private nested_layers_target.

  • Resize rejection. sized_for_failure_probability now returns the rejection reason as a String. select_plan records it as a Rejection { valid: false, .. } and goes on to the next candidate.

  • Deleted fields. For each deleted field, the only readers were tests, the ComparisonScope equality check, and the evidence copy into PropagationStats. The integration test that read time_selection.lookback now takes each window from the PATTERN_A / PATTERN_B_WINDOW_MS constants, which restate the query text. The empirical_cost distribution is a different type (DistributionDescriptor) and stays.

Key code interfaces added / modified

crates/types/src/types.rs

/// Which error quantity an accuracy statement bounds. The type stays; renamed.
pub enum AccuracyMetric {                            // was ErrorMetric
    /// `|estimate − truth| ≤ ε`, in value units or in multiples of a stream norm.
    Absolute { scale: Option<NormScale> },           // unchanged
    /// `|estimate − truth| ≤ ε · |truth|`.
    Relative,                                        // unchanged
    /// The returned value's rank is within `ε · n` of the requested rank.
    Rank,                                            // unchanged
}

/// The outer shape of a guarantee: `Pr[err > error] ≤ failure_probability` over
/// `accuracy_scope`. The type stays; two fields are renamed (JSON keys too).
pub struct AccuracySpec<E, P> {
    /// Which error quantity `error` bounds.
    pub accuracy_metric: AccuracyMetric,             // was metric
    /// ε, in `accuracy_metric`'s units.
    pub error: E,                                    // unchanged
    /// δ.
    pub failure_probability: P,                      // unchanged
    /// Which output values one statement covers at once.
    pub accuracy_scope: AccuracyScope,               // was scope
}

/// A query's accuracy requirement as the user states it. The type stays; two fields
/// are renamed (JSON keys `accuracy_metric`, `accuracy_scope`).
pub struct AccuracyTarget {
    /// The metric ε is stated in; None (default) accepts a guarantee in any metric.
    pub accuracy_metric: Option<AccuracyMetric>,     // was metric
    /// ε, the largest error allowed.
    pub error: f64,                                  // unchanged
    /// δ; None (default) imposes no failure-probability requirement.
    pub failure_probability: Option<f64>,            // unchanged
    /// Which values the target covers at once; default WholeResult.
    pub accuracy_scope: AccuracyScope,               // was scope
}

impl AccuracyTarget {
    /// No approximation permitted (ε = 0, δ = 0).
    pub const fn exact() -> Self;                                                // unchanged
    /// Error at most `error` over the whole result, no δ and no metric set.
    pub const fn max_error(error: f64) -> Self;                                  // new; was epsilon(ε)
    /// This target, met with probability at least `1 − failure_probability`.
    pub const fn failure_probability(self, failure_probability: f64) -> Self;   // new; epsilon_delta(ε, δ) is max_error(ε).failure_probability(δ)
    /// This target with ε stated in `accuracy_metric`.
    pub const fn accuracy_metric(self, accuracy_metric: AccuracyMetric) -> Self; // was with_metric
    /// This target over `accuracy_scope`.
    pub const fn accuracy_scope(self, accuracy_scope: AccuracyScope) -> Self;    // was with_scope
    /// Whether this target permits no approximation.
    pub fn is_exact(&self) -> bool;                                              // unchanged
    pub const fn epsilon(epsilon: f64) -> Self;                                  // removed
    pub const fn epsilon_delta(epsilon: f64, delta: f64) -> Self;                // removed
    pub fn target_in(&self, metric: ErrorMetric) -> Self;                        // removed: now private in realization.rs
    pub fn for_quantile(&self) -> Self;                                          // removed: folded into accuracy_target
}

crates/types/src/post_asap/guarantee.rs

/// The statistic an unbound number of values per result is left as; nothing binds it.
pub const VALUES_PER_RESULT: &str = "values_per_result";   // new; was accuracy::OUTPUT_VALUES = "output_values_per_evaluation"

impl AccuracyGuarantee {
    /// This guarantee over one evaluation's whole result of `values_per_result` values
    /// (`G`): δ becomes `G · δ` by the union bound; None leaves δ symbolic in
    /// VALUES_PER_RESULT. Stage 3 uses it to check a whole-result target.
    pub fn over_whole_result(&self, values_per_result: Option<u64>) -> Self;   // new; was accuracy::whole_result_guarantee(g, output_values: Option<f64>)
}

crates/asap-aware-mapping/src/accuracy/budget.rs

/// The target of one of `approximate_inputs` approximate inputs that compose into a
/// value with `target`, split equally in `metric`'s composition; None for an exact or
/// non-positive target. Stage 1 sizes a combination's summaries with it.
pub fn per_input_target(
    target: &AccuracyTarget,
    metric: AccuracyMetric,
    approximate_inputs: usize,
) -> Option<AccuracyTarget>;                                                   // new
pub fn equal_shares(target: &AccuracyTarget, metric: ErrorMetric, of: usize, shares: usize) -> Option<AccuracyTarget>; // removed: legacy core keeps a private nested_layers_target
pub fn equal_share(target: &AccuracyTarget, metric: ErrorMetric, approximate_inputs: usize) -> Option<AccuracyTarget>; // removed: now per_input_target

crates/asap-aware-mapping/src/realization.rs

/// The target an approximate-capable intent's summary is sized and checked against:
/// the query's target restated in the intent's own metric; None for other intents.
pub fn accuracy_target(intent: &AggIntent) -> Option<AccuracyTarget>;   // was -> Option<&AccuracyTarget>, the intent's stamped target

crates/asap-aware-mapping/src/stage3_plan_selection.rs

/// Stage 3's result. The type stays; `accepted` loses its Option.
pub struct Selection {
    /// The id of the cheapest valid candidate.
    pub selected: String,                                        // unchanged
    /// The selected candidate's roots with confirmed weights: the plan to run.
    pub plan: Vec<QueryRoot>,                                    // unchanged
    /// Per valid candidate, its cost per second.
    pub costs: BTreeMap<String, CandidateCost>,                  // unchanged
    /// Per valid candidate, per query, the accuracy Stage 3 accepted.
    pub accepted: BTreeMap<String, Vec<AcceptedAccuracy>>,       // was Vec<Option<AcceptedAccuracy>>
    /// Every candidate not selected, with why.
    pub rejected: Vec<Rejection>,                                // unchanged
}

/// What Stage 3 accepted for one query of a valid candidate. The type stays; its
/// metric fields change type with the AccuracyMetric rename.
pub struct AcceptedAccuracy {
    /// The target's metric; None when the user did not set one.
    pub target_metric: Option<AccuracyMetric>,       // was Option<ErrorMetric>
    /// The metric of the guarantee that met the target; None for an exact output.
    pub accepted_metric: Option<AccuracyMetric>,     // was Option<ErrorMetric>
    /// The sketch algorithms the query's output reads, in plan order; empty if exact.
    pub sketches: Vec<String>,                       // unchanged
}

/// Why select_plan failed. The type stays; one variant is removed.
pub enum SelectionError {
    /// Bad input: the number of workload entries differs from a candidate's roots.
    EntryCount { candidate: String, roots: usize, entries: usize },                // unchanged (now tested)
    /// Internal bug or bad calibration: a node of a candidate could not be priced.
    Cost { candidate: String, node: PhysicalASAPNodeId, error: AnalyticalCostError }, // unchanged
    /// Unsatisfiable request: every candidate was rejected.
    NoValidCandidate(Vec<Rejection>),                                              // unchanged
    Sizing { candidate: String, error: SizingError },                              // removed: the candidate is rejected instead
}

pub enum SizingError { Structure(SchemaDerivationError), Export(ExecutionDataStateError) }   // removed

/// `roots` with each summary enlarged for its queries' failure probabilities over
/// their values per result; the plan Stage 3 prices and runs.
pub fn resize_for_failure_probability(
    roots: &[QueryRoot],
    targets: &[AccuracyTarget],
    data: &DataWorkload,
) -> Result<Vec<QueryRoot>, SchemaDerivationError>;                            // was sized_roots

crates/asap-aware-mapping/src/accuracy/estimators/hll.rs, crates/asap-aware-mapping/src/per_computation_candidates.rs

/// The smallest classic-HLL precision, at least `at_least`, whose failure probability
/// at `relative_error` over at most `max_distinct` items is at most `failure_probability`.
pub(crate) fn min_precision(
    relative_error: f64,
    failure_probability: f64,
    max_distinct: u64,
    at_least: u8,
) -> u8;                                                                        // was precision_for(precision, relative_error, max_distinct, delta)
/// The one source whose distinct bound gives a summary state's failure probability.
pub(crate) fn distinct_bound_source(state: &OperatorNode) -> Option<String>;  // was summary_source

crates/types/src/workload.rs

/// One-shot query entry of a batch. The type stays; one field is removed.
pub struct BatchEntry {
    /// The query text; its window (range, offset) is in the text.
    pub query: Query,                          // unchanged
    /// Accuracy target and latency bound.
    pub requirements: QueryRequirements,       // unchanged
    /// How far ahead the query is known.
    pub predictability: Predictability,        // unchanged
    /// How many times the batch runs it.
    pub invocations: u64,                      // unchanged
    /// Known execution time, if any.
    pub execute_at: Option<TimestampMs>,       // unchanged
    pub time_selection: TimeSelection,         // removed
}

/// One query with repeated demand. The type stays; one field is removed.
pub struct RepeatingEntry {
    /// The query text; its window is in the text.
    pub query: Query,                          // unchanged
    /// How often it repeats.
    pub demand: RepeatedDemand,                // unchanged
    /// Accuracy target and latency bound.
    pub requirements: QueryRequirements,       // unchanged
    /// How far ahead the query is known.
    pub predictability: Predictability,        // unchanged
    pub time_selection: TimeSelection,         // removed
}

/// The planner's normalized form of either entry. The type stays; one field is removed.
pub struct QueryWorkloadEntry {
    /// The query text in the workload's language.
    pub query: Query,                          // unchanged
    /// Its accuracy target and latency bound (default: exact, no bound).
    pub requirements: QueryRequirements,       // unchanged
    /// How far ahead it is known (default: unknown).
    pub predictability: Predictability,        // unchanged
    /// How often it runs (default: unknown).
    pub recurrence: QueryRecurrence,           // unchanged
    pub time_selection: TimeSelection,         // removed
}

/// Facts about the data being queried. The type stays; two fields are removed.
pub struct DataWorkload {
    /// At rest, continuously ingesting, or both.
    pub arrival: DataArrival,                                            // unchanged
    /// Cadence at which each PromQL series supplies a sample.
    pub data_ingestion_interval: Evidence<DurationMs>,                   // unchanged
    /// Samples arriving per second across all series.
    pub ingestion_rate: Evidence<Rate>,                                  // unchanged
    /// Number of input series (or keys).
    pub input_cardinality: Evidence<u64>,                                // unchanged
    /// Per-source stream norms that scale Count-Min / Count Sketch errors.
    pub stream_norms: Evidence<BTreeMap<String, StreamNorms>>,           // unchanged
    /// Per-source bound on distinct items per evaluation (classic HLL δ).
    pub max_distinct_per_evaluation: Evidence<BTreeMap<String, u64>>,    // unchanged
    /// Declared Prometheus type per metric.
    pub metric_types: BTreeMap<String, MetricType>,                      // unchanged
    pub ingestion_volume: Evidence<u64>,                                 // removed: never read
    pub distribution: Evidence<DataDistribution>,                        // removed: only copied to an unread stat
}

pub struct TimeSelection { scope: QueryTimeScope, lookback: Option<DurationMs>, as_of: Option<TimestampMs> } // removed
pub enum QueryTimeScope { RealTime, Longitudinal, Mixed, Unknown }                                           // removed
pub enum DataDistribution { Zipf, Uniform, Bursty }                                                          // removed

crates/asap-aware-mapping/src/accuracy/evidence.rs

/// Statistics a propagation rule may consult; every field defaults to unknown.
/// The type stays; one field is removed.
pub struct PropagationStats {
    /// Certified ranges of a ratio's true numerator and denominator.
    pub division_operand_domains: Option<[OperandDomain; 2]>,          // unchanged
    /// Provenance for supplied evidence.
    pub evidence_provenance: Vec<GuaranteeSource>,                       // unchanged
    /// Whether every input value is known non-negative.
    pub values_non_negative: Option<bool>,                               // unchanged
    /// The statistic an unknown sign is named by.
    pub sign_statistic: Option<String>,                                  // unchanged
    /// Input rows an exact aggregation consumes.
    pub input_row_count: Option<u64>,                                    // unchanged
    /// The statistic an unknown row count is named by.
    pub row_count_statistic: Option<&'static str>,                       // unchanged
    /// Lower confidence bound of the kth selected TopK item.
    pub topk_selected_lower_bound: Option<f64>,                          // unchanged
    /// Greatest upper confidence bound among excluded TopK items.
    pub topk_excluded_upper_bound: Option<f64>,                          // unchanged
    /// Union-bound δ of the TopK margin intervals.
    pub topk_interval_failure_probability: Option<f64>,                  // unchanged
    /// Hydra shared-grid collision error.
    pub hydra_shared_grid_collision_bound: Option<f64>,                  // unchanged
    /// δ assigned to the Hydra shared-grid term.
    pub hydra_shared_grid_failure_probability: Option<f64>,              // unchanged
    pub data_distribution: Option<DataDistribution>,                     // removed: no rule read it
}

crates/asap-aware-mapping/src/physical_operator_statistics.rs

/// The boundary within which two resource estimates may be compared. The type stays;
/// one field is removed.
pub struct ComparisonScope {
    /// Whether the data is at rest or arriving.
    pub data_arrival: DataArrival,           // unchanged
    /// The planning instant.
    pub planning_time: TimestampMs,          // unchanged
    /// The finite horizon costs are summed over.
    pub horizon: DurationMs,                 // unchanged
    /// How often the query runs.
    pub recurrence: QueryRecurrence,         // unchanged
    /// The exact source selections covered.
    pub sources: Vec<ScanSelection>,         // unchanged
    pub time_selection: TimeSelection,       // removed (and its equality check)
}

The JSON and viewer formats change in the same way:

  • AccuracyTarget and AccuracySpec use the keys "accuracy_metric" and "accuracy_scope" (previously "metric" and "scope").
  • Workload entries no longer take "time_selection". DataWorkload no longer takes "ingestion_volume" or "distribution".
  • In the stage_pipeline output, stage3_plan_selection.accepted[id] is never null.
  • tools/dag-viewer/stages.js reads accuracy_metric.

Usage, end to end. One PromQL p99 panel with a rank-error target over its whole result, planned with run_planning_stages at this head. The numbers come from running this code as a test.

// 1. What the user writes: one dashboard panel, the p99 latency of every series over
//    the last 5 minutes, refreshed every 10 s. `requirements` is its target in the
//    workload JSON with the renamed keys: a rank error of at most 0.01, failing with
//    probability at most 0.01 over the whole returned result (the default
//    `accuracy_scope`). Stage 3 checks the query's output against it.
let requirements: QueryRequirements = serde_json::from_str(
    r#"{"accuracy": {"accuracy_metric": "rank", "error": 0.01, "failure_probability": 0.01}}"#,
)?;
// `target`: the same target through the builders, compared with the parsed one.
let target = AccuracyTarget::max_error(0.01)
    .failure_probability(0.01)
    .accuracy_metric(AccuracyMetric::Rank);
assert_eq!(requirements.accuracy, target);
assert_eq!(target.accuracy_scope, AccuracyScope::WholeResult);
// `panel`: the repeating entry. The 5-minute window is in the query text; there is no
// `time_selection` field any more.
let panel = RepeatingEntry {
    query: Query("quantile_over_time(0.99, latency_ms[5m])".into()),
    demand: RepeatedDemand::FixedInterval(RepetitionInterval(10_000)),
    requirements,
    predictability: Predictability::Predictable { known_at: None },
};
// 2. `data`: what the deployment declares. 10,000 series bound the values one
//    evaluation returns (G = 10,000); a sample per series every 15 s.
let data = DataWorkload {
    arrival: DataArrival::ContinuouslyIngesting,
    data_ingestion_interval: declared(DurationMs(15_000)),
    ingestion_rate: declared(Rate(10_000.0 / 15.0)),
    input_cardinality: declared(10_000),
    ..Default::default()
};
// `workload`: the planner input, the query workload plus the data facts.
let workload = PlanningWorkload {
    query_workload: QueryWorkload {
        language: QueryLanguage::PromQL,
        query_batch: None,
        repeating_queries: Some(vec![panel]),
    },
    data_workload: Some(data),
};
// 3. Stage 0 (query-language frontends): `roots` holds one root per entry. The
//    quantile intent keeps `target` as written; frontends no longer call target_in.
let roots = lower_promql_roots(&workload)?;
// 4. Stages 1-3. `run` holds every stage's candidates and the selection.
//    Stage 1 sizes the KLL candidate through `accuracy_target(intent)`, which restates
//    the target in the quantile's metric (Rank, set here): `Kll { k: 269 }`.
//    Stage 3 calls `resize_for_failure_probability`. The target covers the whole
//    result of G = 10,000 values, so each per-series KLL must fail with probability at
//    most 0.01 / 10,000 = 1e-6: `Kll { k: 521, failure_probability: Some(1e-6) }`.
//    It then checks `guarantee.over_whole_result(Some(10_000))`, whose δ is
//    10,000 · 1e-6 = 0.01 ≤ 0.01.
let run = run_planning_stages(&workload, roots, None)?;
// `selection`: Stage 3's result, read below.
let selection = run.stage3_plan_selection?;
// 5. What the user gets back.
//    The selected plan is P2, the KLL plan, at 0.126 cost/s; it runs the resized
//    `Kll { k: 521, failure_probability: Some(1e-6) }`.
assert_eq!(selection.selected, "P2");
//    `accepted` has one entry per query, with no Option:
//    P1 "metric rank; accepted exact", P2 "metric rank; accepted rank (Kll)".
assert_eq!(selection.accepted["P2"][0].to_string(), "metric rank; accepted rank (Kll)");
//    `rejected`: the exact plan P1 is valid but "costlier: 0.144 vs 0.126
//    cost_per_second". The DDSketch plans P4 and P5 are invalid: "the output
//    guarantees a Relative bound Some(0.01) ... which misses target AccuracyTarget
//    { accuracy_metric: Some(Rank), ... }", because a set metric accepts only that
//    metric. A candidate whose resized summaries did not rebuild would show up here
//    as "resizing for the failure probability failed: ..." instead of failing the call.

Before this PR

  • A caller wrote AccuracyTarget::epsilon_delta(0.01, 0.01).with_metric(ErrorMetric::Rank), and the JSON was {"metric": "rank", ...}.
  • Each frontend called target_in or for_quantile on every intent.
  • One candidate failing to rebuild after the failure-probability resize made select_plan return Err(SelectionError::Sizing), which hid every other candidate.
  • Every workload entry had to carry a time_selection that no stage read.

After this PR

  • A caller writes AccuracyTarget::max_error(0.01).failure_probability(0.01).accuracy_metric(AccuracyMetric::Rank), and the JSON is {"accuracy_metric": "rank", ...}.
  • Intents keep the user's target as written, and sizing restates it in one place.
  • A candidate that fails to rebuild after resizing is one rejected line, and selection still returns a plan.
  • Workload entries and data workloads carry only fields that some stage reads.

Verification

  • cargo nextest run --workspace after rebasing onto c127c62c: 1855 passed, 35 skipped.
  • cargo test --doc -p asap-types -p asap-aware-mapping: passed.
  • cargo fmt --all -- --check and cargo clippy --workspace --all-targets --all-features --locked -- -D warnings: clean.
  • tools/verify_metricsql_external_consumer.sh: passed.
  • python3 -m unittest test_viewer test_server in tools/dag-viewer: 50 tests OK. The unset-metric assertion in test_server.py now checks accuracy_metric, so it is no longer vacuous.
  • New tests:
    • a_candidate_that_fails_to_resize_is_rejected_alone. Written first, it failed on the previous commit with Sizing { candidate: "P2", error: Export(UntimedNode { operator: "Project" }) } and passes now.
    • an_entry_count_mismatch_is_an_error.
    • identical_count_and_distinct_siblings_share_the_strictest_target. This is a regression found in review: two count by (job) siblings (and two distinct-count siblings) at ε 0.05 and 0.01, with no metric set, must size the looser one for 0.01. Written first, it failed on the reviewed head (None instead of Some(max_error(0.01))) and passes after the fix. I found no other equality check that mixes a written target with a restated one: shared_quantile_target compares written targets on both sides, and reconciliation compares restated targets on both sides.
    • a_nested_sub_dag_gets_its_layers_share_of_the_target, which keeps the coverage of the 2-of-3 split that equal_shares had.
  • tools/dag-viewer/examples/planner-layering-example1.json was regenerated. Its diff is only the key renames, plus intents that no longer carry the frontend-stamped "accuracy_metric": "absolute_stream_l1".
  • The Usage numbers come from running that code as a temporary test at this head. The test is not committed.

Limitations and follow-ups

  • Behavior change. PromQL used to stamp an absolute metric on FrequencyL2 intents, while SQL and the root-metric table used a relative one. The single restating helper now uses relative for both. No test depended on the absolute version.
  • Docs.
    • The design doc planner-layering.md still uses lookback and as_of as columns in its example tables, and it still lists "volume" and "distribution" among the data-workload facts in its overview table and diagram. I only removed time_selection from the stage table, said that lookback and as_of are stated by the query text (its range and offset) rather than workload.rs fields, replaced as_of: None with "no offset", and dropped the ingestion_volume and distribution rows from the shared-data-workload table. A fuller rewrite belongs with the docs work in Update the docs after the G1–G11 consolidation groups land #677.
    • The historical proposal end-to-end-accuracy-guarantees.md shows a ResultGuarantee { metric: AccuracyMetric, .. }. Only the type name was renamed there.
  • Existing names this PR touches that still fall short:
    • AccuracyTarget.error would read better as max_error, to match its constructor.
    • Rejection.valid reads as a property of the rejection; passed_checks would say what it means.
    • realization::accuracy_target returns the restated target, so sizing_target would say that.
    • The stage_pipeline and viewer request flags --epsilon / --metric (and the "metric" key of the viewer's /api/plan request) still use the old words.
    • The PATTERN_A tuple (query, window, offset) has unnamed fields.
  • PropagationStats.topk_* are set only by tests and by deployment evidence providers, but a rule reads them, so they stay.
  • The rest of the DataWorkload reshape (a sources map, SourceFacts) is left for a later PR.

- AccuracyTarget is built with exact() or max_error(ε) and the optional
  builders failure_probability(δ), accuracy_metric(m) and accuracy_scope(s);
  epsilon, epsilon_delta, with_metric and with_scope are removed.
- ErrorMetric is renamed AccuracyMetric, and the metric and scope fields of
  AccuracyTarget and AccuracySpec are renamed accuracy_metric and
  accuracy_scope (JSON keys too).
- target_in and for_quantile leave the public type. Frontends keep the
  query's target on each intent as written; asap-aware-mapping's
  accuracy_target restates it in the intent's own metric for sizing.
…erfaces

- equal_shares and equal_share become one per_input_target(target,
  metric, approximate_inputs); the legacy core keeps its nested-layer
  split as a private helper.
- whole_result_guarantee(g, output_values: Option<f64>) becomes
  AccuracyGuarantee::over_whole_result(values_per_result: Option<u64>),
  and OUTPUT_VALUES becomes VALUES_PER_RESULT.
- A candidate whose summaries fail to rebuild after resizing for the
  failure probability is rejected with the reason instead of failing
  select_plan; SizingError and SelectionError::Sizing are removed.
- Selection.accepted holds one AcceptedAccuracy per query (no Option).
- Renames: sized_roots -> resize_for_failure_probability, summary_source
  -> distinct_bound_source, hll::precision_for -> hll::min_precision.
- Tests: the resize rejection (failing first) and the EntryCount error.
- TimeSelection, QueryTimeScope and the time_selection field of
  QueryWorkloadEntry, BatchEntry and RepeatingEntry: the window a query
  reads is in its text. ComparisonScope loses the copy it compared.
- DataWorkload.ingestion_volume and DataWorkload.distribution, the
  DataDistribution type, and PropagationStats.data_distribution, which
  only carried it to no reader.
strictest_sibling_accuracy compared a sibling's written target with a
restated one, so identical count and distinct-count siblings never
matched and the strictest-sibling candidate was never offered. Compare
both intents with the same target. Also fix a vacuous viewer assertion
and the stale lookback/as_of field claim in planner-layering.md.
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 workload Batch, repeating, or multi-query workload modeling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant