diff --git a/crates/accuracy/src/estimators/hll.rs b/crates/accuracy/src/estimators/hll.rs index 864475aba..18d45db02 100644 --- a/crates/accuracy/src/estimators/hll.rs +++ b/crates/accuracy/src/estimators/hll.rs @@ -2,7 +2,7 @@ //! //! This is conditional on independent uniform bucket hashes and an enforced //! upper bound on distinct items in the complete evaluation population (including -//! all merged panes). It is not an RSE-to-normal conversion or an ERP fit. +//! all merged tumbling windows). It is not an RSE-to-normal conversion or an ERP fit. use super::*; @@ -106,7 +106,7 @@ pub(crate) fn classic_guarantee( "max_distinct": format!("{MAX_DISTINCT}[{source}]"), "relative_error": relative_error, "hash_assumption": "independent_uniform_buckets", - "population_scope": "complete_evaluation_including_merged_panes"}), + "population_scope": "complete_evaluation_including_merged_tumbling_windows"}), query: "Cardinality".into(), }], )) @@ -170,7 +170,7 @@ impl ClassicHllConfidence { params: serde_json::json!({"precision": precision, "max_distinct": self.max_distinct, "relative_error": self.relative_error, "hash_assumption": "independent_uniform_buckets", - "population_scope": "complete_evaluation_including_merged_panes"}), + "population_scope": "complete_evaluation_including_merged_tumbling_windows"}), query: "Cardinality".into(), }], )) diff --git a/crates/accuracy/src/estimators/univmon.rs b/crates/accuracy/src/estimators/univmon.rs index be109b3c0..c9378c1d0 100644 --- a/crates/accuracy/src/estimators/univmon.rs +++ b/crates/accuracy/src/estimators/univmon.rs @@ -300,7 +300,7 @@ pub(crate) fn distinct_guarantee(params: &SketchParams, source: &str) -> Option< "variance": "1.35 F0^2 / k", "hash_assumption": "independent_uniform_layer_hashes", "mode": "standard", - "population_scope": "complete_evaluation_including_merged_panes"}), + "population_scope": "complete_evaluation_including_merged_tumbling_windows"}), query: "Cardinality".into(), }], )) diff --git a/crates/accuracy/src/propagation.rs b/crates/accuracy/src/propagation.rs index 9ab658229..ed33978df 100644 --- a/crates/accuracy/src/propagation.rs +++ b/crates/accuracy/src/propagation.rs @@ -7,7 +7,7 @@ use std::collections::{HashMap, HashSet}; use std::rc::Rc; use asap_types::ir::operator::operator_properties::{BinaryOpKind, JoinKind, Reduction, Source}; -use asap_types::ir::operator::pane_chain::{merged_chain, merged_window_build}; +use asap_types::ir::operator::tumbling_window_chain::{merged_chain, merged_window_build}; use asap_types::ir::operator::AggIntent; use asap_types::ir::properties::guarantee::{ AccuracyGuarantee, AccuracyMetric, BoundExpr, CompositionOperator, GuaranteeSource, @@ -406,7 +406,7 @@ pub fn composed_guarantee(node: &OperatorNode) -> Option { if let Some(guarantee) = rolled_up_guarantee(node) { return guarantee; } - if let Some(guarantee) = merged_panes_guarantee(node) { + if let Some(guarantee) = merged_tumbling_windows_guarantee(node) { return guarantee; } let exact = |node: &OperatorNode| { @@ -610,14 +610,14 @@ fn rolled_up_guarantee(node: &OperatorNode) -> Option> } /// The guarantee of an estimate or finalized value read from merged -/// tumbling panes ([`merged_chain`]): the guarantee of the same read of one -/// build over the merged panes' window ([`merged_window_build`]: the whole -/// chain's, or its newest panes') when the +/// tumbling windows ([`merged_chain`]): the guarantee of the same read of one +/// build over the merged tumbling windows' time range ([`merged_window_build`]: the whole +/// chain's, or its newest tumbling windows') when the /// family's merged error for that read is exact or the same as that build's /// (the family-capability table); none when it is approximate (evidence /// only, for plan selection to judge) or the family has no merge. `None` for -/// any node that does not read merged panes. -fn merged_panes_guarantee(node: &OperatorNode) -> Option> { +/// any node that does not read merged tumbling windows. +fn merged_tumbling_windows_guarantee(node: &OperatorNode) -> Option> { let (state, merged) = match node.asap()? { ASAPOp::SummaryEstimate { summary_input, @@ -647,7 +647,7 @@ fn merged_panes_guarantee(node: &OperatorNode) -> Option Option { @@ -771,9 +771,9 @@ pub(crate) fn estimates_raw_data_samples_of(node: &OperatorNode) -> Option None, }, // Only a φ in [0, 1] picks one of the values; outside it the answer - // is -Inf or +Inf, whatever their sign. Merged tumbling panes of one + // is -Inf or +Inf, whatever their sign. Merged tumbling windows of one // definition ([`merged_chain`]) are read through their `SummaryTumblingWindowChain`: - // a quantile of the merged state is still one of the panes' + // a quantile of the merged state is still one of the tumbling windows' // samples, or between two of them. Operator::ASAP(ASAPOp::SummaryEstimate { summary_input, diff --git a/crates/cost/src/execution_demand.rs b/crates/cost/src/execution_demand.rs index 252a53811..6a7e380bd 100644 --- a/crates/cost/src/execution_demand.rs +++ b/crates/cost/src/execution_demand.rs @@ -338,7 +338,7 @@ pub fn consumers(dag: &PhysicalASAPDAG) -> HashMap = (0..10) .map(|i| (1.0 - (f64::from(i) + 0.5) / 10.0).powf(-0.5)) .collect(); @@ -472,7 +472,7 @@ mod tests { /// samples it sees, not its full capacity: its fixed struct and level /// array, plus the item array the library starts empty and doubles from /// 16 items as samples arrive, up to `compute_max_capacity(k, 8)`. E.g. - /// a 10 s pane of a series sampled every 15 s sees one sample: 744 + 128 + /// a 10 s tumbling window of a series sampled every 15 s sees one sample: 744 + 128 /// = 872 bytes, where the full capacity at `k` = 269 is 1,206 items. #[test] fn kll_state_bytes_grow_with_the_samples_it_sees() { diff --git a/crates/cost/src/usage.rs b/crates/cost/src/usage.rs index 3ad96b178..fe4ee9f11 100644 --- a/crates/cost/src/usage.rs +++ b/crates/cost/src/usage.rs @@ -1,5 +1,5 @@ //! Costing a physical plan DAG node by node: each node's resource usage -//! ([`node_usage`]), then the plan's usage, latency, retention, kept pane +//! ([`node_usage`]), then the plan's usage, latency, retention, kept tumbling window //! chains and cost ([`plan_cost`]). Moved from Stage 3 (`asap-plan-selection`, //! whose module doc, "Cost model", describes the model), so Stage 3 computes //! no cost of its own. @@ -45,9 +45,9 @@ use asap_types::workload::bounds::{ /// time ranges keep, times how often it is computed per second /// ([`computations`]); an ingestion-time node over one second of arriving /// rows. A materialized node's kept state is counted in byte-seconds per -/// second; a pane chain kept beyond one evaluation keeps the panes its +/// second; a tumbling-window chain kept beyond one evaluation keeps the tumbling windows its /// retention covers ([`tumbling_window_retention_ms`]). `nodes` and `kept_chains` are -/// `dag`'s ([`node_map`], [`kept_pane_chains`]); `recurrence[q]` and +/// `dag`'s ([`node_map`], [`kept_tumbling_window_chains`]); `recurrence[q]` and /// `range_evaluations[q]` say how often query `q` runs and how many steps /// each run evaluates (one when missing). #[allow(clippy::too_many_arguments)] @@ -440,7 +440,7 @@ fn each_node_usage<'a>( &views, multipliers[&node.id], scan_window, - retained_panes(node, &retention_ms), + retained_tumbling_windows(node, &retention_ms), (node.output_state.timing == ExecutionTiming::IngestionTime) .then(|| maintained_window_ms(node.id, nodes, &producers_of)) .flatten(), @@ -545,8 +545,8 @@ pub struct NodeUsage<'a> { /// `rate` its `(multiplier, per_second)` ([`computations`]), `scan_window` /// the raw rows a query-time scan reads ([`PhysicalASAPDAG::scan_window`]; /// read only for scans: it reads `width_ms` of rows and keeps them back to -/// [`ScanWindow::lookback_ms`]), `retained_pane_count` the panes a pane chain keeps -/// completed (`retained_panes`), and `ingestion_window_ms` the window an +/// [`ScanWindow::lookback_ms`]), `retained_tumbling_window_count` the tumbling windows a tumbling-window chain keeps +/// completed (`retained_tumbling_windows`), and `ingestion_window_ms` the window an /// ingestion-time node maintains (`maintained_window_ms`). #[allow(clippy::too_many_arguments)] pub fn node_usage<'a>( @@ -554,7 +554,7 @@ pub fn node_usage<'a>( producers: &[ProducerOutput<'a>], rate: (u64, f64), scan_window: ScanWindow, - retained_pane_count: u64, + retained_tumbling_window_count: u64, ingestion_window_ms: Option, data: &DataWorkload, settings: &'a CostInputs, @@ -945,19 +945,19 @@ pub fn node_usage<'a>( tumbling_window_count, .. }) => { - // Each input row is kept by the one pane its time falls in, + // Each input row is kept by the one tumbling window its time falls in, // as a time range keeps it (one operation), and updates - // that pane. + // that tumbling window. let group_count = groups(reduction)?; let states = group_count .checked_mul(u64::from(*tumbling_window_count)) .ok_or(AnalyticalCostError::Overflow)?; - // Each pane's state sees its group's rows in one pane: of + // Each tumbling window's state sees its group's rows in one tumbling window: of // one second's rows at ingestion time, of the rows read at // query time (all of them for a chain kept across - // evaluations, whose scan Stage 2 bounds to its newest pane, + // evaluations, whose scan Stage 2 bounds to its newest tumbling window, // the only one it builds). - let rows_per_pane = match (ingestion, node.materialization) { + let rows_per_tumbling_window = match (ingestion, node.materialization) { (true, _) => u64::try_from( (u128::from(input.rows) * u128::from(*tumbling_window_width_ms)) .div_ceil(1_000), @@ -969,7 +969,7 @@ pub fn node_usage<'a>( .div_ceil(u64::from(*tumbling_window_count).max(1)), }; let input_rows = StateInputRows { - rows_per_state: Some(rows_per_pane.div_ceil(group_count.max(1))), + rows_per_state: Some(rows_per_tumbling_window.div_ceil(group_count.max(1))), non_negative: raw_data_samples_non_negative(), }; let (depth, state_bytes) = summary_shape(family, input_rows); @@ -988,7 +988,7 @@ pub fn node_usage<'a>( 0, )), format!( - "build {} in {tumbling_window_count} panes of {tumbling_window_width_ms} ms for \ + "build {} in {tumbling_window_count} tumbling windows of {tumbling_window_width_ms} ms for \ {group_count} groups: {} rows x (1 + depth {depth})", family_name(family), input.rows @@ -1001,20 +1001,20 @@ pub fn node_usage<'a>( .. }) => { // One operation per input state read; one state per group - // out, each pane of a chain being a state of its group. A - // merge of a chain's newest `n` panes reads `n` per group. A + // out, each tumbling window of a chain being a state of its group. A + // merge of a chain's newest `n` tumbling windows reads `n` per group. A // rollup's groups are those of its keys, at most its input // groups. let mut group_count = 0; for producer in producers { - let panes = match producer.payload { + let tumbling_windows = match producer.payload { Payload::ASAP(ASAPOp::SummaryTumblingWindowChain { tumbling_window_count, .. }) => u64::from(*tumbling_window_count), _ => 1, }; - group_count = group_count.max(producer.output.rows / panes.max(1)); + group_count = group_count.max(producer.output.rows / tumbling_windows.max(1)); } let states = merged_states(*newest_tumbling_windows, producers)?; if let Some(keys) = group_by { @@ -1119,7 +1119,7 @@ pub fn node_usage<'a>( let mut detail = format!("{detail}; x {per_second:.4}/s"); // State kept between computations: at ingestion time, the window // being built and the completed ones queries read (a chain of N - // panes keeps N + 1), every second. At query time the output is + // tumbling windows keeps N + 1), every second. At query time the output is // reused only within one evaluation, so it is held only while that // evaluation runs (counted below, once latencies are known). The // state is what the node holds: its output, or the values an exact @@ -1139,14 +1139,14 @@ pub fn node_usage<'a>( } (0, Medium::Memory) } - // A pane chain kept across evaluations holds the panes its - // retention covers ([`tumbling_window_retention_ms`]: N, plus one per pane + // A tumbling-window chain kept across evaluations holds the tumbling windows its + // retention covers ([`tumbling_window_retention_ms`]: N, plus one per tumbling window // width of the readers' range-evaluation span), all but the // newest between evaluations and all while one runs. (Materialization::MatAtQueryKeptAcrossEvaluations(medium), _) => { - let panes = panes_of(node); - let retained = retained_pane_count; - let between = (state / panes) + let tumbling_windows = tumbling_windows_of(node); + let retained = retained_tumbling_window_count; + let between = (state / tumbling_windows) .checked_mul( retained .checked_sub(1) @@ -1156,9 +1156,9 @@ pub fn node_usage<'a>( match medium { Medium::Memory => { average_memory_bytes = between as f64; - peak_memory_bytes = match retained == panes { + peak_memory_bytes = match retained == tumbling_windows { true => state, - false => (state / panes) + false => (state / tumbling_windows) .checked_mul(retained) .ok_or(AnalyticalCostError::Overflow)?, }; @@ -1178,16 +1178,16 @@ pub fn node_usage<'a>( } (0, medium) } - // The retained panes and the one being built. + // The retained tumbling windows and the one being built. ( Materialization::MatAtIngestion(medium), Payload::ASAP(ASAPOp::SummaryTumblingWindowChain { .. }), ) => { - let panes = panes_of(node); + let tumbling_windows = tumbling_windows_of(node); ( - (state / panes) + (state / tumbling_windows) .checked_mul( - retained_pane_count + retained_tumbling_window_count .checked_add(1) .ok_or(AnalyticalCostError::Overflow)?, ) @@ -1201,7 +1201,7 @@ pub fn node_usage<'a>( ), }; if node.materialization == Materialization::MatAtIngestion(Medium::Memory) { - maintained = Some(state / panes_of(node)); + maintained = Some(state / tumbling_windows_of(node)); } if kept > 0 { match medium { @@ -1328,7 +1328,7 @@ fn query_latencies( |id: PhysicalASAPNodeId| nodes[&id].output_state.timing == ExecutionTiming::IngestionTime; let compaction_ms = |state: PhysicalASAPNodeId| { let workers = ingest_workers(dag, work_ms, state) as f64; - let states = (output[&state].rows / panes_of(nodes[&state])) as f64; + let states = (output[&state].rows / tumbling_windows_of(nodes[&state])) as f64; let per_merge_ms = merge_ms.get(&state).copied().unwrap_or(formula_merge_ms); (workers - 1.0) * states * per_merge_ms }; @@ -1381,10 +1381,10 @@ fn ingest_workers( (ingest_ms_per_s / 1_000.0).ceil().max(1.0) as u64 } -/// For each tumbling-pane chain of `dag` kept beyond one evaluation (at +/// For each tumbling-window chain of `dag` kept beyond one evaluation (at /// ingestion time, or at query time across evaluations), how far back -/// from the newest evaluation its completed panes must be kept, in -/// milliseconds: how long before the evaluation its newest pane ends (the +/// from the newest evaluation its completed tumbling windows must be kept, in +/// milliseconds: how long before the evaluation its newest tumbling window ends (the /// offset of its input, `chain_input_offset_ms`), plus its window /// `tumbling_window_count · tumbling_window_width_ms`, plus the longest /// `span_ms` ([`RangeEvaluation`]) among the queries reaching it, @@ -1431,7 +1431,7 @@ pub fn tumbling_window_retention_ms( .collect() } -/// How long before the evaluation a chain's newest pane ends: the offsets +/// How long before the evaluation a chain's newest tumbling window ends: the offsets /// of the `TimeShift`s (without an `@` anchor) directly below the chain, /// added up; 0 for a negative total. fn chain_input_offset_ms( @@ -1452,7 +1452,7 @@ fn chain_input_offset_ms( u64::try_from(offset_ms).unwrap_or(0) } -/// The pane retention of every kept pane chain of `dag` +/// The tumbling-window retention of every kept tumbling-window chain of `dag` /// ([`tumbling_window_retention_ms`] over [`query_reach`]), for a plan whose query /// `q` has range evaluation `range_evaluations[q]` (none when missing): /// what Stage 2's realizer records for a realized plan. @@ -1464,8 +1464,8 @@ pub fn tumbling_window_retention_ms_of_plan( } /// The input states a `SummaryMerge` over `producers` reads: every row of -/// each, or of the one pane chain whose newest `newest_tumbling_windows` it merges, -/// that many panes' states per group. +/// each, or of the one tumbling-window chain whose newest `newest_tumbling_windows` it merges, +/// that many tumbling windows' states per group. fn merged_states( newest_tumbling_windows: Option, producers: &[ProducerOutput<'_>], @@ -1487,9 +1487,9 @@ fn merged_states( }) } -/// The panes a chain in `retention_ms` ([`tumbling_window_retention_ms`]) keeps -/// completed: its retention over its pane width, at least its pane count. -fn retained_panes( +/// The tumbling windows a chain in `retention_ms` ([`tumbling_window_retention_ms`]) keeps +/// completed: its retention over its tumbling-window width, at least its tumbling-window count. +fn retained_tumbling_windows( node: &asap_types::ir::physical_export::PhysicalASAPDAGNode, retention_ms: &BTreeMap, ) -> u64 { @@ -1500,13 +1500,13 @@ fn retained_panes( .. }), Some(&retention), - ) => (retention / tumbling_window_width_ms.max(&1)).max(panes_of(node)), - _ => panes_of(node), + ) => (retention / tumbling_window_width_ms.max(&1)).max(tumbling_windows_of(node)), + _ => tumbling_windows_of(node), } } -/// A node's panes: a pane chain's count, 1 for any other node. -pub fn panes_of(node: &asap_types::ir::physical_export::PhysicalASAPDAGNode) -> u64 { +/// A node's tumbling windows: a tumbling-window chain's count, 1 for any other node. +pub fn tumbling_windows_of(node: &asap_types::ir::physical_export::PhysicalASAPDAGNode) -> u64 { match &node.payload { Payload::ASAP(ASAPOp::SummaryTumblingWindowChain { tumbling_window_count, @@ -1543,8 +1543,8 @@ pub fn query_reach( .collect() } -/// The window an ingestion-time grouping `node` maintains: a pane chain's -/// panes, or the time range below it (through filters, projections and +/// The window an ingestion-time grouping `node` maintains: a tumbling-window chain's +/// tumbling windows, or the time range below it (through filters, projections and /// time shifts). `None` when there is none. fn maintained_window_ms( node: PhysicalASAPNodeId, @@ -1594,13 +1594,13 @@ pub fn node_map(dag: &PhysicalASAPDAG) -> NodeMap<'_> { pub type NodeMap<'a> = HashMap; -/// The tumbling-pane chains of `dag` kept across evaluations +/// The tumbling-window chains of `dag` kept across evaluations /// (`MatAtQueryKeptAcrossEvaluations`, which Stage 2 offers only when the /// deployment keeps query-time results and every reader builds one new -/// pane per evaluation). Each evaluation builds the newest pane, and the +/// tumbling window per evaluation). Each evaluation builds the newest tumbling window, and the /// older ones, which complete the readers' look-back, are kept from /// earlier evaluations. -pub fn kept_pane_chains(dag: &PhysicalASAPDAG) -> HashSet { +pub fn kept_tumbling_window_chains(dag: &PhysicalASAPDAG) -> HashSet { dag.nodes .iter() .filter(|node| { @@ -1619,7 +1619,7 @@ pub fn kept_pane_chains(dag: &PhysicalASAPDAG) -> HashSet { /// The widest window, in milliseconds, read above the scan `scan` at query /// time, ignoring readers that read it outside a window and the anchors of /// shifts: each range `TimeRange` and `SummaryTumblingWindowChain` (its whole window, or -/// one pane for a chain in `kept_chains`) reached through time shifts. +/// one tumbling window for a chain in `kept_chains`) reached through time shifts. /// Prices a scan whose [`PhysicalASAPDAG::scan_window`] is unbounded as /// Stage 3 did before; #955 decides `@` anchors. fn widest_window_read_ms( diff --git a/crates/cost/tests/held_memory.rs b/crates/cost/tests/held_memory.rs index 6f467f544..8bc3978c0 100644 --- a/crates/cost/tests/held_memory.rs +++ b/crates/cost/tests/held_memory.rs @@ -4,7 +4,9 @@ use asap_cost::execution_demand::evaluation_rate; use asap_cost::ResourceUnitCost; -use asap_cost::{kept_pane_chains, node_map, plan_cost, CostInputs, CostModel, PlanCost}; +use asap_cost::{ + kept_tumbling_window_chains, node_map, plan_cost, CostInputs, CostModel, PlanCost, +}; use asap_logical_asap_optimization::test_support::lower_promql; use asap_physical_asap_optimization::optimize_physical_asap; use asap_types::ir::physical_export::PhysicalASAPDAG; @@ -54,7 +56,7 @@ fn cost(dag: &PhysicalASAPDAG, cost_per_memory_byte_second: f64) -> PlanCost { plan_cost( dag, &nodes, - &kept_pane_chains(dag), + &kept_tumbling_window_chains(dag), &recurrence(), &[], &data(), diff --git a/crates/cost/tests/ingestion_refresh_runs.rs b/crates/cost/tests/ingestion_refresh_runs.rs index bdc09b2f8..ebd33bab5 100644 --- a/crates/cost/tests/ingestion_refresh_runs.rs +++ b/crates/cost/tests/ingestion_refresh_runs.rs @@ -51,7 +51,7 @@ fn quantile_sketch(algorithm: SketchAlgorithm) -> FieldDataType { FieldDataType::Sketch(SketchKind::new(algorithm, params), Default::default()) } -/// `tumbling_window_count` panes of `tumbling_window_width_ms` of a quantile sketch of +/// `tumbling_window_count` tumbling windows of `tumbling_window_width_ms` of a quantile sketch of /// `algorithm` per `job` over `input`. fn chain( input: &Rc, @@ -134,11 +134,11 @@ fn shifted(child: &Rc, offset_ms: i64) -> Rc { const AT_INGESTION: Materialization = Materialization::MatAtIngestion(Medium::Memory); -/// A KLL chain and a DDSketch chain of 10 s panes over one `Scan`, both -/// kept at ingestion, are refreshed in one ingestion run per pane: the +/// A KLL chain and a DDSketch chain of 10 s tumbling windows over one `Scan`, both +/// kept at ingestion, are refreshed in one ingestion run per tumbling window: the /// `Scan` is computed once per second of arriving data, not twice. #[test] -fn a_scan_under_two_chains_of_one_pane_width_is_computed_once() { +fn a_scan_under_two_chains_of_one_tumbling_window_width_is_computed_once() { let scan = scan(); let dag = dag_of( &[ @@ -150,11 +150,11 @@ fn a_scan_under_two_chains_of_one_pane_width_is_computed_once() { assert_eq!(scan_computations(&dag), (1, 1.0)); } -/// With 10 s and 60 s panes the chains need different runs (the 10 s chain -/// needs five pane ends the 60 s chain does not): the `Scan` is computed +/// With 10 s and 60 s tumbling windows the chains need different runs (the 10 s chain +/// needs five tumbling window ends the 60 s chain does not): the `Scan` is computed /// once for each. #[test] -fn a_scan_under_chains_of_two_pane_widths_is_computed_for_each() { +fn a_scan_under_chains_of_two_tumbling_window_widths_is_computed_for_each() { let scan = scan(); let dag = dag_of( &[ @@ -217,7 +217,7 @@ fn a_scan_under_chains_of_10_10_and_60_s_is_computed_for_each_width() { } /// A 10 s chain over the `Scan` and a 10 s chain over the `Scan` shifted by -/// 5 s end their panes on different grids (phases 0 and 5 s): two runs. +/// 5 s end their tumbling windows on different grids (phases 0 and 5 s): two runs. #[test] fn chains_of_one_width_and_two_phases_are_computed_apart() { let scan = scan(); diff --git a/crates/devtools/src/bin/stage_pipeline.rs b/crates/devtools/src/bin/stage_pipeline.rs index 8d235cc47..6889a6e3f 100644 --- a/crates/devtools/src/bin/stage_pipeline.rs +++ b/crates/devtools/src/bin/stage_pipeline.rs @@ -73,16 +73,16 @@ // `guarantee_check_rejection`, the sketches read and a one-line summary), // `pruned_candidates` and `search_report` (`SearchReport`: `status`, // `cost_lower_bound_per_second`, MILP size and timings); `error` when no -// plan was selected. `tumbling_window_retention_ms` maps each pane chain the +// plan was selected. `tumbling_window_retention_ms` maps each tumbling-window chain the // selected plan keeps beyond one evaluation (by node id in `P1`) to how -// far back the runtime must keep its panes (`RealizedPlan::tumbling_window_retention_ms`). +// far back the runtime must keep its tumbling windows (`RealizedPlan::tumbling_window_retention_ms`). // `materialized_views` are the selected plan's materialized views as // `plan_installable`'s plan derives them (`installable_plan_of`, then // `InstallablePlan::materialized_views`: each view's installed node id, // `refresh`, `coverage` with its `definition_id` and `selection`, // `inputs` and `retention_ms`), each with the node of `P1` it is // installed from (`stage2_node_id`), its position among that node's -// installed nodes (`installed_position`, a pane chain's panes newest +// installed nodes (`installed_position`, a tumbling-window chain's tumbling windows newest // first) and their number (`installed_count`); empty without a // selection, with `materialized_views_error` when the plan is not // installable. `warnings` are one-line warnings about the workload and @@ -94,7 +94,7 @@ // declares no memory capacity (`large_peak_memory_warning`); absent when // there are none; // - planning_report: the facade's `PlanningReport`: Pass 1's time and, per -// pane-width round, Pass 2's and Stage 2's counts and each stage's time, +// tumbling-window-width round, Pass 2's and Stage 2's counts and each stage's time, // with Stage 3's search report or error; // - reference_pricing (with `--exhaustive`): the reference pricer's // selection among the enumerated plans, named as Stage 3's `Selection` @@ -115,7 +115,7 @@ // their parents); `steps`, one per // memo step in pipeline order (`stage0`: the frontends' DAG hash-consed, // groups without candidates; `pass1`: Pass 1's candidates, with the -// accuracy-scoped sizes; `pass2`: local window composition's pane +// accuracy-scoped sizes; `pass2`: local window composition's tumbling window // candidates; `concrete_states`: the Pass 2 memo rules (scan labels, // summary inputs, top-k reuse, finer aggregates, rollups, summary results, // state configurations) and the concrete summary-state groups, the memo the @@ -214,7 +214,7 @@ // does not generate a query-time scan of raw data (its `pruned_candidates` // lists each with `RawDataNotRetained`), and stored data (at rest or // mixed) is refused as a contradiction; with `query_time_results_retained`, -// a tumbling-pane chain whose readers evaluate together at its pane width is +// a tumbling-window chain whose readers evaluate together at its tumbling-window width is // also offered built at query time and kept across evaluations, beside being // shared within one evaluation. The document's top-level // `deployment_capabilities` shows the capabilities used, with the @@ -1173,7 +1173,7 @@ fn round_robin<'a, C: PartialEq>( /// plus the node of the selected Stage 2 candidate it is installed from /// (`stage2_node_id`, null when Stage 3 recorded no cost to map it by), its /// position among that node's installed nodes (`installed_position`: a -/// pane chain's panes, newest first) and their number (`installed_count`). +/// tumbling-window chain's tumbling windows, newest first) and their number (`installed_count`). fn materialized_views_json( roots: &[QueryRoot], selection: &Selection, diff --git a/crates/devtools/src/candidate_labels.rs b/crates/devtools/src/candidate_labels.rs index 56fe294b0..772fa3b30 100644 --- a/crates/devtools/src/candidate_labels.rs +++ b/crates/devtools/src/candidate_labels.rs @@ -136,7 +136,7 @@ fn duration_name(ms: u64) -> String { } /// `logical` followed by what the candidate materializes, e.g. -/// "… · ingestion time: Kll for Q1, 5 × 1m panes · query time, shared: +/// "… · ingestion time: Kll for Q1, 5 × 1m tumbling windows · query time, shared: /// DDSketch"; /// just `logical` when nothing is. pub fn physical_label(logical: &str, candidate: &PhysicalWorkloadPlan) -> String { @@ -155,14 +155,14 @@ pub fn physical_label(logical: &str, candidate: &PhysicalWorkloadPlan) -> String .find(|n| n.id == *id) .expect("an assigned node is in the DAG"); let label = match &node.payload { - // Name the chain: chains of one family and pane count differ by - // their readers and pane width. + // Name the chain: chains of one family and tumbling-window count differ by + // their readers and tumbling-window width. Operator::ASAP(ASAPOp::SummaryTumblingWindowChain { tumbling_window_width_ms, tumbling_window_count, .. }) => format!( - "{}, {tumbling_window_count} × {} panes", + "{}, {tumbling_window_count} × {} tumbling windows", for_queries( state_label(&node.payload), &dag_readers(&candidate.dag, *id) @@ -186,20 +186,23 @@ pub fn physical_label(logical: &str, candidate: &PhysicalWorkloadPlan) -> String .join(" · ") } -/// A summary build, e.g. "Kll ×5 panes" or "Sum acc". +/// A summary build, e.g. "Kll ×5 tumbling windows" or "Sum acc". fn build_label(payload: &Operator) -> String { match payload { Operator::ASAP(ASAPOp::SummaryTumblingWindowChain { tumbling_window_count, .. }) => { - format!("{} ×{tumbling_window_count} panes", state_label(payload)) + format!( + "{} ×{tumbling_window_count} tumbling windows", + state_label(payload) + ) } _ => state_label(payload), } } -/// The state a summary build or pane chain keeps, e.g. "Kll" or "Sum acc"; +/// The state a summary build or tumbling-window chain keeps, e.g. "Kll" or "Sum acc"; /// any other operator's kind. fn state_label(payload: &Operator) -> String { match payload { @@ -309,13 +312,13 @@ mod tests { assert_eq!(label.matches("shared Sum acc").count(), 1); } - /// Two physical candidates that each maintain a different query's pane + /// Two physical candidates that each maintain a different query's tumbling window /// chain at ingestion time get different labels, even when both chains - /// have the same family and pane count (6 × 10s for Q1, 6 × 30s for Q2). + /// have the same family and tumbling-window count (6 × 10s for Q1, 6 × 30s for Q2). #[test] - fn physical_labels_name_the_pane_chain_maintained_at_ingestion() { + fn physical_labels_name_the_tumbling_window_chain_maintained_at_ingestion() { use asap_logical_asap_optimization::test_support::{ - pane_candidates, window_composition_pass2_candidates, + tumbling_window_candidates, window_composition_pass2_candidates, }; use asap_physical_asap_optimization::optimize_physical_asap; use asap_types::workload::PlanningWorkload; @@ -354,12 +357,12 @@ mod tests { let summary: Vec<_> = compose_logical_candidate(&inventory, &choice).unwrap(); let entries: Vec<_> = workload.query_workload.entries().collect(); let grids: Vec<_> = entries.iter().map(|e| e.evaluation_grid_ms()).collect(); - let panes = pane_candidates(&summary, &grids); - let roots = window_composition_pass2_candidates(&summary, &panes) + let tumbling_windows = tumbling_window_candidates(&summary, &grids); + let roots = window_composition_pass2_candidates(&summary, &tumbling_windows) .unwrap() .into_iter() - .find(|c| c.pane_candidates.len() == 2 && c.shared_chains.is_empty()) - .expect("each query's DDSketch in its own pane chain") + .find(|c| c.tumbling_window_candidates.len() == 2 && c.shared_chains.is_empty()) + .expect("each query's DDSketch in its own tumbling-window chain") .roots .unwrap(); let plans = optimize_physical_asap( @@ -370,7 +373,7 @@ mod tests { false, ) .unwrap(); - // The plans that maintain exactly one pane chain at ingestion time. + // The plans that maintain exactly one tumbling-window chain at ingestion time. let one_chain_at_ingestion: Vec = plans .iter() .filter(|plan| { @@ -403,14 +406,14 @@ mod tests { one_chain_at_ingestion .iter() .any(|l| ingestion(l).as_deref() - == Some("ingestion time: DDSketch for Q1, 6 × 10s panes")), + == Some("ingestion time: DDSketch for Q1, 6 × 10s tumbling windows")), "{one_chain_at_ingestion:#?}" ); assert!( one_chain_at_ingestion .iter() .any(|l| ingestion(l).as_deref() - == Some("ingestion time: DDSketch for Q2, 6 × 30s panes")), + == Some("ingestion time: DDSketch for Q2, 6 × 30s tumbling windows")), "{one_chain_at_ingestion:#?}" ); let mut distinct = one_chain_at_ingestion.clone(); diff --git a/crates/devtools/src/planner_fuzz/README.md b/crates/devtools/src/planner_fuzz/README.md index 9e21316a9..d6adb0c47 100644 --- a/crates/devtools/src/planner_fuzz/README.md +++ b/crates/devtools/src/planner_fuzz/README.md @@ -91,7 +91,7 @@ For a query `q` of workload `W` and a query `q'` derivable from `q`, the tester builds a **witness**. The witness is `W`'s selected logical plan (its Stage 1 candidate) plus one more root that derives `q'` from `q`'s node in that plan, by the operator the relation names. Three witnesses change that plan too: m -and n lengthen the pane chain `q` reads (so `q` merges its newest panes of +and n lengthen the tumbling-window chain `q` reads (so `q` merges its newest tumbling windows of the longer chain); l's `q'` also reads a sibling query, so `W` is planned with the sibling added; and h's second witness is `W ∪ {q'}` planned with UnivMon as the only sketch family. Each is a plan for `W ∪ {q'}`. The tester @@ -133,7 +133,7 @@ Two rules decide whether a check counts: | `a-looser-accuracy` | `q` at the next looser target | reading `q`'s node | | `b-filter-on-group-key` | grouped `q` plus `label = 'value'` on a group key | `Filter` over `q`'s result | | `c-coarser-grouping` | `q` grouped by one key fewer | exact `Aggregate` (sum / max / min) over `q`'s result, or a sketch `SummaryMerge{group_by}` over `q`'s state | -| `d-nested-shorter-window` | `q` over a shorter window that divides it | `SummaryMerge{newest_tumbling_windows}` over `q`'s pane chain | +| `d-nested-shorter-window` | `q` over a shorter window that divides it | `SummaryMerge{newest_tumbling_windows}` over `q`'s tumbling-window chain | | `e-other-quantile-rank` | `q` at another rank | `q`'s estimate (or exact quantile aggregate) at the other rank, same input | | `f-exact-duplicate` | `q` | reading `q`'s node | | `g-outer-over-result` | an operator over `q`, by `q`'s outer operator: `topk(3, q)` (sum), `bottomk(3, q)` (count), `max(q)` (max), `quantile(0.5, q)` (avg), `q > 0` (min, quantile) | the operator's lowering grafted onto `q`'s node | @@ -142,7 +142,7 @@ Two rules decide whether a check counts: | `j-smaller-topk` | `q`'s top-k at k = 3 | `q`'s `Limit` / `TopK` at 3 over the same input | | `k-union-of-selections` | `q` over `label =~ "v1\|v2"`, with `q`'s `v2` twin added to `W` | `SummaryMerge` of the two states, then `q`'s estimate | | `l-binary-over-result` | PromQL: `q{label="v"} / q` on a label `q` does not group by, or `q / q_count` for a `sum_over_time` `q` with no outer operator or `sum` (avg as sum/count) | the division's lowering grafted onto `q`'s node | -| `m-longer-window` | `q` over the shortest longer window (up to 1 h) that is a multiple of `q`'s; not for `rate` or `increase` | `q`'s pane chain lengthened: every merge of it in `W`'s plan reads its old newest panes, and `q'` merges the whole chain | +| `m-longer-window` | `q` over the shortest longer window (up to 1 h) that is a multiple of `q`'s; not for `rate` or `increase` | `q`'s tumbling-window chain lengthened: every merge of it in `W`'s plan reads its old newest tumbling windows, and `q'` merges the whole chain | | `n-longer-window-rollup` | `q` over a longer window, as in m, grouped by one key fewer | m's derivation, then c's rollup | | `o-equivalent-range-function` | `q` with `rate` and `increase` swapped (not under `count`) | `q`'s result times (or divided by) the window in seconds, grafted onto `q`'s node | @@ -200,21 +200,21 @@ status column, "conclusive" means the check either held or was violated. | Slice: a filter on a group key over a grouped result or summary (planned) | asap-primitive-schema.md "Other uses of coverage" L846–848, L118 | `b-filter-on-group-key` | VIOLATED (995 of 1,003): #891, #902 (#689 for approximate inputs) | | Rollup to a coarser grouping: exact, or sketch `SummaryMerge{group_by}` | planner-layering.md rule table L588; asap-primitive-schema.md §5.6 L670, L754–794, L830–844; joint-planning-search.md L324 | `c-coarser-grouping` | VIOLATED (167 of 389): new drafts (exact rollup; sketch rollup over an inner range function) | | Scan widening: scans of one metric with different labels share one scan | planner-layering.md Pass 2 L597–607 | `c-coarser-grouping` (sketch rollup witness) | covered by c; no separate violation | -| Nested window: a shorter window merges the newest panes of a longer window's chain | planner-layering.md L790–807; asap-primitive-schema.md §5.6 L669 | `d-nested-shorter-window` | VIOLATED (73 of 250): new draft (exact sums); compare #873 (rate/increase) | +| Nested window: a shorter window merges the newest tumbling windows of a longer window's chain | planner-layering.md L790–807; asap-primitive-schema.md §5.6 L669 | `d-nested-shorter-window` | VIOLATED (73 of 250): new draft (exact sums); compare #873 (rate/increase) | | Another quantile rank from the same sketch | planner-layering.md L631–634, L804–817 | `e-other-quantile-rank` | VIOLATED (39 of 465), exact percentiles only: new draft (unshared exact aggregates) | | Identical expressions (common-subexpression elimination) | planner-layering.md rule table L583–585, Algorithm 2 L655–658 | `f-exact-duplicate` | VIOLATED (92 of 2,915), exact query-time aggregates: new draft | | A query over another query's result (top-k over a shared sum) | joint-planning-search.md L177–187; planner-layering.md L583–585 | `g-outer-over-result` | holds (552 conclusive checks). With the operator following q's (ASAPWorkloads #4, #12, #17, #18): VIOLATED (6 of 49), `topk(3, …)` over an exact instant sum and `… > 0` over an approximate quantile | | Summary capability: another statistic the family answers (`FamilyCapability::answers`) | planner-layering.md rule table L586, L611–617, Example 2 L1306–1370 | `h-other-statistic` | KLL and KMV answer one statistic, so h now checks SQL frequency statistics of a high-cardinality column (planner-layering Example 2; ASAPWorkloads #57, #107–#108): holds (5 conclusive checks) | -| gcd pane grid across repeat intervals | planner-layering.md L791–809 | `i-other-repeat-interval` | VIOLATED (49 of 2,717): new draft; compare #731 | +| gcd tumbling-window grid across repeat intervals | planner-layering.md L791–809 | `i-other-repeat-interval` | VIOLATED (49 of 2,717): new draft; compare #731 | | Exact top-k reuse (smaller k) | planner-layering.md rule table L589; joint-planning-search.md L325 | `j-smaller-topk` | holds (62 conclusive checks) | | Merge of disjoint selections | asap-primitive-schema.md §4.2 L194–232, §5.6 Example B L721–752 | `k-union-of-selections` | never conclusive: Stage 1 composes no guarantee for the merged estimate | -| Panes kept across evaluations when query-time results are retained | planner-layering.md Materialization L889–893 | `results-retained` | holds (1,442 conclusive checks) | -| Window = interval: the whole window as a one-pane tumbling chain | planner-layering.md Materialization L895–905 | — | not checked: a Stage 2 choice within one query | -| Rollup over newest panes (windowed rollup) | planner-layering.md L809–823 | — | not checked: a composition of c and d | +| Tumbling windows kept across evaluations when query-time results are retained | planner-layering.md Materialization L889–893 | `results-retained` | holds (1,442 conclusive checks) | +| Window = interval: the whole window as a one-tumbling-window chain | planner-layering.md Materialization L895–905 | — | not checked: a Stage 2 choice within one query | +| Rollup over newest tumbling windows (windowed rollup) | planner-layering.md L809–823 | — | not checked: a composition of c and d | | avg as sum/count: shared exact accumulators | planner-layering.md L372–383, L502–503; ASAPWorkloads #8 | `l-binary-over-result` | holds (15 conclusive checks, with the ratio row below); checks that the sum is shared, not one accumulator for both | | A ratio with a shared operand: a filtered share of a rate (5xx share), one rate over another | ASAPWorkloads #11, #12, #30, #126 | `l-binary-over-result` | holds (see above) | -| Longer window over the same panes: one chain serves 5m and 1h windows | planner-layering.md L790–807; ASAPWorkloads #1–#2, #19, #122 | `m-longer-window` | VIOLATED (2 of 7): SQL count and percentile at a longer window build new state | -| Longer window rolled up to a coarser grouping | ASAPWorkloads #1–#2, #61–#62, #65 | `n-longer-window-rollup` | never conclusive yet: needs a grouped q planned with a pane chain | +| Longer window over the same tumbling windows: one chain serves 5m and 1h windows | planner-layering.md L790–807; ASAPWorkloads #1–#2, #19, #122 | `m-longer-window` | VIOLATED (2 of 7): SQL count and percentile at a longer window build new state | +| Longer window rolled up to a coarser grouping | ASAPWorkloads #1–#2, #61–#62, #65 | `n-longer-window-rollup` | never conclusive yet: needs a grouped q planned with a tumbling-window chain | | `rate` and `increase` of one counter read one state | ASAPWorkloads #1–#2 | `o-equivalent-range-function` | VIOLATED (6 of 6): the second function builds its own state; compare #873 | | Stored-state reuse; sliding windows, exponential histograms, SummarySubtract | asap-primitive-schema.md L849, L851–869; planner-layering.md L618–630, L827 | — | not checked: planned or not implemented | diff --git a/crates/devtools/src/planner_fuzz/relations.rs b/crates/devtools/src/planner_fuzz/relations.rs index a35706a20..1b72e8cc9 100644 --- a/crates/devtools/src/planner_fuzz/relations.rs +++ b/crates/devtools/src/planner_fuzz/relations.rs @@ -44,8 +44,8 @@ pub enum Relation { /// (`Aggregate` over `q`'s result) or a sketch rollup (`SummaryMerge` /// with `group_by` over `q`'s state). CoarserGrouping, - /// (d) `q'` = `q` over a shorter window on the same pane grid: derived - /// by merging the newest panes of `q`'s pane chain. + /// (d) `q'` = `q` over a shorter window on the same tumbling-window grid: derived + /// by merging the newest tumbling windows of `q`'s tumbling-window chain. NestedShorterWindow, /// (e) `q'` = `q` at another quantile rank: derived by one more /// estimate from `q`'s sketch. @@ -75,8 +75,8 @@ pub enum Relation { /// the division with `q` read from `q`'s result. BinaryOverResult, /// (m) `q'` = `q` over a longer window that is a multiple of it: - /// derived by lengthening `q`'s pane chain, which `q` reads the newest - /// panes of. + /// derived by lengthening `q`'s tumbling-window chain, which `q` reads the newest + /// tumbling windows of. LongerWindow, /// (n) `q'` = `q` over a longer window and grouped by one key fewer: /// derived by a rollup over (m)'s derivation. @@ -245,9 +245,9 @@ pub enum Witness { /// `q`'s root estimate reading a `SummaryMerge` with `group_by` the /// `labels` columns over its state, when the family merges groups. SketchRollup { labels: Vec }, - /// `q`'s `SummaryMerge` over a pane chain merging only the newest panes + /// `q`'s `SummaryMerge` over a tumbling-window chain merging only the newest tumbling windows /// covering `window_ms`. - NewestPanes { window_ms: u64 }, + NewestTumblingWindows { window_ms: u64 }, /// `q`'s topmost quantile estimate (or exact quantile aggregate) at rank /// `to` instead of `from`. QuantileRank { from: f64, to: f64 }, @@ -257,8 +257,8 @@ pub enum Witness { /// `q`'s root estimate over a `SummaryMerge` of its state and the state /// of `W`'s last query (the other selection, added to `W`). UnionWithLast, - /// `q`'s pane chain lengthened to cover `window_ms`: every reader of - /// the chain in `W`'s plan merges its old newest panes of the longer + /// `q`'s tumbling-window chain lengthened to cover `window_ms`: every reader of + /// the chain in `W`'s plan merges its old newest tumbling windows of the longer /// chain, and `q'` merges all of them, then applies `rollup` when set. LongerWindow { window_ms: u64, @@ -447,7 +447,7 @@ pub fn derive(relation: Relation, workload: &FuzzWorkload, target: usize) -> Opt let spec = match q.spec.as_ref()? { QuerySpec::Promql(spec) => { // A counter's rate and increase read the window length - // and its boundary samples, not only merged pane state. + // and its boundary samples, not only merged tumbling-window state. if matches!(spec.range?, RangeFunction::Rate | RangeFunction::Increase) { return None; } @@ -465,7 +465,10 @@ pub fn derive(relation: Relation, workload: &FuzzWorkload, target: usize) -> Opt QuerySpec::Promql(s) => s.window_ms, QuerySpec::Sql(s) => s.window_ms, }; - derived(q.with_spec(spec), vec![Witness::NewestPanes { window_ms }]) + derived( + q.with_spec(spec), + vec![Witness::NewestTumblingWindows { window_ms }], + ) } Relation::OtherQuantileRank => { let (spec, from) = match q.spec.as_ref()? { @@ -684,7 +687,7 @@ pub fn derive(relation: Relation, workload: &FuzzWorkload, target: usize) -> Opt let (longer, window_ms) = match q.spec.as_ref()? { QuerySpec::Promql(spec) => { // As for (d): rate and increase read more than merged - // pane state. + // tumbling-window state. if matches!(spec.range?, RangeFunction::Rate | RangeFunction::Increase) { return None; } @@ -1150,8 +1153,10 @@ pub fn witness_root( .map_err(|e| format!("rollup estimate: {e}"))?; with_root_estimate(q_root, estimate, &dropped) } - Witness::NewestPanes { window_ms } => { - rewrite_root(q_root, "summary merge over a pane chain", |node| { + Witness::NewestTumblingWindows { window_ms } => rewrite_root( + q_root, + "summary merge over a tumbling-window chain", + |node| { let Some(ASAPOp::SummaryMerge { children, newest_tumbling_windows: None, @@ -1171,22 +1176,22 @@ pub fn witness_root( else { return None; }; - let panes = window_ms / tumbling_window_width_ms; + let tumbling_windows = window_ms / tumbling_window_width_ms; (window_ms % tumbling_window_width_ms == 0 - && panes >= 1 - && (panes as u32) < *tumbling_window_count) + && tumbling_windows >= 1 + && (tumbling_windows as u32) < *tumbling_window_count) .then(|| { with_operator( node, Operator::ASAP(ASAPOp::SummaryMerge { children: children.clone(), - newest_tumbling_windows: Some(panes as u32), + newest_tumbling_windows: Some(tumbling_windows as u32), group_by: group_by.clone(), }), ) }) - }) - } + }, + ), Witness::QuantileRank { from, to } => { let mut done = false; rewrite_root(q_root, "quantile estimate", |node| { @@ -1448,8 +1453,8 @@ fn collect( } } -/// `W`'s roots with the pane chain `q` merges whole lengthened to cover -/// `window_ms`, every merge of it reading its old newest panes of the +/// `W`'s roots with the tumbling-window chain `q` merges whole lengthened to cover +/// `window_ms`, every merge of it reading its old newest tumbling windows of the /// longer chain; and `q`'s root merging the whole longer chain. fn longer_chain_roots( base_roots: &[QueryRoot], @@ -1467,14 +1472,14 @@ fn longer_chain_roots( ) }; let merge = find_first(q_root, &whole_chain_merge) - .ok_or("q's plan has no summary merge over a pane chain")?; + .ok_or("q's plan has no summary merge over a tumbling-window chain")?; // Lengthening one of several chains (say, an SQL idiom's exact row // count beside its statistic) would leave the others at q's window. let mut merges = Vec::new(); collect(q_root, &whole_chain_merge, &mut merges); if merges.len() > 1 { return Err(format!( - "q's plan merges {} pane chains whole", + "q's plan merges {} tumbling-window chains whole", merges.len() )); } @@ -1489,14 +1494,14 @@ fn longer_chain_roots( .. }) = &mut operator else { - unreachable!("found a pane chain"); + unreachable!("found a tumbling-window chain"); }; let old_count = *tumbling_window_count; if !window_ms.is_multiple_of(*tumbling_window_width_ms) || window_ms / *tumbling_window_width_ms <= old_count as u64 { return Err(format!( - "q's chain of {old_count} panes of {tumbling_window_width_ms} ms does not extend to {window_ms} ms" + "q's chain of {old_count} tumbling windows of {tumbling_window_width_ms} ms does not extend to {window_ms} ms" )); } *tumbling_window_count = (window_ms / *tumbling_window_width_ms) as u32; @@ -1970,7 +1975,7 @@ mod tests { op: RollupOp::Sum } ); - // A counter's rate reads more than merged panes: no nested window. + // A counter's rate reads more than merged tumbling windows: no nested window. assert_eq!(text(Relation::NestedShorterWindow), None); assert_eq!(text(Relation::OtherQuantileRank), None); assert_eq!( @@ -2078,7 +2083,7 @@ mod tests { text: "(sum by (job, instance) (rate(m_counter[5m]))) * 300".into() }] ); - // As for (d), a rate's window is not merged pane state. + // As for (d), a rate's window is not merged tumbling-window state. assert_eq!(text(&rate, Relation::LongerWindow), None); let mut sum = sum_by(&["job", "instance"]); diff --git a/crates/devtools/src/planner_fuzz/report.rs b/crates/devtools/src/planner_fuzz/report.rs index b29aca898..88abee049 100644 --- a/crates/devtools/src/planner_fuzz/report.rs +++ b/crates/devtools/src/planner_fuzz/report.rs @@ -57,8 +57,8 @@ pub fn documented_patterns() -> Vec { note: "checked through the sketch rollup witness, which reads the finer query's scan and state", }, DocumentedPattern { - pattern: "Nested window: a shorter window merges the newest panes of a longer window's chain", - doc_reference: "planner-layering.md window composition L790–807; asap-primitive-schema.md §5.6 \"Newest panes\" L669", + pattern: "Nested window: a shorter window merges the newest tumbling windows of a longer window's chain", + doc_reference: "planner-layering.md window composition L790–807; asap-primitive-schema.md §5.6 \"Newest tumbling windows\" L669", relation: Some(Relation::NestedShorterWindow), note: "", }, @@ -87,7 +87,7 @@ pub fn documented_patterns() -> Vec { note: "UnivMon's count, distinct count, L2 and entropy (ASAPWorkloads #57, #107–#108); the witness reads q's own state, or is W ∪ {q'} planned with UnivMon as the only family", }, DocumentedPattern { - pattern: "gcd pane grid across repeat intervals: queries at different intervals read one chain at their own cadence", + pattern: "gcd tumbling-window grid across repeat intervals: queries at different intervals read one chain at their own cadence", doc_reference: "planner-layering.md L791–809", relation: Some(Relation::OtherRepeatInterval), note: "", @@ -105,19 +105,19 @@ pub fn documented_patterns() -> Vec { note: "the merge operator is implemented; the docs name no Pass 2 rule that creates it", }, DocumentedPattern { - pattern: "Panes kept across evaluations when the deployment retains query-time results", + pattern: "Tumbling windows kept across evaluations when the deployment retains query-time results", doc_reference: "planner-layering.md Materialization L889–893", relation: Some(Relation::ResultsRetained), note: "a capability never raises cost", }, DocumentedPattern { - pattern: "Window = interval: the whole window maintained as a one-pane tumbling chain", + pattern: "Window = interval: the whole window maintained as a one-tumbling-window chain", doc_reference: "planner-layering.md Materialization L895–905", relation: None, note: "a Stage 2 choice within one query, not a q → q' derivation", }, DocumentedPattern { - pattern: "Rollup over newest panes (windowed rollup)", + pattern: "Rollup over newest tumbling windows (windowed rollup)", doc_reference: "planner-layering.md L809–823", relation: None, note: "a composition of c and d; not checked as one relation", @@ -135,7 +135,7 @@ pub fn documented_patterns() -> Vec { note: "", }, DocumentedPattern { - pattern: "Longer window over the same panes: one chain serves 5m and 1h windows", + pattern: "Longer window over the same tumbling windows: one chain serves 5m and 1h windows", doc_reference: "planner-layering.md window composition L790–807; ASAPWorkloads #1–#2, #19, #122", relation: Some(Relation::LongerWindow), note: "rate and increase excluded, as for d", @@ -725,8 +725,8 @@ fn derivation_text(witness: &Witness) -> String { "a `SummaryMerge` with `group_by` = ({}) over q's summary state, then q's estimate", labels.join(", ") ), - Witness::NewestPanes { window_ms } => format!( - "a `SummaryMerge` of the newest panes of q's pane chain covering {window_ms} ms" + Witness::NewestTumblingWindows { window_ms } => format!( + "a `SummaryMerge` of the newest tumbling windows of q's tumbling-window chain covering {window_ms} ms" ), Witness::QuantileRank { from, to } => { format!("q's quantile estimate at rank {to} instead of {from}, from the same state") @@ -748,8 +748,8 @@ fn derivation_text(witness: &Witness) -> String { } Witness::LongerWindow { window_ms, rollup } => { let lengthened = format!( - "q's pane chain lengthened to cover {window_ms} ms (q merges its old newest \ - panes), merged whole" + "q's tumbling-window chain lengthened to cover {window_ms} ms (q merges its old newest \ + tumbling windows), merged whole" ); match rollup { Some(rollup) => format!("{lengthened}, then {}", derivation_text(rollup)), @@ -806,7 +806,9 @@ fn write_draft( format!("A coarser grouping of `{q}` does not roll up its result or state") } Relation::NestedShorterWindow => { - format!("A shorter window of `{q}` does not merge the newest panes of its chain") + format!( + "A shorter window of `{q}` does not merge the newest tumbling windows of its chain" + ) } Relation::OtherQuantileRank => { format!("Another quantile rank of `{q}` does not read the same sketch") @@ -831,7 +833,7 @@ fn write_draft( format!("A ratio with `{q}` as an operand does not read `{q}`'s result") } Relation::LongerWindow => { - format!("A longer window of `{q}` does not extend its pane chain") + format!("A longer window of `{q}` does not extend its tumbling-window chain") } Relation::LongerWindowRollup => format!( "A longer window and coarser grouping of `{q}` does not roll up its extended chain" diff --git a/crates/devtools/src/planning_stages.rs b/crates/devtools/src/planning_stages.rs index 853987555..47618fdd6 100644 --- a/crates/devtools/src/planning_stages.rs +++ b/crates/devtools/src/planning_stages.rs @@ -21,7 +21,7 @@ pub const DEFAULT_REFERENCE_CHOICES: usize = 10_000; pub struct PlanningStagesRun { /// The query-language frontends' roots, one per workload entry. pub stage0_query_language_frontends: Vec, - /// The facade's run: Pass 1, the pane-width rounds and the selection. + /// The facade's run: Pass 1, the tumbling-window-width rounds and the selection. pub run: PlanningRun, /// `--exhaustive`: the selected round's memo choices, each implemented /// by Stage 2 and all priced by the reference pricer. diff --git a/crates/devtools/tests/corpus_snapshots.rs b/crates/devtools/tests/corpus_snapshots.rs index 251d5f7e5..10ef56a76 100644 --- a/crates/devtools/tests/corpus_snapshots.rs +++ b/crates/devtools/tests/corpus_snapshots.rs @@ -12,7 +12,7 @@ //! //! A snapshot holds only the plan, never the search that found it: the //! selected plan's DAG (Stage 2 candidate `P1`) and display name, its cost -//! and accepted accuracy per query, the materialized views, pane retention, +//! and accepted accuracy per query, the materialized views, tumbling-window retention, //! and whether the search proved the plan optimal; for a query that does not //! plan, the error. Candidate ids, round and evaluation counters, timings, the //! Stage 1 and Stage 2 candidate lists and the memo vary from run to run @@ -90,7 +90,7 @@ fn stage_pipeline(args: &[String], scratch: &Path) -> Value { } /// Whether the wall-clock budget ended the search: planning (Pass 1 and -/// every pane-width round) used the whole budget. A round cut short by its +/// every tumbling-window-width round) used the whole budget. A round cut short by its /// own budget is not visible in the report. fn hit_time_limit(document: &Value) -> bool { let report = &document["planning_report"]; @@ -118,10 +118,10 @@ fn search_status(document: &Value) -> &Value { } /// Whether the plan is proven optimal: its search says so and every legal -/// pane width was searched. +/// tumbling-window width was searched. fn proven_optimal(document: &Value) -> bool { search_status(document) == "optimal" - && document["planning_report"]["pane_widths_pending"] != json!(true) + && document["planning_report"]["tumbling_window_widths_pending"] != json!(true) } /// `value` with every non-integer number rounded to 12 significant digits. @@ -420,14 +420,14 @@ fn time_limit_detection() { )); } -/// An optimal search proves the plan optimal only when no legal pane width +/// An optimal search proves the plan optimal only when no legal tumbling-window width /// was left unsearched. #[test] -fn proven_optimal_needs_every_pane_width() { +fn proven_optimal_needs_every_tumbling_window_width() { let document = |status: &str, pending: bool| { json!({ "stage3_plan_selection": {"search_report": {"status": status}}, - "planning_report": {"pane_widths_pending": pending}, + "planning_report": {"tumbling_window_widths_pending": pending}, }) }; assert!(proven_optimal(&document("optimal", false))); diff --git a/crates/devtools/tests/corpus_snapshots/o11y_bench/q02.json b/crates/devtools/tests/corpus_snapshots/o11y_bench/q02.json index 9937946e5..d803d90f7 100644 --- a/crates/devtools/tests/corpus_snapshots/o11y_bench/q02.json +++ b/crates/devtools/tests/corpus_snapshots/o11y_bench/q02.json @@ -262,9 +262,6 @@ "stage2_node_id": 1 } ], - "tumbling_window_retention_ms": { - "1": 3900000 - }, "proven_optimal": true, "query": "histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{job=\"order-service\"}[5m])) by (le))", "selected": { @@ -295,7 +292,7 @@ "1": { "cost_per_second": 0.464, "cpu_core_seconds_per_second": 2e-6, - "detail": "build exact Rate accumulator in 5 panes of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 3696000 bytes", + "detail": "build exact Rate accumulator in 5 tumbling windows of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 3696000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -947,9 +944,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 5, - "tumbling_window_width_ms": 60000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 60000 } } } @@ -1313,6 +1310,9 @@ ], "scalar_roots": [] }, - "display_name": "Q1 Sum acc + Rate acc ×5 panes · ingestion time: Rate acc for Q1, 5 × 1m panes" + "display_name": "Q1 Sum acc + Rate acc ×5 tumbling windows · ingestion time: Rate acc for Q1, 5 × 1m tumbling windows" + }, + "tumbling_window_retention_ms": { + "1": 3900000 } } diff --git a/crates/devtools/tests/corpus_snapshots/o11y_bench/q05.json b/crates/devtools/tests/corpus_snapshots/o11y_bench/q05.json index f64439876..1e72f4c5c 100644 --- a/crates/devtools/tests/corpus_snapshots/o11y_bench/q05.json +++ b/crates/devtools/tests/corpus_snapshots/o11y_bench/q05.json @@ -2,10 +2,6 @@ "accuracy_guarantees": "enforced", "materialized_views": [], "materialized_views_error": "raw-data window of node 1 is unsupported: a subquery or a TimeShift above the range", - "tumbling_window_retention_ms": { - "1": 3900000, - "7": 3900000 - }, "proven_optimal": false, "query": "max_over_time((sum(rate(http_requests_total{job=\"order-service\",status=~\"5..\"}[5m])) / sum(rate(http_requests_total{job=\"order-service\"}[5m])))[6h:1m])", "selected": { @@ -36,7 +32,7 @@ "1": { "cost_per_second": 0.464, "cpu_core_seconds_per_second": 2e-6, - "detail": "build exact Rate accumulator in 5 panes of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 3696000 bytes", + "detail": "build exact Rate accumulator in 5 tumbling windows of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 3696000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -144,7 +140,7 @@ "7": { "cost_per_second": 0.464, "cpu_core_seconds_per_second": 2e-6, - "detail": "build exact Rate accumulator in 5 panes of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 3696000 bytes", + "detail": "build exact Rate accumulator in 5 tumbling windows of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 3696000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -1121,9 +1117,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 5, - "tumbling_window_width_ms": 60000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 60000 } } } @@ -1607,9 +1603,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 5, - "tumbling_window_width_ms": 60000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 60000 } } } @@ -2078,6 +2074,10 @@ ], "scalar_roots": [] }, - "display_name": "Q1 Max acc + Sum acc + Rate acc ×5 panes · ingestion time: Rate acc for Q1, 5 × 1m panes, Rate acc for Q1, 5 × 1m panes" + "display_name": "Q1 Max acc + Sum acc + Rate acc ×5 tumbling windows · ingestion time: Rate acc for Q1, 5 × 1m tumbling windows, Rate acc for Q1, 5 × 1m tumbling windows" + }, + "tumbling_window_retention_ms": { + "1": 3900000, + "7": 3900000 } } diff --git a/crates/devtools/tests/corpus_snapshots/o11y_bench/q06.json b/crates/devtools/tests/corpus_snapshots/o11y_bench/q06.json index f105a966d..99affb00a 100644 --- a/crates/devtools/tests/corpus_snapshots/o11y_bench/q06.json +++ b/crates/devtools/tests/corpus_snapshots/o11y_bench/q06.json @@ -2,9 +2,6 @@ "accuracy_guarantees": "enforced", "materialized_views": [], "materialized_views_error": "raw-data window of node 1 is unsupported: a subquery or a TimeShift above the range", - "tumbling_window_retention_ms": { - "1": 3900000 - }, "proven_optimal": true, "query": "max_over_time(histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{job=\"user-service\"}[5m])) by (le))[12h:1m])", "selected": { @@ -35,7 +32,7 @@ "1": { "cost_per_second": 0.464, "cpu_core_seconds_per_second": 2e-6, - "detail": "build exact Rate accumulator in 5 panes of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 3696000 bytes", + "detail": "build exact Rate accumulator in 5 tumbling windows of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 3696000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -795,9 +792,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 5, - "tumbling_window_width_ms": 60000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 60000 } } } @@ -1317,6 +1314,9 @@ ], "scalar_roots": [] }, - "display_name": "Q1 Max acc + Sum acc + Rate acc ×5 panes · ingestion time: Rate acc for Q1, 5 × 1m panes" + "display_name": "Q1 Max acc + Sum acc + Rate acc ×5 tumbling windows · ingestion time: Rate acc for Q1, 5 × 1m tumbling windows" + }, + "tumbling_window_retention_ms": { + "1": 3900000 } } diff --git a/crates/devtools/tests/corpus_snapshots/o11y_bench/q11.json b/crates/devtools/tests/corpus_snapshots/o11y_bench/q11.json index 4bb42ebd1..16f3c5c73 100644 --- a/crates/devtools/tests/corpus_snapshots/o11y_bench/q11.json +++ b/crates/devtools/tests/corpus_snapshots/o11y_bench/q11.json @@ -2,7 +2,6 @@ "accuracy_guarantees": "enforced", "materialized_views": [], "materialized_views_error": "raw-data window of node 1 is unsupported: a subquery or a TimeShift above the range", - "tumbling_window_retention_ms": {}, "proven_optimal": true, "query": "max_over_time(sum(process_resident_memory_bytes)[6h:1m])", "selected": { @@ -758,5 +757,6 @@ "scalar_roots": [] }, "display_name": "Q1 Max acc + Sum acc" - } + }, + "tumbling_window_retention_ms": {} } diff --git a/crates/devtools/tests/corpus_snapshots/o11y_bench/q12.json b/crates/devtools/tests/corpus_snapshots/o11y_bench/q12.json index 1515e4fe8..2a4d99a83 100644 --- a/crates/devtools/tests/corpus_snapshots/o11y_bench/q12.json +++ b/crates/devtools/tests/corpus_snapshots/o11y_bench/q12.json @@ -1,7 +1,6 @@ { "accuracy_guarantees": "enforced", "materialized_views": [], - "tumbling_window_retention_ms": {}, "proven_optimal": true, "query": "service_cache_refresh_lag_seconds{job=\"user-service\"}", "selected": { @@ -344,5 +343,6 @@ "scalar_roots": [] }, "display_name": "Q1 exact" - } + }, + "tumbling_window_retention_ms": {} } diff --git a/crates/devtools/tests/corpus_snapshots/o11y_bench/q19.json b/crates/devtools/tests/corpus_snapshots/o11y_bench/q19.json index c9266c6c7..5c0c496f3 100644 --- a/crates/devtools/tests/corpus_snapshots/o11y_bench/q19.json +++ b/crates/devtools/tests/corpus_snapshots/o11y_bench/q19.json @@ -6242,10 +6242,6 @@ "stage2_node_id": 7 } ], - "tumbling_window_retention_ms": { - "1": 7200000, - "7": 7200000 - }, "proven_optimal": false, "query": "sum(increase(http_requests_total{job=\"payment-service\",status=~\"5..\"}[1h])) / sum(increase(http_requests_total{job=\"payment-service\"}[1h]))", "selected": { @@ -6276,7 +6272,7 @@ "1": { "cost_per_second": 0.849, "cpu_core_seconds_per_second": 2e-6, - "detail": "build exact Increase accumulator in 60 panes of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 6776000 bytes", + "detail": "build exact Increase accumulator in 60 tumbling windows of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 6776000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -6357,7 +6353,7 @@ "7": { "cost_per_second": 0.849, "cpu_core_seconds_per_second": 2e-6, - "detail": "build exact Increase accumulator in 60 panes of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 6776000 bytes", + "detail": "build exact Increase accumulator in 60 tumbling windows of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 6776000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -7253,9 +7249,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 60, - "tumbling_window_width_ms": 60000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 60000 } } } @@ -7739,9 +7735,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 60, - "tumbling_window_width_ms": 60000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 60000 } } } @@ -8054,6 +8050,10 @@ ], "scalar_roots": [] }, - "display_name": "Q1 Sum acc + Increase acc ×60 panes · ingestion time: Increase acc for Q1, 60 × 1m panes, Increase acc for Q1, 60 × 1m panes" + "display_name": "Q1 Sum acc + Increase acc ×60 tumbling windows · ingestion time: Increase acc for Q1, 60 × 1m tumbling windows, Increase acc for Q1, 60 × 1m tumbling windows" + }, + "tumbling_window_retention_ms": { + "1": 7200000, + "7": 7200000 } } diff --git a/crates/devtools/tests/corpus_snapshots/o11y_bench/q20.json b/crates/devtools/tests/corpus_snapshots/o11y_bench/q20.json index aeffc1c16..6b44c713b 100644 --- a/crates/devtools/tests/corpus_snapshots/o11y_bench/q20.json +++ b/crates/devtools/tests/corpus_snapshots/o11y_bench/q20.json @@ -1,7 +1,6 @@ { "accuracy_guarantees": "enforced", "materialized_views": [], - "tumbling_window_retention_ms": {}, "proven_optimal": true, "query": "sum(process_resident_memory_bytes)", "selected": { @@ -493,5 +492,6 @@ "scalar_roots": [] }, "display_name": "Q1 Sum acc" - } + }, + "tumbling_window_retention_ms": {} } diff --git a/crates/devtools/tests/corpus_snapshots/o11y_bench/q22.json b/crates/devtools/tests/corpus_snapshots/o11y_bench/q22.json index d85712efc..b7c91f320 100644 --- a/crates/devtools/tests/corpus_snapshots/o11y_bench/q22.json +++ b/crates/devtools/tests/corpus_snapshots/o11y_bench/q22.json @@ -262,9 +262,6 @@ "stage2_node_id": 1 } ], - "tumbling_window_retention_ms": { - "1": 3900000 - }, "proven_optimal": true, "query": "sum(rate(http_requests_total{job=\"order-service\"}[5m]))", "selected": { @@ -295,7 +292,7 @@ "1": { "cost_per_second": 0.464, "cpu_core_seconds_per_second": 2e-6, - "detail": "build exact Rate accumulator in 5 panes of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 3696000 bytes", + "detail": "build exact Rate accumulator in 5 tumbling windows of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 3696000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -832,9 +829,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 5, - "tumbling_window_width_ms": 60000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 60000 } } } @@ -1089,6 +1086,9 @@ ], "scalar_roots": [] }, - "display_name": "Q1 Sum acc + Rate acc ×5 panes · ingestion time: Rate acc for Q1, 5 × 1m panes" + "display_name": "Q1 Sum acc + Rate acc ×5 tumbling windows · ingestion time: Rate acc for Q1, 5 × 1m tumbling windows" + }, + "tumbling_window_retention_ms": { + "1": 3900000 } } diff --git a/crates/devtools/tests/corpus_snapshots/o11y_bench/q23.json b/crates/devtools/tests/corpus_snapshots/o11y_bench/q23.json index f8da85219..2ad030eed 100644 --- a/crates/devtools/tests/corpus_snapshots/o11y_bench/q23.json +++ b/crates/devtools/tests/corpus_snapshots/o11y_bench/q23.json @@ -192,9 +192,6 @@ "stage2_node_id": 1 } ], - "tumbling_window_retention_ms": { - "1": 3900000 - }, "proven_optimal": true, "query": "sum(rate(http_requests_total{job=~\"user-service|order-service|payment-service\"}[5m]))", "selected": { @@ -225,7 +222,7 @@ "1": { "cost_per_second": 0.464, "cpu_core_seconds_per_second": 2e-6, - "detail": "build exact Rate accumulator in 5 panes of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 3696000 bytes", + "detail": "build exact Rate accumulator in 5 tumbling windows of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 3696000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -762,9 +759,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 5, - "tumbling_window_width_ms": 60000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 60000 } } } @@ -1019,6 +1016,9 @@ ], "scalar_roots": [] }, - "display_name": "Q1 Sum acc + Rate acc ×5 panes · ingestion time: Rate acc for Q1, 5 × 1m panes" + "display_name": "Q1 Sum acc + Rate acc ×5 tumbling windows · ingestion time: Rate acc for Q1, 5 × 1m tumbling windows" + }, + "tumbling_window_retention_ms": { + "1": 3900000 } } diff --git a/crates/devtools/tests/corpus_snapshots/o11y_bench/q25.json b/crates/devtools/tests/corpus_snapshots/o11y_bench/q25.json index 78a62793d..61dd6c49f 100644 --- a/crates/devtools/tests/corpus_snapshots/o11y_bench/q25.json +++ b/crates/devtools/tests/corpus_snapshots/o11y_bench/q25.json @@ -4562,10 +4562,6 @@ "stage2_node_id": 7 } ], - "tumbling_window_retention_ms": { - "1": 7200000, - "7": 7200000 - }, "proven_optimal": false, "query": "sum(rate(http_requests_total{status=~\"5..\",job=~\"user-service|order-service|payment-service\"}[1h])) / sum(rate(http_requests_total{job=~\"user-service|order-service|payment-service\"}[1h]))", "selected": { @@ -4596,7 +4592,7 @@ "1": { "cost_per_second": 0.849, "cpu_core_seconds_per_second": 2e-6, - "detail": "build exact Rate accumulator in 60 panes of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 6776000 bytes", + "detail": "build exact Rate accumulator in 60 tumbling windows of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 6776000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -4677,7 +4673,7 @@ "7": { "cost_per_second": 0.849, "cpu_core_seconds_per_second": 2e-6, - "detail": "build exact Rate accumulator in 60 panes of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 6776000 bytes", + "detail": "build exact Rate accumulator in 60 tumbling windows of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 6776000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -5573,9 +5569,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 60, - "tumbling_window_width_ms": 60000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 60000 } } } @@ -6059,9 +6055,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 60, - "tumbling_window_width_ms": 60000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 60000 } } } @@ -6374,6 +6370,10 @@ ], "scalar_roots": [] }, - "display_name": "Q1 Sum acc + Rate acc ×60 panes · ingestion time: Rate acc for Q1, 60 × 1m panes, Rate acc for Q1, 60 × 1m panes" + "display_name": "Q1 Sum acc + Rate acc ×60 tumbling windows · ingestion time: Rate acc for Q1, 60 × 1m tumbling windows, Rate acc for Q1, 60 × 1m tumbling windows" + }, + "tumbling_window_retention_ms": { + "1": 7200000, + "7": 7200000 } } diff --git a/crates/devtools/tests/corpus_snapshots/o11y_bench/q26.json b/crates/devtools/tests/corpus_snapshots/o11y_bench/q26.json index 12c488c91..e33bf4553 100644 --- a/crates/devtools/tests/corpus_snapshots/o11y_bench/q26.json +++ b/crates/devtools/tests/corpus_snapshots/o11y_bench/q26.json @@ -2282,9 +2282,6 @@ "stage2_node_id": 1 } ], - "tumbling_window_retention_ms": { - "1": 7200000 - }, "proven_optimal": true, "query": "sum(rate(process_cpu_seconds_total[1h]))", "selected": { @@ -2315,7 +2312,7 @@ "1": { "cost_per_second": 0.849, "cpu_core_seconds_per_second": 2e-6, - "detail": "build exact Rate accumulator in 60 panes of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 6776000 bytes", + "detail": "build exact Rate accumulator in 60 tumbling windows of 60000 ms for 1000 groups: 1000 rows x (1 + depth 1); x 1.0000/s; keeps 6776000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -2781,9 +2778,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 60, - "tumbling_window_width_ms": 60000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 60000 } } } @@ -3022,6 +3019,9 @@ ], "scalar_roots": [] }, - "display_name": "Q1 Sum acc + Rate acc ×60 panes · ingestion time: Rate acc for Q1, 60 × 1m panes" + "display_name": "Q1 Sum acc + Rate acc ×60 tumbling windows · ingestion time: Rate acc for Q1, 60 × 1m tumbling windows" + }, + "tumbling_window_retention_ms": { + "1": 7200000 } } diff --git a/crates/devtools/tests/corpus_snapshots/o11y_bench/q27.json b/crates/devtools/tests/corpus_snapshots/o11y_bench/q27.json index 091d09615..e119b2b13 100644 --- a/crates/devtools/tests/corpus_snapshots/o11y_bench/q27.json +++ b/crates/devtools/tests/corpus_snapshots/o11y_bench/q27.json @@ -1,7 +1,6 @@ { "accuracy_guarantees": "enforced", "materialized_views": [], - "tumbling_window_retention_ms": {}, "proven_optimal": true, "query": "sum(up)", "selected": { @@ -493,5 +492,6 @@ "scalar_roots": [] }, "display_name": "Q1 Sum acc" - } + }, + "tumbling_window_retention_ms": {} } diff --git a/crates/devtools/tests/planner_fuzz.rs b/crates/devtools/tests/planner_fuzz.rs index c12e256f5..73df7cc00 100644 --- a/crates/devtools/tests/planner_fuzz.rs +++ b/crates/devtools/tests/planner_fuzz.rs @@ -344,7 +344,7 @@ fn threshold_witness_prices_over_the_result() { )); } -/// (m): a longer window lengthens q's pane chain of exact sums. +/// (m): a longer window lengthens q's tumbling-window chain of exact sums. #[test] fn longer_window_witness_prices_over_a_lengthened_chain() { let q = promql( diff --git a/crates/devtools/tests/sketch_plan_snapshots.rs b/crates/devtools/tests/sketch_plan_snapshots.rs index 7217ee0f2..88759ff63 100644 --- a/crates/devtools/tests/sketch_plan_snapshots.rs +++ b/crates/devtools/tests/sketch_plan_snapshots.rs @@ -2,9 +2,9 @@ //! //! The corpus snapshots are single queries without an accuracy target, so //! their plans are exact. These workloads carry accuracy targets and select -//! sketches (KLL, DDSketch, a CMS heap), share a sketch pane chain between -//! queries or nested windows, answer a ratio of two quantiles from one pane -//! chain, and materialize panes, under the MVP deployment (raw data not +//! sketches (KLL, DDSketch, a CMS heap), share a sketch tumbling-window chain between +//! queries or nested windows, answer a ratio of two quantiles from one tumbling window +//! chain, and materialize tumbling windows, under the MVP deployment (raw data not //! retained) and raw data retained. A change to sketch kinds, parameters, //! sizes, sharing, timing, materialization or cost shows up as a snapshot //! diff. @@ -18,14 +18,14 @@ //! Regenerate with //! `UPDATE_SNAPSHOTS=1 cargo test --release -p asap-devtools --test corpus_snapshots -- --ignored sketch_plans_match_snapshots`. //! -//! The search's seed and pane-width rounds have wall-clock budgets (5 s +//! The search's seed and tumbling-window-width rounds have wall-clock budgets (5 s //! each), so a search that stops at a budget, of time or of evaluations, can //! select a different plan on a slower machine. Only a search that proves its //! plan optimal is independent of speed, so every workload must end with -//! status `optimal` and no pane width left unsearched; any other ending +//! status `optimal` and no tumbling-window width left unsearched; any other ending //! fails instead of being recorded. A workload with a //! `deadline_reached_reason` in the manifest may instead end with status -//! `deadline_reached` (no pane width left unsearched); its snapshot records +//! `deadline_reached` (no tumbling-window width left unsearched); its snapshot records //! that status, so the plan, cost and status must still match. Each //! kept workload gave the same plan in three runs and on one core. //! Workloads whose search stops at a budget (the raw-retained memo examples, @@ -147,17 +147,17 @@ fn sketch_plans_match_snapshots() { continue; } // A `deadline_reached_reason` accepts a search that ended at its - // deadline after every pane width; plan and cost are still compared. + // deadline after every tumbling-window width; plan and cost are still compared. let deadline_accepted = entry.get("deadline_reached_reason").is_some() && search_status(&document) == "deadline_reached" - && document["planning_report"]["pane_widths_pending"] != json!(true); + && document["planning_report"]["tumbling_window_widths_pending"] != json!(true); if !proven_optimal(&document) && !deadline_accepted { failures.push(format!( - "{id}: the plan is not proven optimal (status {}, pane widths \ + "{id}: the plan is not proven optimal (status {}, tumbling-window widths \ pending {}), so a round's budget may have chosen it; drop the \ workload or make it smaller", search_status(&document), - document["planning_report"]["pane_widths_pending"] + document["planning_report"]["tumbling_window_widths_pending"] )); continue; } diff --git a/crates/devtools/tests/sketch_plan_snapshots/fuzz-issue903-raw-retained.json b/crates/devtools/tests/sketch_plan_snapshots/fuzz-issue903-raw-retained.json index 6ef669d60..225a81a8a 100644 --- a/crates/devtools/tests/sketch_plan_snapshots/fuzz-issue903-raw-retained.json +++ b/crates/devtools/tests/sketch_plan_snapshots/fuzz-issue903-raw-retained.json @@ -1,7 +1,6 @@ { "accuracy_guarantees": "enforced", "materialized_views": [], - "tumbling_window_retention_ms": {}, "proven_optimal": true, "queries": [ "quantile_over_time(0.9, data[5m])", @@ -913,5 +912,6 @@ "scalar_roots": [] }, "display_name": "Q1 exact · Q2 Kll · shared Scan (Q1, Q2) · query time, shared: TimeRange" - } + }, + "tumbling_window_retention_ms": {} } diff --git a/crates/devtools/tests/sketch_plan_snapshots/nested-windows-15m-1h-mvp.json b/crates/devtools/tests/sketch_plan_snapshots/nested-windows-15m-1h-mvp.json index d5f6f2441..9b4fb2c61 100644 --- a/crates/devtools/tests/sketch_plan_snapshots/nested-windows-15m-1h-mvp.json +++ b/crates/devtools/tests/sketch_plan_snapshots/nested-windows-15m-1h-mvp.json @@ -2282,9 +2282,6 @@ "stage2_node_id": 1 } ], - "tumbling_window_retention_ms": { - "1": 3600000 - }, "proven_optimal": true, "queries": [ "quantile_over_time(0.9, data[15m])", @@ -2331,7 +2328,7 @@ "1": { "cost_per_second": 3.89542189639e-6, "cpu_core_seconds_per_second": 0.004, - "detail": "build DDSketch in 60 panes of 60000 ms for 10000 groups: 2000000 rows x (1 + depth 1); x 1.0000/s; keeps 3347680000 bytes", + "detail": "build DDSketch in 60 tumbling windows of 60000 ms for 10000 groups: 2000000 rows x (1 + depth 1); x 1.0000/s; keeps 3347680000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -2979,9 +2976,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 60, - "tumbling_window_width_ms": 60000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 60000 } } } @@ -3518,6 +3515,9 @@ ], "scalar_roots": [] }, - "display_name": "Q1 DDSketch ×60 panes · Q2 DDSketch ×60 panes · shared DDSketch ×60 panes (Q1, Q2) · shared Scan (Q1, Q2) · ingestion time: DDSketch for Q1, Q2, 60 × 1m panes" + "display_name": "Q1 DDSketch ×60 tumbling windows · Q2 DDSketch ×60 tumbling windows · shared DDSketch ×60 tumbling windows (Q1, Q2) · shared Scan (Q1, Q2) · ingestion time: DDSketch for Q1, Q2, 60 × 1m tumbling windows" + }, + "tumbling_window_retention_ms": { + "1": 3600000 } } diff --git a/crates/devtools/tests/sketch_plan_snapshots/quantile-grouped-mvp.json b/crates/devtools/tests/sketch_plan_snapshots/quantile-grouped-mvp.json index 91def3992..3baaa111e 100644 --- a/crates/devtools/tests/sketch_plan_snapshots/quantile-grouped-mvp.json +++ b/crates/devtools/tests/sketch_plan_snapshots/quantile-grouped-mvp.json @@ -1142,9 +1142,6 @@ "stage2_node_id": 1 } ], - "tumbling_window_retention_ms": { - "1": 300000 - }, "proven_optimal": true, "queries": [ "avg by (region) (quantile_over_time(0.9, data[5m]))" @@ -1180,7 +1177,7 @@ "1": { "cost_per_second": 1.95903278013e-6, "cpu_core_seconds_per_second": 0.0002, - "detail": "build DDSketch in 30 panes of 10000 ms for 10000 groups: 100000 rows x (1 + depth 1); x 1.0000/s; keeps 1701280000 bytes", + "detail": "build DDSketch in 30 tumbling windows of 10000 ms for 10000 groups: 100000 rows x (1 + depth 1); x 1.0000/s; keeps 1701280000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -2099,9 +2096,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 30, - "tumbling_window_width_ms": 10000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 10000 } } } @@ -3156,6 +3153,9 @@ ], "scalar_roots": [] }, - "display_name": "Q1 Sum acc + DDSketch ×30 panes + Count acc · ingestion time: DDSketch for Q1, 30 × 10s panes" + "display_name": "Q1 Sum acc + DDSketch ×30 tumbling windows + Count acc · ingestion time: DDSketch for Q1, 30 × 10s tumbling windows" + }, + "tumbling_window_retention_ms": { + "1": 300000 } } diff --git a/crates/devtools/tests/sketch_plan_snapshots/quantile-single-mvp.json b/crates/devtools/tests/sketch_plan_snapshots/quantile-single-mvp.json index a73b7542e..b1a914db3 100644 --- a/crates/devtools/tests/sketch_plan_snapshots/quantile-single-mvp.json +++ b/crates/devtools/tests/sketch_plan_snapshots/quantile-single-mvp.json @@ -1142,9 +1142,6 @@ "stage2_node_id": 1 } ], - "tumbling_window_retention_ms": { - "1": 300000 - }, "proven_optimal": false, "queries": [ "quantile_over_time(0.99, data[5m])" @@ -1180,7 +1177,7 @@ "1": { "cost_per_second": 6.32641306719e-7, "cpu_core_seconds_per_second": 0.0002, - "detail": "build Kll in 30 panes of 10000 ms for 10000 groups: 100000 rows x (1 + depth 1); x 1.0000/s; keeps 548080000 bytes", + "detail": "build Kll in 30 tumbling windows of 10000 ms for 10000 groups: 100000 rows x (1 + depth 1); x 1.0000/s; keeps 548080000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -1640,9 +1637,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 30, - "tumbling_window_width_ms": 10000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 10000 } } } @@ -1902,6 +1899,9 @@ ], "scalar_roots": [] }, - "display_name": "Q1 Kll ×30 panes · ingestion time: Kll for Q1, 30 × 10s panes" + "display_name": "Q1 Kll ×30 tumbling windows · ingestion time: Kll for Q1, 30 × 10s tumbling windows" + }, + "tumbling_window_retention_ms": { + "1": 300000 } } diff --git a/crates/devtools/tests/sketch_plan_snapshots/random-mixed-seed-862-long-w01-q3-mvp.json b/crates/devtools/tests/sketch_plan_snapshots/random-mixed-seed-862-long-w01-q3-mvp.json index 812e10e7d..04ea77719 100644 --- a/crates/devtools/tests/sketch_plan_snapshots/random-mixed-seed-862-long-w01-q3-mvp.json +++ b/crates/devtools/tests/sketch_plan_snapshots/random-mixed-seed-862-long-w01-q3-mvp.json @@ -11402,9 +11402,6 @@ "stage2_node_id": 1 } ], - "tumbling_window_retention_ms": { - "1": 300000 - }, "proven_optimal": true, "queries": [ "avg by (region) (quantile_over_time(0.95, data[300s]))" @@ -11440,7 +11437,7 @@ "1": { "cost_per_second": 0.0000190019900264, "cpu_core_seconds_per_second": 0.0002, - "detail": "build DDSketch in 300 panes of 1000 ms for 10000 groups: 100000 rows x (1 + depth 1); x 1.0000/s; keeps 16518880000 bytes", + "detail": "build DDSketch in 300 tumbling windows of 1000 ms for 10000 groups: 100000 rows x (1 + depth 1); x 1.0000/s; keeps 16518880000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -12359,9 +12356,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 300, - "tumbling_window_width_ms": 1000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 1000 } } } @@ -13416,6 +13413,9 @@ ], "scalar_roots": [] }, - "display_name": "Q1 Sum acc + DDSketch ×300 panes + Count acc · ingestion time: DDSketch for Q1, 300 × 1s panes" + "display_name": "Q1 Sum acc + DDSketch ×300 tumbling windows + Count acc · ingestion time: DDSketch for Q1, 300 × 1s tumbling windows" + }, + "tumbling_window_retention_ms": { + "1": 300000 } } diff --git a/crates/devtools/tests/sketch_plan_snapshots/random-mixed-seed-862-w01-q3-q8-mvp.json b/crates/devtools/tests/sketch_plan_snapshots/random-mixed-seed-862-w01-q3-q8-mvp.json index 2e13e936f..f9db2cbc5 100644 --- a/crates/devtools/tests/sketch_plan_snapshots/random-mixed-seed-862-w01-q3-q8-mvp.json +++ b/crates/devtools/tests/sketch_plan_snapshots/random-mixed-seed-862-w01-q3-q8-mvp.json @@ -5322,10 +5322,6 @@ "stage2_node_id": 13 } ], - "tumbling_window_retention_ms": { - "1": 100000, - "13": 1200000 - }, "proven_optimal": true, "queries": [ "avg by (region) (quantile_over_time(0.95, data[100s]))", @@ -5370,7 +5366,7 @@ "1": { "cost_per_second": 6.3775772514e-6, "cpu_core_seconds_per_second": 0.0002, - "detail": "build DDSketch in 100 panes of 1000 ms for 10000 groups: 100000 rows x (1 + depth 1); x 1.0000/s; keeps 5542880000 bytes", + "detail": "build DDSketch in 100 tumbling windows of 1000 ms for 10000 groups: 100000 rows x (1 + depth 1); x 1.0000/s; keeps 5542880000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -5406,7 +5402,7 @@ "13": { "cost_per_second": 2.86570103968e-8, "cpu_core_seconds_per_second": 0.0002, - "detail": "build exact Rate accumulator in 40 panes of 30000 ms for 10000 groups: 100000 rows x (1 + depth 1); x 1.0000/s; keeps 22960000 bytes", + "detail": "build exact Rate accumulator in 40 tumbling windows of 30000 ms for 10000 groups: 100000 rows x (1 + depth 1); x 1.0000/s; keeps 22960000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -6692,9 +6688,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 100, - "tumbling_window_width_ms": 1000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 1000 } } } @@ -7947,9 +7943,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 40, - "tumbling_window_width_ms": 30000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 30000 } } } @@ -8359,6 +8355,10 @@ ], "scalar_roots": [] }, - "display_name": "Q1 Sum acc + DDSketch ×100 panes + Count acc · Q2 Rate acc ×40 panes · ingestion time: DDSketch for Q1, 100 × 1s panes, Rate acc for Q2, 40 × 30s panes" + "display_name": "Q1 Sum acc + DDSketch ×100 tumbling windows + Count acc · Q2 Rate acc ×40 tumbling windows · ingestion time: DDSketch for Q1, 100 × 1s tumbling windows, Rate acc for Q2, 40 × 30s tumbling windows" + }, + "tumbling_window_retention_ms": { + "1": 100000, + "13": 1200000 } } diff --git a/crates/devtools/tests/sketch_plan_snapshots/random-mixed-seed-862-w02-q2-q7-mvp.json b/crates/devtools/tests/sketch_plan_snapshots/random-mixed-seed-862-w02-q2-q7-mvp.json index f4b488492..6de034914 100644 --- a/crates/devtools/tests/sketch_plan_snapshots/random-mixed-seed-862-w02-q2-q7-mvp.json +++ b/crates/devtools/tests/sketch_plan_snapshots/random-mixed-seed-862-w02-q2-q7-mvp.json @@ -6082,10 +6082,6 @@ "stage2_node_id": 13 } ], - "tumbling_window_retention_ms": { - "1": 100000, - "13": 300000 - }, "proven_optimal": true, "queries": [ "avg by (region) (quantile_over_time(0.75, data[100s]))", @@ -6130,7 +6126,7 @@ "1": { "cost_per_second": 6.3775772514e-6, "cpu_core_seconds_per_second": 0.0002, - "detail": "build DDSketch in 100 panes of 1000 ms for 10000 groups: 100000 rows x (1 + depth 1); x 1.0000/s; keeps 5542880000 bytes", + "detail": "build DDSketch in 100 tumbling windows of 1000 ms for 10000 groups: 100000 rows x (1 + depth 1); x 1.0000/s; keeps 5542880000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -6166,7 +6162,7 @@ "13": { "cost_per_second": 4.15390642488e-8, "cpu_core_seconds_per_second": 0.0002, - "detail": "build exact Increase accumulator in 60 panes of 5000 ms for 10000 groups: 100000 rows x (1 + depth 1); x 1.0000/s; keeps 34160000 bytes", + "detail": "build exact Increase accumulator in 60 tumbling windows of 5000 ms for 10000 groups: 100000 rows x (1 + depth 1); x 1.0000/s; keeps 34160000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -7452,9 +7448,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 100, - "tumbling_window_width_ms": 1000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 1000 } } } @@ -8707,9 +8703,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 60, - "tumbling_window_width_ms": 5000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 5000 } } } @@ -9119,6 +9115,10 @@ ], "scalar_roots": [] }, - "display_name": "Q1 Sum acc + DDSketch ×100 panes + Count acc · Q2 Increase acc ×60 panes · ingestion time: DDSketch for Q1, 100 × 1s panes, Increase acc for Q2, 60 × 5s panes" + "display_name": "Q1 Sum acc + DDSketch ×100 tumbling windows + Count acc · Q2 Increase acc ×60 tumbling windows · ingestion time: DDSketch for Q1, 100 × 1s tumbling windows, Increase acc for Q2, 60 × 5s tumbling windows" + }, + "tumbling_window_retention_ms": { + "1": 100000, + "13": 300000 } } diff --git a/crates/devtools/tests/sketch_plan_snapshots/ratio-of-quantiles-1h-mvp.json b/crates/devtools/tests/sketch_plan_snapshots/ratio-of-quantiles-1h-mvp.json index ab9bca0ec..0d170be65 100644 --- a/crates/devtools/tests/sketch_plan_snapshots/ratio-of-quantiles-1h-mvp.json +++ b/crates/devtools/tests/sketch_plan_snapshots/ratio-of-quantiles-1h-mvp.json @@ -2282,9 +2282,6 @@ "stage2_node_id": 1 } ], - "tumbling_window_retention_ms": { - "1": 3600000 - }, "proven_optimal": true, "queries": [ "quantile_over_time(0.9, data[1h]) / quantile_over_time(0.5, data[1h])" @@ -2320,7 +2317,7 @@ "1": { "cost_per_second": 6.38754722874e-6, "cpu_core_seconds_per_second": 0.004, - "detail": "build DDSketch in 60 panes of 60000 ms for 10000 groups: 2000000 rows x (1 + depth 1); x 1.0000/s; keeps 5514400000 bytes", + "detail": "build DDSketch in 60 tumbling windows of 60000 ms for 10000 groups: 2000000 rows x (1 + depth 1); x 1.0000/s; keeps 5514400000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -3067,9 +3064,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 60, - "tumbling_window_width_ms": 60000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 60000 } } } @@ -3666,6 +3663,9 @@ ], "scalar_roots": [] }, - "display_name": "Q1 DDSketch ×60 panes · ingestion time: DDSketch for Q1, 60 × 1m panes" + "display_name": "Q1 DDSketch ×60 tumbling windows · ingestion time: DDSketch for Q1, 60 × 1m tumbling windows" + }, + "tumbling_window_retention_ms": { + "1": 3600000 } } diff --git a/crates/devtools/tests/sketch_plan_snapshots/shared-quantiles-1h-mvp.json b/crates/devtools/tests/sketch_plan_snapshots/shared-quantiles-1h-mvp.json index 5947593fe..0c2de0f45 100644 --- a/crates/devtools/tests/sketch_plan_snapshots/shared-quantiles-1h-mvp.json +++ b/crates/devtools/tests/sketch_plan_snapshots/shared-quantiles-1h-mvp.json @@ -2282,9 +2282,6 @@ "stage2_node_id": 1 } ], - "tumbling_window_retention_ms": { - "1": 3600000 - }, "proven_optimal": false, "queries": [ "quantile_over_time(0.5, data[1h])", @@ -2331,7 +2328,7 @@ "1": { "cost_per_second": 3.89542189639e-6, "cpu_core_seconds_per_second": 0.004, - "detail": "build DDSketch in 60 panes of 60000 ms for 10000 groups: 2000000 rows x (1 + depth 1); x 1.0000/s; keeps 3347680000 bytes", + "detail": "build DDSketch in 60 tumbling windows of 60000 ms for 10000 groups: 2000000 rows x (1 + depth 1); x 1.0000/s; keeps 3347680000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -2979,9 +2976,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 60, - "tumbling_window_width_ms": 60000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 60000 } } } @@ -3517,6 +3514,9 @@ ], "scalar_roots": [] }, - "display_name": "Q1 DDSketch ×60 panes · Q2 DDSketch ×60 panes · shared DDSketch ×60 panes (Q1, Q2) · shared Scan (Q1, Q2) · ingestion time: DDSketch for Q1, Q2, 60 × 1m panes" + "display_name": "Q1 DDSketch ×60 tumbling windows · Q2 DDSketch ×60 tumbling windows · shared DDSketch ×60 tumbling windows (Q1, Q2) · shared Scan (Q1, Q2) · ingestion time: DDSketch for Q1, Q2, 60 × 1m tumbling windows" + }, + "tumbling_window_retention_ms": { + "1": 3600000 } } diff --git a/crates/devtools/tests/sketch_plan_snapshots/topk-rate-guarantees-ignored-mvp.json b/crates/devtools/tests/sketch_plan_snapshots/topk-rate-guarantees-ignored-mvp.json index e2def6a64..b49a890c2 100644 --- a/crates/devtools/tests/sketch_plan_snapshots/topk-rate-guarantees-ignored-mvp.json +++ b/crates/devtools/tests/sketch_plan_snapshots/topk-rate-guarantees-ignored-mvp.json @@ -1142,9 +1142,6 @@ "stage2_node_id": 1 } ], - "tumbling_window_retention_ms": { - "1": 300000 - }, "proven_optimal": true, "queries": [ "topk(5, sum by (job) (rate(data_total[5m])))" @@ -1180,7 +1177,7 @@ "1": { "cost_per_second": 2.22159834707e-8, "cpu_core_seconds_per_second": 0.0002, - "detail": "build exact Rate accumulator in 30 panes of 10000 ms for 10000 groups: 100000 rows x (1 + depth 1); x 1.0000/s; keeps 17360000 bytes", + "detail": "build exact Rate accumulator in 30 tumbling windows of 10000 ms for 10000 groups: 100000 rows x (1 + depth 1); x 1.0000/s; keeps 17360000 bytes", "multiplier": 1, "operator": "SummaryTumblingWindowChain", "price_source": "analytical", @@ -1758,9 +1755,9 @@ "kind": "unknown_or_signed" } }, + "reduction": "PerEntity", "tumbling_window_count": 30, - "tumbling_window_width_ms": 10000, - "reduction": "PerEntity" + "tumbling_window_width_ms": 10000 } } } @@ -2096,6 +2093,9 @@ ], "scalar_roots": [] }, - "display_name": "Q1 CMS+heap + Rate acc ×30 panes · ingestion time: Rate acc for Q1, 30 × 10s panes" + "display_name": "Q1 CMS+heap + Rate acc ×30 tumbling windows · ingestion time: Rate acc for Q1, 30 × 10s tumbling windows" + }, + "tumbling_window_retention_ms": { + "1": 300000 } } diff --git a/crates/devtools/tests/stage_pipeline.rs b/crates/devtools/tests/stage_pipeline.rs index 54669bcff..fd61757ba 100644 --- a/crates/devtools/tests/stage_pipeline.rs +++ b/crates/devtools/tests/stage_pipeline.rs @@ -177,7 +177,7 @@ fn example1_document_preserves_workload_and_validates_archived_plans() { #[test] fn display_cap_is_recorded_and_keeps_the_selection() { // Instant quantiles provide several implementations without expanding a - // pane-width domain. Exhaustive mode ensures the display cap is exercised. + // tumbling-window-width domain. Exhaustive mode ensures the display cap is exercised. let document = document_of(&[ "--promql", "quantile(0.5, data)", @@ -302,7 +302,7 @@ fn example3a_archive_and_viewer_select_consistently() { /// selects a plan. #[test] fn example2_plans_the_sql_workload() { - // The default search: its SQL windows get tumbling panes, so an + // The default search: its SQL windows get tumbling windows, so an // exhaustive run has over 10^4 Stage 2 candidates. The integration test // `stage1_logical_asap_optimization_sql_adds_one_univmon_for_all_three` // checks the shared UnivMon candidate. @@ -871,9 +871,9 @@ fn cost_model_flag_chooses_how_candidates_are_compared() { /// at AWS Fargate's us-east-1 prices, in US dollars per second: the /// whole-window KLL (P2) is still selected, at $2.60278e-8/s (its 2.1 /// CPU-ms/s at $1.1244e-8 per CPU-ms, and its states held while the query -/// runs), and the panes +/// runs), and the tumbling windows /// maintained at ingestion time (P3-m1) cost about $6.0238e-6/s, almost all -/// for their 5.232 GB kept in memory (six panes of 1,000,000 series, each a +/// for their 5.232 GB kept in memory (six tumbling windows of 1,000,000 series, each a /// KLL of 872 bytes: its fixed 744 and an item array grown for its 4 /// samples). A costlier candidate's reason shows /// such small costs in scientific notation. @@ -898,7 +898,7 @@ fn aws_fargate_preset_prices_example_4_in_dollars() { /// without the flag, every summary supported, raw data retained, no /// query-time result retained). For Pattern B with KLL only and query-time /// results retained: no DDSketch candidate is offered, and Stage 1's -/// `pruned_candidates` says why; the KLL panes gain B3, built at query time and +/// `pruned_candidates` says why; the KLL tumbling windows gain B3, built at query time and /// kept (`P3-m2`, 436.59 cost/s); the whole-window KLL (`P2`) is still /// selected. Without raw data, every query-time scan is invalid and B1 /// (`P3-m1`) is selected when guarantees are enforced. A deployment with an @@ -1038,7 +1038,7 @@ fn logical_candidate_labels_name_each_query() { /// alone: round 1 proves the selected plan optimal within 1e-5, and the /// last round finds nothing below the incumbent. That round's bound (the /// incumbent less the solver's pruning tolerance, scaled by the largest -/// charge, here the query-time top-k panes at about 308 times the +/// charge, here the query-time top-k tumbling windows at about 308 times the /// selected plan) used to replace round 1's, reporting a gap of 3.09e-4. #[test] fn milp_final_bound_keeps_the_bound_an_earlier_round_proved() { @@ -1101,7 +1101,7 @@ fn milp_final_bound_keeps_the_bound_an_earlier_round_proved() { /// (`max_over_time(m[5m])` every minute over 1M series sampled every /// 1.5 s) answered as the last hour at 1-minute steps /// (`range_evaluation`) is priced as 61 evaluations per request: the kept -/// panes (`P2-m2`, 460.04 cost/s) beat rebuilding the window at every step +/// tumbling windows (`P2-m2`, 460.04 cost/s) beat rebuilding the window at every step /// (`P1`, 1,270.52), and the document reports their retention, 1 h + 5 min. /// The workload section shows the range. An invalid range is refused. #[test] @@ -1140,16 +1140,16 @@ fn workload_file_with_a_range_evaluation_is_planned() { ); let selection = &document["stage3_plan_selection"]; let cost = selection["cost"]["cost_per_second"].as_f64().unwrap(); - // The reference: the window merged from panes kept across evaluations, + // The reference: the window merged from tumbling windows kept across evaluations, // priced under the same models. Its absolute cost is pinned in // integration-tests/tests/range_evaluation.rs. - let kept_panes = { + let kept_tumbling_windows = { use asap_accuracy::propagation::recompose_guarantees; use asap_logical_asap_optimization::test_support::{ compose_logical_candidate, generate_per_computation_candidates, }; use asap_logical_asap_optimization::test_support::{ - pane_candidates, window_composition_pass2_candidates, + tumbling_window_candidates, window_composition_pass2_candidates, }; use asap_types::ir::physical_export::PhysicalASAPOperatorPayload; use asap_types::ir::ASAPOp; @@ -1165,8 +1165,8 @@ fn workload_file_with_a_range_evaluation_is_planned() { .unwrap(); let exact: Vec<_> = compose_logical_candidate(&inventory, &[0]).unwrap(); let grids: Vec<_> = entries.iter().map(|e| e.evaluation_grid_ms()).collect(); - let panes = pane_candidates(&exact, &grids); - let paned = window_composition_pass2_candidates(&exact, &panes) + let tumbling_windows = tumbling_window_candidates(&exact, &grids); + let windowed = window_composition_pass2_candidates(&exact, &tumbling_windows) .unwrap() .remove(0) .roots @@ -1176,8 +1176,8 @@ fn workload_file_with_a_range_evaluation_is_planned() { ..Default::default() }; let kept = asap_physical_asap_optimization::optimize_physical_asap( - "kept-panes", - &recompose_guarantees(&paned).unwrap(), + "kept-tumbling-windows", + &recompose_guarantees(&windowed).unwrap(), &entries, &data, deployment.query_time_results_retained, @@ -1193,7 +1193,7 @@ fn workload_file_with_a_range_evaluation_is_planned() { == Materialization::MatAtQueryKeptAcrossEvaluations(Medium::Memory) }) }) - .expect("a plan keeps its panes across evaluations"); + .expect("a plan keeps its tumbling windows across evaluations"); let settings = asap_cost::CostInputs::default(); let priced = asap_devtools::memo_pricing::reference_select( std::slice::from_ref(&kept), @@ -1207,8 +1207,8 @@ fn workload_file_with_a_range_evaluation_is_planned() { priced.costs[&priced.selected].cost_per_second.unwrap() }; assert!( - cost <= kept_panes * (1.0 + 1e-9), - "selected {SELECTED} costs {cost}, more than the kept panes' {kept_panes}" + cost <= kept_tumbling_windows * (1.0 + 1e-9), + "selected {SELECTED} costs {cost}, more than the kept tumbling windows' {kept_tumbling_windows}" ); assert!(selection["tumbling_window_retention_ms"] .as_object() diff --git a/crates/executor/README.md b/crates/executor/README.md index 717d33070..de199a07f 100644 --- a/crates/executor/README.md +++ b/crates/executor/README.md @@ -131,6 +131,6 @@ maintenance-policy admission remain deployment/planning contracts; this compiler does not discover storage or silently change a selected maintenance strategy. -Pane construction and geometry belong to deployment. A deployment refreshes -each pane view once per pane and returns the pane states from its store; the +Tumbling window construction and geometry belong to deployment. A deployment refreshes +each tumbling window view once per tumbling window and returns the tumbling-window states from its store; the query DAG merges and reads them out as computation. diff --git a/crates/executor/src/evaluation.rs b/crates/executor/src/evaluation.rs index 39b86538c..d294ec061 100644 --- a/crates/executor/src/evaluation.rs +++ b/crates/executor/src/evaluation.rs @@ -37,7 +37,7 @@ pub fn insufficient_counter_raw_data_samples( .is_some_and(|state| state.insufficient_counter_raw_data_samples(statistic, None)) } -/// Merge already selected exact panes and read one population. `None` means +/// Merge already selected exact tumbling windows and read one population. `None` means /// the population is absent from the result: a counter with too few samples, /// or an empty MIN/MAX. pub fn exact_evaluation( diff --git a/crates/executor/src/physical_planner/mod.rs b/crates/executor/src/physical_planner/mod.rs index a7eac13a0..56f194fd1 100644 --- a/crates/executor/src/physical_planner/mod.rs +++ b/crates/executor/src/physical_planner/mod.rs @@ -110,7 +110,7 @@ fn compile_internal( deadline: Option, ) -> Result { preflight_depth(dag, deadline)?; - // A pane chain stands for its panes; the deployment binds each pane's + // A tumbling-window chain stands for its tumbling windows; the deployment binds each tumbling window's // raw input, so it runs only once expanded. if let Some(node) = dag.nodes.iter().find(|n| { matches!( @@ -119,8 +119,8 @@ fn compile_internal( ) }) { return Err(invalid(format!( - "node {}: a SummaryTumblingWindowChain runs as its panes; expand it with \ - `asap_types::ir::operator::pane_chain::expand_pane_chains` before compiling", + "node {}: a SummaryTumblingWindowChain runs as its tumbling windows; expand it with \ + `asap_types::ir::operator::tumbling_window_chain::expand_tumbling_window_chains` before compiling", node.id ))); } @@ -189,7 +189,7 @@ fn compile_internal( } } // Each view's and each `Scan`'s window, the DAG indexed once for all: - // a materialized pane chain has a view per pane. + // a materialized tumbling-window chain has a view per tumbling window. let view_windows = dag.view_windows(views.iter().map(|view| view.node_id)); let scan_windows = dag.scan_windows( dag.nodes @@ -496,7 +496,7 @@ fn compile_internal( "per-entity summary requires a resolved raw time range", )); }; - // A tumbling pane reads its window through `TimeShift`s; the + // A tumbling window reads its window through `TimeShift`s; the // deployment supplies the shifted raw rows. let source = below_time_shifts(child, |node| node.non_asap(), Ok)?; let Some(NonASAPOp::Scan { schema, .. }) = source.non_asap() else { @@ -627,7 +627,7 @@ fn compile_internal( .ok_or_else(|| invalid(format!("node {id}: summary merge requires inputs")))?; require_sound_merge(input).map_err(|e| invalid(format!("node {id}: {e}")))?; if output.time_index.is_some() { - // Pane timestamps describe their own builds. The merged + // Tumbling window timestamps describe their own builds. The merged // state covers this evaluation's window, so merge by // series identity and attach the run scope's timestamp, // as a per-series `SummaryAgg` does. @@ -657,7 +657,7 @@ fn compile_internal( continue; } } - // A window over a table or a time series (and a tumbling pane's + // A window over a table or a time series (and a tumbling window's // shift under it) runs natively on the time column. if let Some(operator) = time_window_operator(id, &nodes, &dependencies, &schemas) .map_err(|error| invalid(format!("node {id}: {error}")))? @@ -714,7 +714,7 @@ fn built_from_relation(state: &OperatorNode) -> bool { /// each series' (each label set's) latest row. A `TimeShift` only moves /// the window its `TimeRange` reads (the range above applies its offset), /// so it passes the rows through; a shift is meaningful only under a -/// range, the shape the frontends and Pass 2's tumbling panes build. An +/// range, the shape the frontends and Pass 2's tumbling windows build. An /// `@` anchor has no lowering, so a range or shift over one gets `None` and /// the node fails compilation; an instant window over a table is refused. fn time_window_operator( @@ -830,7 +830,7 @@ pub(crate) fn below_time_shifts<'a, N, C: 'a>( /// lookback)`: it ends `offset` ms before the evaluation time and spans /// `lookback` ms. It is the union of the range windows the evaluation /// reads, each moved back by the time shifts below its range: one range for -/// a single build, back-to-back panes for merged tumbling panes. `None` +/// a single build, back-to-back tumbling windows for merged tumbling windows. `None` /// without a range; an error when the ranges do not form one window. fn counter_window( id: NodeId, @@ -1645,7 +1645,7 @@ mod compilation_deadline_tests { /// `window_count` one-minute windows over one table `Scan`, the `i`-th /// shifted `i` minutes back, each window a root (the shape of a - /// pane-expanded DAG), with the roots' node ids. + /// tumbling-window-expanded DAG), with the roots' node ids. fn shifted_windows_over_one_scan(window_count: i64) -> (PhysicalASAPDAG, Vec) { use planner_types::ir::operator::{Source, TimeShift}; use planner_types::ir::physical_export::compile_physical_asap_workload; @@ -1795,7 +1795,7 @@ pub mod test_support { /// not a view is marked `MatAtIngestion`, and it and every node below it /// run at ingestion time (their outputs and outgoing edges); then every /// view's `retention_ms` is set ([`PhysicalASAPDAG::set_retention`]) - /// with no pane or range-evaluation span. For tests that bind states at + /// with no tumbling window or range-evaluation span. For tests that bind states at /// those nodes, as a deployment binds its views. pub fn views_at( dag: &mut PhysicalASAPDAG, diff --git a/crates/executor/src/summary_kernels/exact.rs b/crates/executor/src/summary_kernels/exact.rs index 8235fb066..ebfe857aa 100644 --- a/crates/executor/src/summary_kernels/exact.rs +++ b/crates/executor/src/summary_kernels/exact.rs @@ -106,7 +106,7 @@ impl ExactAccumulator { /// The earliest sample time of a counter (Rate/Increase) state, over /// all its populations; `None` for an empty or non-counter state. - /// Counter states merge pairwise in time order, so a merge of panes + /// Counter states merge pairwise in time order, so a merge of tumbling windows /// orders them by this first. pub fn earliest_sample_ms(&self) -> Option { std::iter::once(&self.scalar) @@ -456,7 +456,7 @@ mod tests { SummaryFamilyType::ExactAggregate(ExactKind::Sum, ExactParams::Sum) } - // Stored Sum preserves low-order increments across updates, persistence and pane merge. + // Stored Sum preserves low-order increments across updates, persistence and tumbling-window merge. #[test] fn stored_sum_is_compensated() { let mut state = ExactAccumulator::new(sum(), false).unwrap(); diff --git a/crates/executor/src/summary_kernels/factory.rs b/crates/executor/src/summary_kernels/factory.rs index b650f9bd4..e3345397f 100644 --- a/crates/executor/src/summary_kernels/factory.rs +++ b/crates/executor/src/summary_kernels/factory.rs @@ -26,7 +26,7 @@ macro_rules! impl_clone_accumulator_methods { fn into_accumulator(self: Box) -> Box { // Consume the updater and MOVE the accumulator out — no clone. - // Avoids a clone when a pane is evicted at window close. + // Avoids a clone when a tumbling window is evicted at window close. let this = *self; Box::new(this.$acc_field) } @@ -130,7 +130,7 @@ pub trait AccumulatorUpdater: Send { fn take_accumulator(&mut self) -> Box; /// Non-destructive read of the current accumulator state (clone without reset). - /// Used by pane-based sliding windows to read shared panes. + /// Used by tumbling-window-based sliding windows to read shared tumbling windows. fn snapshot_accumulator(&self) -> Box; /// Consume the updater and return its accumulator by move, avoiding the diff --git a/crates/executor/src/summary_kernels/increase.rs b/crates/executor/src/summary_kernels/increase.rs index 12c7df9d8..fcb9e5b6b 100644 --- a/crates/executor/src/summary_kernels/increase.rs +++ b/crates/executor/src/summary_kernels/increase.rs @@ -19,7 +19,7 @@ pub struct IncreaseAccumulator { impl IncreaseAccumulator { /// Merge two counter intervals without a temporary collection. Ties retain - /// the left input, matching the stable ordering of multi-pane merges. + /// the left input, matching the stable ordering of multi-tumbling-window merges. pub(crate) fn merge_pair(left: &Self, right: &Self) -> Self { let (first, second) = if left.starting_timestamp <= right.starting_timestamp { (left, right) diff --git a/crates/executor/src/summary_kernels/state_column_updater.rs b/crates/executor/src/summary_kernels/state_column_updater.rs index e4bce12fc..e059ab25c 100644 --- a/crates/executor/src/summary_kernels/state_column_updater.rs +++ b/crates/executor/src/summary_kernels/state_column_updater.rs @@ -350,7 +350,7 @@ impl StateColumnUpdater { } /// Merge `other`, an updater of the same family whose groups have the - /// same numbers (another shard of one pane), into this one, group by + /// same numbers (another shard of one tumbling window), into this one, group by /// group; a group only `other` has is added. /// /// For a counter (rate, increase), each group's samples in the two diff --git a/crates/executor/src/values/batch.rs b/crates/executor/src/values/batch.rs index 66439ff6b..2bc7e0284 100644 --- a/crates/executor/src/values/batch.rs +++ b/crates/executor/src/values/batch.rs @@ -150,15 +150,15 @@ impl Batch { /// The batch of `columns` in `schema`, one per field, without running /// the DAG: how a runtime installs states it built sample by sample - /// (a sealed [`crate::StateColumnUpdater`]) as a stored pane. Bad input + /// (a sealed [`crate::StateColumnUpdater`]) as a stored tumbling window. Bad input /// when a column's count, layout, length or nullability differs from /// the schema, a dictionary holds a NULL value (a NULL is a NULL key), /// a summary field's states are of another family, or a state index is /// NULL or past its states. /// - /// A stored pane has one row per group: the caller must not repeat a + /// A stored tumbling window has one row per group: the caller must not repeat a /// group's labels (two rows of one series would be merged as two - /// panes of it). This is not checked, as it would cost a pass grouping + /// tumbling windows of it). This is not checked, as it would cost a pass grouping /// every row. pub fn try_from_columns(schema: SchemaRef, columns: Vec) -> Result { validate_schema(&schema)?; @@ -575,7 +575,7 @@ impl Batch { } /// Records that something outside any run (the caller of a plan, a - /// store of panes) holds this batch's states, so runs that read them + /// store of tumbling windows) holds this batch's states, so runs that read them /// are not charged for them again. States a run copies or creates from /// them are new state columns and are charged. pub(crate) fn mark_states_held_outside_run(&self) { @@ -839,7 +839,7 @@ mod tests { ); } - // A runtime can share batches, stored panes included, across threads. + // A runtime can share batches, stored tumbling windows included, across threads. #[test] fn batches_and_state_columns_are_send_and_sync() { fn shareable() {} @@ -999,7 +999,7 @@ mod tests { assert!(nulls.is_valid(0) && nulls.is_null(1)); } - // A filter that keeps few rows keeps only their states: a pane filtered + // A filter that keeps few rows keeps only their states: a tumbling window filtered // to two of ten series holds two states, not ten. #[test] fn a_selective_filter_compacts_its_state_column() { diff --git a/crates/executor/src/values/state_column.rs b/crates/executor/src/values/state_column.rs index 24a4a1a37..d2b8c3ddd 100644 --- a/crates/executor/src/values/state_column.rs +++ b/crates/executor/src/values/state_column.rs @@ -18,7 +18,7 @@ pub struct StateColumn { /// Memory the states hold: each state's own footprint, plus each state /// shared by several states (one Hydra grid) once. state_memory_bytes: usize, - /// Whether something outside the run holds these states (a stored pane + /// Whether something outside the run holds these states (a stored tumbling window /// handed to a plan as a source): immutable and shared, so a run that /// reads them is not charged for them. Set once, never cleared. held_outside_run: AtomicBool, diff --git a/crates/executor/tests/deployment.rs b/crates/executor/tests/deployment.rs index c9e2da35f..70804d9f4 100644 --- a/crates/executor/tests/deployment.rs +++ b/crates/executor/tests/deployment.rs @@ -364,8 +364,8 @@ fn deep_plain_count_sketch_estimates_meet_the_certified_bound() { } } -// Panes merge cell by cell: every pane seeds row `r` the same way, so merging -// two panes equals building the union in one pane. +// Tumbling-window merge cell by cell: every tumbling window seeds row `r` the same way, so merging +// two tumbling windows equals building the union in one tumbling window. #[test] fn plain_count_sketch_merge_equals_union_build() { let family = plain_count_sketch(1_001, 7); diff --git a/crates/executor/tests/exact_state_columns.rs b/crates/executor/tests/exact_state_columns.rs index e676545d6..27cf337b7 100644 --- a/crates/executor/tests/exact_state_columns.rs +++ b/crates/executor/tests/exact_state_columns.rs @@ -35,7 +35,7 @@ fn input() -> SchemaRef { } /// Samples of 3 series in `(start, start + 1000]`, counters with a reset. -fn pane_input(start: i64) -> Batch { +fn tumbling_window_input(start: i64) -> Batch { let rows = (1..=40) .map(|i| { let series = i % 3; @@ -81,8 +81,8 @@ fn run(operator: Operator, inputs: Vec) -> Vec { }) } -// For each exact family: two panes built as numeric records, merged and -// evaluated, equal the same panes read back as kernel objects (all objects, +// For each exact family: two tumbling windows built as numeric records, merged and +// evaluated, equal the same tumbling windows read back as kernel objects (all objects, // or one of each), row for row. #[test] fn exact_records_merge_and_evaluate_as_kernel_objects_do() { @@ -98,10 +98,16 @@ fn exact_records_merge_and_evaluate_as_kernel_objects_do() { let build = || { Operator::per_group_summary_build(input(), family.clone(), 2, Some(1), vec![0]).unwrap() }; - let panes = [0, 1_000].map(|start| run(build(), vec![pane_input(start)]).remove(0)); - let as_objects = - |pane: &Batch| Batch::try_new(pane.schema().clone(), pane.to_rows().to_vec()).unwrap(); - let state_schema = panes[0].schema().clone(); + let tumbling_windows = + [0, 1_000].map(|start| run(build(), vec![tumbling_window_input(start)]).remove(0)); + let as_objects = |tumbling_window: &Batch| { + Batch::try_new( + tumbling_window.schema().clone(), + tumbling_window.to_rows().to_vec(), + ) + .unwrap() + }; + let state_schema = tumbling_windows[0].schema().clone(); let answer = |inputs: Vec| { let merged = run( Operator::summary_merge(state_schema.clone(), 1, vec![0]).unwrap(), @@ -121,10 +127,19 @@ fn exact_records_merge_and_evaluate_as_kernel_objects_do() { assert_eq!(rows.len(), 3, "{kind:?}: one row per series"); format!("{rows:?}") }; - // Newest pane first: counters must merge in time order regardless. - let records = answer(vec![panes[1].clone(), panes[0].clone()]); - let objects = answer(vec![as_objects(&panes[1]), as_objects(&panes[0])]); - let mixed = answer(vec![panes[1].clone(), as_objects(&panes[0])]); + // Newest tumbling window first: counters must merge in time order regardless. + let records = answer(vec![ + tumbling_windows[1].clone(), + tumbling_windows[0].clone(), + ]); + let objects = answer(vec![ + as_objects(&tumbling_windows[1]), + as_objects(&tumbling_windows[0]), + ]); + let mixed = answer(vec![ + tumbling_windows[1].clone(), + as_objects(&tumbling_windows[0]), + ]); assert_eq!(records, objects, "{kind:?}"); assert_eq!(records, mixed, "{kind:?}"); } diff --git a/crates/executor/tests/main.rs b/crates/executor/tests/main.rs index 96a45776c..89abe7da8 100644 --- a/crates/executor/tests/main.rs +++ b/crates/executor/tests/main.rs @@ -23,7 +23,7 @@ mod promql_binary; mod promql_values; mod raw_scan; mod state_column_updater; -mod stored_pane_memory_limit; +mod stored_tumbling_window_memory_limit; mod summary_projection; mod text_extremum_memory; mod thread_safety; diff --git a/crates/executor/tests/physical_semantics.rs b/crates/executor/tests/physical_semantics.rs index 8319bd29c..2831befe1 100644 --- a/crates/executor/tests/physical_semantics.rs +++ b/crates/executor/tests/physical_semantics.rs @@ -646,18 +646,18 @@ fn kll_partial_merge_and_multiple_evaluations_preserve_population() { } } -// KLL panes that each compact merge into a state within the calibrated -// single-build rank error: 60 panes of 1000 values at k = 200. +// KLL tumbling windows that each compact merge into a state within the calibrated +// single-build rank error: 60 tumbling windows of 1000 values at k = 200. #[test] -fn compacted_kll_panes_merge_within_the_calibrated_rank_error() { +fn compacted_kll_tumbling_windows_merge_within_the_calibrated_rank_error() { use planner_types::ir::schema::{SketchAlgorithm, SketchKind, SketchParams, SketchStatistic}; - const PANES: u64 = 60; - const PER_PANE: u64 = 1000; + const TUMBLING_WINDOW_COUNT: u64 = 60; + const ROWS_PER_TUMBLING_WINDOW: u64 = 1000; const K: u32 = 200; // The planner's empirical 99% KLL rank error, `2.296 / k^0.9723` // (`asap_logical_asap_optimization`'s `kll_rank_error_99`). let bound = 2.296 / f64::from(K).powf(0.9723); - let n = (PANES * PER_PANE) as f64; + let n = (TUMBLING_WINDOW_COUNT * ROWS_PER_TUMBLING_WINDOW) as f64; let input = schema(&[("v", DataType::Float64, false)]); let family = FieldDataType::Sketch( SketchKind::new( @@ -674,14 +674,18 @@ fn compacted_kll_panes_merge_within_the_calibrated_rank_error() { }; let state = build().schema(); let mut dag = PhysicalDAG::default(); - for pane in 0..PANES { - // Pane `p` holds `[p, p + 1) * 1000`, as tumbling panes of a - // rising value would, so a lost pane shifts every rank. - let rows = (0..PER_PANE) - .map(|j| vec![Value::Float64((pane * PER_PANE + j) as f64)]) + for tumbling_window in 0..TUMBLING_WINDOW_COUNT { + // Tumbling window `p` holds `[p, p + 1) * 1000`, as tumbling windows of a + // rising value would, so a lost tumbling window shifts every rank. + let rows = (0..ROWS_PER_TUMBLING_WINDOW) + .map(|j| { + vec![Value::Float64( + (tumbling_window * ROWS_PER_TUMBLING_WINDOW + j) as f64, + )] + }) .collect(); dag.add( - pane, + tumbling_window, vec![], Operator::source( input.clone(), @@ -690,13 +694,18 @@ fn compacted_kll_panes_merge_within_the_calibrated_rank_error() { .unwrap(), ) .unwrap(); - dag.add(PANES + pane, vec![pane], build()).unwrap(); + dag.add( + TUMBLING_WINDOW_COUNT + tumbling_window, + vec![tumbling_window], + build(), + ) + .unwrap(); } - let union = 2 * PANES; + let union = 2 * TUMBLING_WINDOW_COUNT; dag.add( union, - (PANES..2 * PANES).collect(), - Operator::union(state.clone(), PANES as usize).unwrap(), + (TUMBLING_WINDOW_COUNT..2 * TUMBLING_WINDOW_COUNT).collect(), + Operator::union(state.clone(), TUMBLING_WINDOW_COUNT as usize).unwrap(), ) .unwrap(); dag.add( @@ -732,11 +741,11 @@ fn compacted_kll_panes_merge_within_the_calibrated_rank_error() { } } -// Counter panes merged in any input order (here A, C, B) give the increase -// of one build over the whole window, including the jumps between panes +// Counter tumbling windows merged in any input order (here A, C, B) give the increase +// of one build over the whole window, including the jumps between tumbling windows // and a counter reset inside B. #[test] -fn counter_panes_merge_in_any_order() { +fn counter_tumbling_windows_merge_in_any_order() { use asap_executor::summary_kernels::exact::ExactAccumulator; use planner_types::ir::schema::{ExactKind, ExactParams}; let input = schema(&[ @@ -772,7 +781,7 @@ fn counter_panes_merge_in_any_order() { .unwrap() }; let mut dag = PhysicalDAG::default(); - // Panes A = [0, 4), B = [4, 8), C = [8, 12), added as A, C, B. + // Tumbling windows A = [0, 4), B = [4, 8), C = [8, 12), added as A, C, B. for (id, range) in [(0, 0..4), (1, 8..12), (2, 4..8), (3, 0..12)] { let batch = Batch::try_new(input.clone(), samples(range)).unwrap(); dag.add( diff --git a/crates/executor/tests/precompute_population.rs b/crates/executor/tests/precompute_population.rs index 265b1d55f..81e9b58cb 100644 --- a/crates/executor/tests/precompute_population.rs +++ b/crates/executor/tests/precompute_population.rs @@ -244,7 +244,7 @@ fn sum_state(value: f64) -> Arc { Arc::new(state) } -// Only an explicit merge may collapse distinct pane updates before finalization. +// Only an explicit merge may collapse distinct tumbling window updates before finalization. #[test] fn precompute_merge_requires_a_family_with_a_sound_merge() { // IRate reads the window's last two samples, so the family-capability @@ -258,7 +258,7 @@ fn precompute_merge_requires_a_family_with_a_sound_merge() { } #[test] -fn explicit_merge_changes_pane_cardinality() { +fn explicit_merge_changes_tumbling_window_cardinality() { let family = FieldDataType::ExactAggregate(ExactKind::Sum, ExactParams::Sum); for (merge, expected) in [(false, vec![2., 7.]), (true, vec![9.])] { let program = state_dag(family.clone(), None, merge); diff --git a/crates/executor/tests/promql_values.rs b/crates/executor/tests/promql_values.rs index 50160251f..0585c4198 100644 --- a/crates/executor/tests/promql_values.rs +++ b/crates/executor/tests/promql_values.rs @@ -195,7 +195,7 @@ fn exact_state_schema(family: planner_types::ir::schema::FieldDataType) -> Schem Arc::new(schema) } -/// Merge stored exact panes per series and finalize them; a counter (`Rate`, +/// Merge stored exact tumbling windows per series and finalize them; a counter (`Rate`, /// `Increase`) over the `lookback_ms` window ending at the evaluation time. fn compile_exact_evaluation( family: planner_types::ir::schema::FieldDataType, @@ -519,10 +519,10 @@ fn scalar_broadcast_rejects_colliding_result_labels_after_recovery() { ); } -// Persisted exact evaluation graphs, rather than the storage adapter, merge panes, +// Persisted exact evaluation graphs, rather than the storage adapter, merge tumbling windows, // finalize each population, and preserve the requested metric-name semantics. #[test] -fn exact_state_evaluations_recover_and_finalize_panes() { +fn exact_state_evaluations_recover_and_finalize_tumbling_windows() { use asap_executor::factory::create_planner_accumulator; use planner_types::ir::schema::*; diff --git a/crates/executor/tests/raw_scan.rs b/crates/executor/tests/raw_scan.rs index 952c477e2..5cdafc837 100644 --- a/crates/executor/tests/raw_scan.rs +++ b/crates/executor/tests/raw_scan.rs @@ -915,7 +915,7 @@ impl DataStore for RecordingViewStore { } } -// A materialized view slot (the count over the range, as an expanded pane +// A materialized view slot (the count over the range, as an expanded tumbling window // over its range) is asked for its window, `(T - 5m, T]` or // `(T - 15m, T - 10m]` under a 10-minute shift, and its state is the // answer's. diff --git a/crates/executor/tests/stored_pane_memory_limit.rs b/crates/executor/tests/stored_tumbling_window_memory_limit.rs similarity index 80% rename from crates/executor/tests/stored_pane_memory_limit.rs rename to crates/executor/tests/stored_tumbling_window_memory_limit.rs index 81df883b5..350e66212 100644 --- a/crates/executor/tests/stored_pane_memory_limit.rs +++ b/crates/executor/tests/stored_tumbling_window_memory_limit.rs @@ -1,4 +1,4 @@ -//! Stored panes are immutable states the store holds and shares: a query +//! Stored tumbling windows are immutable states the store holds and shares: a query //! that reads them is not charged for those states again, only for what it //! allocates. A merge over them still charges the states it creates. use asap_executor::arrow::array::{DictionaryArray, UInt32Array}; @@ -15,18 +15,18 @@ use futures::{executor::block_on, StreamExt}; use std::sync::Arc; const SERIES: u32 = 1_000; -const PANES: usize = 12; +const TUMBLING_WINDOW_COUNT: usize = 12; /// Samples per state: past 1,024 a k = 200 KLL holds its whole item buffer. const SAMPLES: u32 = 1_100; -/// One pane of `SERIES` KLL states (k = 200, about 11 kB charged each, so -/// about 11 MB per pane), keyed by series. -fn pane() -> (Arc, Batch) { - pane_of(SERIES) +/// One tumbling window of `SERIES` KLL states (k = 200, about 11 kB charged each, so +/// about 11 MB per tumbling window), keyed by series. +fn tumbling_window() -> (Arc, Batch) { + tumbling_window_of(SERIES) } -/// One pane of `series` KLL states (k = 200), keyed by series. -fn pane_of(series_count: u32) -> (Arc, Batch) { +/// One tumbling window of `series` KLL states (k = 200), keyed by series. +fn tumbling_window_of(series_count: u32) -> (Arc, Batch) { let family = FieldDataType::Sketch( SketchKind::new( SketchAlgorithm::Kll, @@ -83,14 +83,14 @@ fn pane_of(series_count: u32) -> (Arc, Batch) { (schema, batch) } -/// Merges `PANES` copies of one stored pane per series under `max_bytes`. -fn merge_stored_panes(max_bytes: usize) -> Result { - let (schema, pane) = pane(); +/// Merges `TUMBLING_WINDOW_COUNT` copies of one stored tumbling window per series under `max_bytes`. +fn merge_stored_tumbling_windows(max_bytes: usize) -> Result { + let (schema, tumbling_window) = tumbling_window(); let mut dag = PhysicalDAG::default(); dag.add( 0, vec![], - Operator::source(schema.clone(), vec![pane; PANES]).unwrap(), + Operator::source(schema.clone(), vec![tumbling_window; TUMBLING_WINDOW_COUNT]).unwrap(), ) .unwrap(); dag.add( @@ -119,36 +119,39 @@ fn merge_stored_panes(max_bytes: usize) -> Result { }) } -// Twelve stored panes hold about 130 MB of states, which charging them per +// Twelve stored tumbling windows hold about 130 MB of states, which charging them per // read would put far past a 32 MiB limit. The merge allocates one state per // series (about 11 MB), so it fits. #[test] -fn reading_stored_panes_does_not_charge_their_states() { - assert_eq!(merge_stored_panes(32 << 20).unwrap(), SERIES as usize); +fn reading_stored_tumbling_windows_does_not_charge_their_states() { + assert_eq!( + merge_stored_tumbling_windows(32 << 20).unwrap(), + SERIES as usize + ); } // The merged states themselves (about 11 MB) do not fit in 4 MiB. #[test] fn a_merge_that_allocates_past_the_limit_still_fails() { assert!(matches!( - merge_stored_panes(4 << 20), + merge_stored_tumbling_windows(4 << 20), Err(Error::MemoryLimit) )); } -// A Limit keeping 49% of a stored pane of 2,000 KLLs (about 21 MB of +// A Limit keeping 49% of a stored tumbling window of 2,000 KLLs (about 21 MB of // states) compacts its state column, and the compacted column shares the // stored sketches by pointer: under an 8 MiB limit it is charged for the // pointers, not the sketches. Keeping 51% (no compaction) fits too. #[test] -fn compacting_a_stored_pane_does_not_charge_its_shared_sketches() { +fn compacting_a_stored_tumbling_window_does_not_charge_its_shared_sketches() { for kept in [980, 1_020] { - let (schema, pane) = pane_of(2_000); + let (schema, tumbling_window) = tumbling_window_of(2_000); let mut dag = PhysicalDAG::default(); dag.add( 0, vec![], - Operator::source(schema.clone(), vec![pane]).unwrap(), + Operator::source(schema.clone(), vec![tumbling_window]).unwrap(), ) .unwrap(); dag.add( @@ -179,14 +182,14 @@ fn compacting_a_stored_pane_does_not_charge_its_shared_sketches() { } } -// topk over several panes: a Sort concatenates two stored panes of 1,000 +// topk over several tumbling windows: a Sort concatenates two stored tumbling windows of 1,000 // KLLs (about 21 MB of states), then a Limit keeps 49% and compacts the // concatenated column. Both derivations share the stored sketches by // pointer, so the run fits in 8 MiB. #[test] -fn sorting_then_limiting_stored_panes_does_not_charge_their_sketches() { - let (schema, first) = pane_of(SERIES); - let (_, second) = pane_of(SERIES); +fn sorting_then_limiting_stored_tumbling_windows_does_not_charge_their_sketches() { + let (schema, first) = tumbling_window_of(SERIES); + let (_, second) = tumbling_window_of(SERIES); let mut dag = PhysicalDAG::default(); dag.add( 0, @@ -228,17 +231,17 @@ fn sorting_then_limiting_stored_panes_does_not_charge_their_sketches() { assert_eq!(rows.unwrap(), 980); } -// A merge over one stored pane of 2,000 KLLs (about 21 MB of states), one +// A merge over one stored tumbling window of 2,000 KLLs (about 21 MB of states), one // state per series: every state passes through unchanged, shared by // pointer, so the merge fits in 8 MiB. #[test] fn a_merge_passing_stored_states_through_does_not_charge_them() { - let (schema, pane) = pane_of(2_000); + let (schema, tumbling_window) = tumbling_window_of(2_000); let mut dag = PhysicalDAG::default(); dag.add( 0, vec![], - Operator::source(schema.clone(), vec![pane]).unwrap(), + Operator::source(schema.clone(), vec![tumbling_window]).unwrap(), ) .unwrap(); dag.add( @@ -268,12 +271,12 @@ fn a_merge_passing_stored_states_through_does_not_charge_them() { assert_eq!(rows.unwrap(), 2_000); } -/// A deployment's own data store (not `InMemoryDataStore`): returns `PANES` -/// copies of one stored pane for every materialized view read. -struct StoredPanes { - pane: Batch, +/// A deployment's own data store (not `InMemoryDataStore`): returns `TUMBLING_WINDOW_COUNT` +/// copies of one stored tumbling window for every materialized view read. +struct StoredTumblingWindows { + tumbling_window: Batch, } -impl asap_executor::sources::DataStore for StoredPanes { +impl asap_executor::sources::DataStore for StoredTumblingWindows { fn read_raw_data( &self, source: &asap_executor::planner::ir::operator::Source, @@ -288,14 +291,18 @@ impl asap_executor::sources::DataStore for StoredPanes { _: &asap_executor::sources::MaterializedViewRequest, _: RunContext, ) -> Result, Error> { - Ok(futures::stream::iter(vec![Ok(self.pane.clone()); PANES]).boxed_local()) + Ok(futures::stream::iter(vec![ + Ok(self.tumbling_window.clone()); + TUMBLING_WINDOW_COUNT + ]) + .boxed_local()) } } -/// `SummaryMerge` (node 1) over the materialized view of a pane (node 0): -/// the pane's KLLs per series, built over a minute of raw samples of +/// `SummaryMerge` (node 1) over the materialized view of a tumbling window (node 0): +/// the tumbling window's KLLs per series, built over a minute of raw samples of /// `latency` (`Scan` node 2, `TimeRange` node 3). -fn merge_over_a_stored_pane( +fn merge_over_a_stored_tumbling_window( schema: &Schema, ) -> asap_executor::planner::ir::physical_export::PhysicalASAPDAG { use asap_executor::planner::ir::operator::{GroupKeys, Reduction, Source}; @@ -395,18 +402,18 @@ fn merge_over_a_stored_pane( } // A materialized view slot's states are the store's whatever store returns them: the -// merge over twelve stored panes from a deployment's own store fits the +// merge over twelve stored tumbling windows from a deployment's own store fits the // 32 MiB limit, as it does over `Operator::source`. #[test] fn a_materialized_view_providers_states_are_not_charged_to_the_run() { use asap_executor::physical_planner::{compile, test_support::views_at}; - let (schema, pane) = pane(); - let mut dag = merge_over_a_stored_pane(&schema); + let (schema, tumbling_window) = tumbling_window(); + let mut dag = merge_over_a_stored_tumbling_window(&schema); let views = views_at(&mut dag, &[0]).unwrap(); let compiled = compile(&dag, &views, &[1]).unwrap(); assert_eq!(compiled.operator_name(0), Some("MaterializedView")); let dag = compiled - .instantiate(Arc::new(StoredPanes { pane })) + .instantiate(Arc::new(StoredTumblingWindows { tumbling_window })) .unwrap(); let context = RunContext::new( Scope::Query { diff --git a/crates/executor/tests/thread_safety.rs b/crates/executor/tests/thread_safety.rs index 6bac1277d..1ddf3d4fd 100644 --- a/crates/executor/tests/thread_safety.rs +++ b/crates/executor/tests/thread_safety.rs @@ -20,6 +20,6 @@ fn the_compiled_plan_and_its_data_stores_are_send_and_sync() { shareable::(); shareable::(); shareable::(); - // An open pane's updater moves to the worker that ingests its shard. + // An open tumbling window's updater moves to the worker that ingests its shard. movable::(); } diff --git a/crates/executor/tests/time_series_window.rs b/crates/executor/tests/time_series_window.rs index 4e8adee9d..792a2881b 100644 --- a/crates/executor/tests/time_series_window.rs +++ b/crates/executor/tests/time_series_window.rs @@ -251,7 +251,7 @@ fn an_instant_window_whose_latest_sample_is_stale_has_no_row() { } // In an ingestion run a window ends at the run's `window_end_ms`, whatever -// the shifts below it: the run builds the pane the shifts select. +// the shifts below it: the run builds the tumbling window the shifts select. #[test] fn an_ingestion_run_builds_the_window_ending_at_its_end() { let dag = window_over(1_000, TimeRangeKind::Range, 5_000); diff --git a/crates/frontend-sql/src/sql/mod.rs b/crates/frontend-sql/src/sql/mod.rs index 935e99763..819941f7a 100644 --- a/crates/frontend-sql/src/sql/mod.rs +++ b/crates/frontend-sql/src/sql/mod.rs @@ -417,7 +417,7 @@ impl<'a> SqlLowerer<'a> { // A trailing window on the table's time column is the query's // lookback: a range `TimeRange` over the `Scan`, as PromQL's `m[w]`. // Only when no other conjunct reads `now()`: such a condition would - // be evaluated per pane and at ingestion time once the window is a + // be evaluated per tumbling window and at ingestion time once the window is a // `TimeRange`, and would select other rows there. let other_reads_now = |window: usize| { residual diff --git a/crates/frontend-sql/tests/sql_lowering.rs b/crates/frontend-sql/tests/sql_lowering.rs index 38ed8af0a..fcb2b731d 100644 --- a/crates/frontend-sql/tests/sql_lowering.rs +++ b/crates/frontend-sql/tests/sql_lowering.rs @@ -3175,7 +3175,7 @@ fn trailing_window(node: &OperatorNode) -> Option<(std::time::Duration, &Operato // A trailing window on the table's time column is the query's lookback, the // same `TimeRange` a PromQL range selector lowers to, so Pass 2 can build it -// in panes and Stage 2 at ingestion time. Other conjuncts stay on the `Scan`. +// in tumbling windows and Stage 2 at ingestion time. Other conjuncts stay on the `Scan`. #[tokio::test] async fn trailing_window_on_the_time_column_lowers_to_a_time_range() { for (sql, window_ms) in [ @@ -3218,7 +3218,7 @@ async fn trailing_window_on_the_time_column_lowers_to_a_time_range() { // predicates: an upper bound alone, a month-long (variable) interval, a // window under `OR`, a window on another column, two windows, and a window on // a table without a time index. A window next to another condition on -// `now()` stays one too: that condition would read other rows in a pane or +// `now()` stays one too: that condition would read other rows in a tumbling window or // at ingestion time. #[tokio::test] async fn other_time_conditions_stay_scan_predicates() { diff --git a/crates/integration-tests/src/lib.rs b/crates/integration-tests/src/lib.rs index e82b7f4b3..cee691bb9 100644 --- a/crates/integration-tests/src/lib.rs +++ b/crates/integration-tests/src/lib.rs @@ -340,7 +340,7 @@ pub mod planning { }) .collect() } - /// Pass 1, local panes for `entries`' recurrences and every Pass 2 rule + /// Pass 1, local tumbling windows for `entries`' recurrences and every Pass 2 rule /// on the seed grids (no query pruning). Rule tests read a group's /// candidates by `OriginOfCandidateForMemoGroup`. pub fn expanded_memo( @@ -351,7 +351,7 @@ pub mod planning { use asap_logical_asap_optimization::test_support::{ add_local_window_composition, try_populate_pass1, }; - use asap_logical_asap_optimization::PaneWidthDomain; + use asap_logical_asap_optimization::TumblingWindowWidthDomain; let initial = asap_logical_asap_optimization::generate_dag_of_logical_memo_groups(&roots); let inventory = generate_per_computation_candidates( &asap_logical_asap_optimization::generate_dag_of_logical_memo_groups(&roots), @@ -361,7 +361,7 @@ pub mod planning { add_local_window_composition(&mut memo, entries); asap_logical_asap_optimization::test_support::sharing_rules_and_shared_state_groups( memo, - PaneWidthDomain::SeedGrids, + TumblingWindowWidthDomain::SeedGrids, ) .unwrap() } diff --git a/crates/integration-tests/tests/deployment_capabilities.rs b/crates/integration-tests/tests/deployment_capabilities.rs index 0c8a19dc6..482ca9ac5 100644 --- a/crates/integration-tests/tests/deployment_capabilities.rs +++ b/crates/integration-tests/tests/deployment_capabilities.rs @@ -130,13 +130,13 @@ fn pattern_b_with_p50() -> asap_types::workload::PlanningWorkload { workload } -/// One explicitly shared KLL pane chain, passed through normal physical rules. -fn shared_pane_configurations(deployment: &DeploymentCapabilities) -> Run { +/// One explicitly shared KLL tumbling-window chain, passed through normal physical rules. +fn shared_tumbling_window_configurations(deployment: &DeploymentCapabilities) -> Run { use asap_logical_asap_optimization::test_support::{ compose_logical_candidate, generate_per_computation_candidates, }; use asap_logical_asap_optimization::test_support::{ - pane_candidates, window_composition_pass2_candidates, + tumbling_window_candidates, window_composition_pass2_candidates, }; use asap_types::ir::QueryRoot; use asap_types::stages::HowToComputeAggregateMeasure; @@ -176,9 +176,9 @@ fn shared_pane_configurations(deployment: &DeploymentCapabilities) -> Run { .into_iter() .map(|(_, node)| QueryRoot::Operator(node)) .collect::>(); - let panes = pane_candidates(&shared, &[Some(60_000), Some(60_000)]); - assert_eq!(panes.len(), 1, "both estimators share one build"); - let pane_roots = window_composition_pass2_candidates(&shared, &panes) + let tumbling_windows = tumbling_window_candidates(&shared, &[Some(60_000), Some(60_000)]); + assert_eq!(tumbling_windows.len(), 1, "both estimators share one build"); + let tumbling_window_roots = window_composition_pass2_candidates(&shared, &tumbling_windows) .unwrap() .remove(0) .roots @@ -187,7 +187,7 @@ fn shared_pane_configurations(deployment: &DeploymentCapabilities) -> Run { run_explicit_candidates( &workload, original, - vec![pane_roots], + vec![tumbling_window_roots], asap_planner::PlanningModels::builtin() .with_cost_inputs(&settings) .with_deployment_capabilities(deployment), @@ -197,7 +197,7 @@ fn shared_pane_configurations(deployment: &DeploymentCapabilities) -> Run { /// Physical candidate ids with their cost per second. type CandidateCosts = Vec<(String, f64)>; -/// The per-second cost of every physical candidate whose pane chain both +/// The per-second cost of every physical candidate whose tumbling-window chain both /// panels read is materialized at query time: shared within one evaluation /// (`MatAtQuery`), and kept across evaluations /// (`MatAtQueryKeptAcrossEvaluations`, which Stage 3 prices as such). @@ -232,7 +232,7 @@ fn shared_chain_costs(run: &Run) -> (CandidateCosts, CandidateCosts) { /// With query-time results retained, a chain two panels read is offered /// both ways: shared within one evaluation (rebuilt from the 5-min window /// every minute, then reused by both panels) and kept across evaluations -/// (B3: one new pane per evaluation, the 4 older panes held in memory +/// (B3: one new tumbling window per evaluation, the 4 older tumbling windows held in memory /// between evaluations). Holding 1M per-series KLLs between evaluations /// costs far more than rebuilding them, so the shared option is cheaper /// and the kept one is not selected. Without query-time retention, the @@ -243,7 +243,7 @@ fn query_time_results_retained_still_offers_the_chain_shared_within_one_evaluati query_time_results_retained: true, ..DeploymentCapabilities::default() }; - let run = shared_pane_configurations(&keeping); + let run = shared_tumbling_window_configurations(&keeping); let (within_one, kept) = shared_chain_costs(&run); assert!(!within_one.is_empty(), "no shared option: kept {kept:?}"); assert!(!kept.is_empty(), "no kept option: shared {within_one:?}"); @@ -264,7 +264,7 @@ fn query_time_results_retained_still_offers_the_chain_shared_within_one_evaluati ); assert_selects_cheapest_valid(&run); - let (_, kept) = shared_chain_costs(&shared_pane_configurations( + let (_, kept) = shared_chain_costs(&shared_tumbling_window_configurations( &DeploymentCapabilities::default(), )); assert!(kept.is_empty(), "{kept:?}"); diff --git a/crates/integration-tests/tests/finer_aggregate_reuse.rs b/crates/integration-tests/tests/finer_aggregate_reuse.rs index f8791fd06..c82760554 100644 --- a/crates/integration-tests/tests/finer_aggregate_reuse.rs +++ b/crates/integration-tests/tests/finer_aggregate_reuse.rs @@ -6,7 +6,7 @@ use asap_executor::values::Value; use asap_logical_asap_optimization::generate_dag_of_logical_memo_groups; use asap_logical_asap_optimization::{ test_support::{generate_per_computation_candidates, try_populate_pass1}, - PaneWidthDomain, + TumblingWindowWidthDomain, }; use asap_types::ir::{ASAPOp, NonASAPOp, OperatorNode}; use asap_types::stages::{CandidateForMemoGroup, MemoCandidates, OriginOfCandidateForMemoGroup}; @@ -57,7 +57,7 @@ fn expanded(queries: &[String]) -> MemoCandidates { let memo = try_populate_pass1(initial, &inventory).unwrap(); asap_logical_asap_optimization::test_support::sharing_rules_and_shared_state_groups( memo, - PaneWidthDomain::SeedGrids, + TumblingWindowWidthDomain::SeedGrids, ) .unwrap() } diff --git a/crates/integration-tests/tests/having_shared_aggregate.rs b/crates/integration-tests/tests/having_shared_aggregate.rs index 369c9e720..b4ced9e59 100644 --- a/crates/integration-tests/tests/having_shared_aggregate.rs +++ b/crates/integration-tests/tests/having_shared_aggregate.rs @@ -6,7 +6,7 @@ use asap_executor::values::Value; use asap_logical_asap_optimization::generate_dag_of_logical_memo_groups; use asap_logical_asap_optimization::{ test_support::{generate_per_computation_candidates, try_populate_pass1}, - PaneWidthDomain, + TumblingWindowWidthDomain, }; use asap_types::ir::NonASAPOp; use asap_types::stages::MemoCandidates; @@ -55,7 +55,7 @@ fn expanded(queries: &[String]) -> MemoCandidates { let memo = try_populate_pass1(dag, &inventory).unwrap(); asap_logical_asap_optimization::test_support::sharing_rules_and_shared_state_groups( memo, - PaneWidthDomain::SeedGrids, + TumblingWindowWidthDomain::SeedGrids, ) .unwrap() } diff --git a/crates/integration-tests/tests/independent_plan_seed.rs b/crates/integration-tests/tests/independent_plan_seed.rs index f7b31f130..6785d3532 100644 --- a/crates/integration-tests/tests/independent_plan_seed.rs +++ b/crates/integration-tests/tests/independent_plan_seed.rs @@ -78,7 +78,7 @@ fn assert_no_costlier_than_separate(queries: &[String]) -> (f64, f64) { (together, separate) } -/// Two per-city p90 queries: each alone maintains an ingestion-time KLL pane +/// Two per-city p90 queries: each alone maintains an ingestion-time KLL tumbling window /// chain; together the search used to stop at a query-time plan about twice /// as costly. #[test] diff --git a/crates/integration-tests/tests/joint_search_equivalence.rs b/crates/integration-tests/tests/joint_search_equivalence.rs index 5a5b47361..f1dc5d333 100644 --- a/crates/integration-tests/tests/joint_search_equivalence.rs +++ b/crates/integration-tests/tests/joint_search_equivalence.rs @@ -136,7 +136,7 @@ fn run_workload( run } -// Bounded runs can stop at different progressive pane-width domains. Compare +// Bounded runs can stop at different progressive tumbling-window-width domains. Compare // validated output semantics here, not inventory sizes from different catalogs. // Fixed-catalog terminal/producer scaling is tested in candidates::scopes. #[test] diff --git a/crates/integration-tests/tests/kll_pane_execution.rs b/crates/integration-tests/tests/kll_tumbling_window_execution.rs similarity index 79% rename from crates/integration-tests/tests/kll_pane_execution.rs rename to crates/integration-tests/tests/kll_tumbling_window_execution.rs index 52b0aa5c1..7b0db2dfc 100644 --- a/crates/integration-tests/tests/kll_pane_execution.rs +++ b/crates/integration-tests/tests/kll_tumbling_window_execution.rs @@ -1,4 +1,4 @@ -//! Maintenance -> stored pane state -> independently bound query execution. +//! Maintenance -> stored tumbling-window state -> independently bound query execution. use crate::physical_common; use asap_executor::sources::InMemoryDataStore; use asap_executor::{ @@ -34,7 +34,7 @@ use std::{ }, }; -const PANE_MS: i64 = 60_000; +const TUMBLING_WINDOW_WIDTH_MS: i64 = 60_000; fn family(k: u32) -> FieldDataType { FieldDataType::Sketch( @@ -65,12 +65,12 @@ fn query_scope() -> Scope { } } -/// p50 and p99 of one KLL (k = 200) merged from five one-minute panes of -/// `t`, each pane a materialized view. +/// p50 and p99 of one KLL (k = 200) merged from five one-minute tumbling windows of +/// `t`, each tumbling window a materialized view. struct Fixture { dag: PhysicalASAPDAG, - /// Pane `i` reads the minute ending `i` minutes before the evaluation time. - panes: Vec, + /// Tumbling window `i` reads the minute ending `i` minutes before the evaluation time. + tumbling_windows: Vec, views: Vec, merge: u64, p50: u64, @@ -85,17 +85,17 @@ fn fixture() -> Fixture { predicates: vec![], schema: table_schema(), })); - let panes: Vec> = (0..5) + let tumbling_windows: Vec> = (0..5) .map(|i| { let shifted = new(asap_types::ir::Operator::NonASAP(NonASAPOp::TimeShift { shift: TimeShift { - offset_ms: i * PANE_MS, + offset_ms: i * TUMBLING_WINDOW_WIDTH_MS, at: None, }, child: scan.clone(), })); let range = new(asap_types::ir::Operator::NonASAP(NonASAPOp::TimeRange { - range: std::time::Duration::from_millis(PANE_MS as u64), + range: std::time::Duration::from_millis(TUMBLING_WINDOW_WIDTH_MS as u64), kind: TimeRangeKind::Range, child: shifted, })); @@ -110,7 +110,7 @@ fn fixture() -> Fixture { }) .collect(); let merge = new(asap_types::ir::Operator::ASAP(ASAPOp::SummaryMerge { - children: panes.clone(), + children: tumbling_windows.clone(), newest_tumbling_windows: None, group_by: None, })); @@ -129,7 +129,7 @@ fn fixture() -> Fixture { ) .unwrap(); let compiled = compile_physical_asap_workload_with_node_ids(&roots).unwrap(); - let mut pane_ids = vec![0; 5]; + let mut tumbling_window_ids = vec![0; 5]; let (mut merge, mut p50, mut p99) = (0, 0, 0); for node in &compiled.dag.nodes { let id = node.id as u64; @@ -137,9 +137,9 @@ fn fixture() -> Fixture { asap_types::ir::Operator::ASAP(ASAPOp::SummaryAgg { child, .. }) => { let shift = match child.children()[0].non_asap() { Some(NonASAPOp::TimeShift { shift, .. }) => shift.offset_ms, - _ => unreachable!("a pane reads a shifted range"), + _ => unreachable!("a tumbling window reads a shifted range"), }; - pane_ids[(shift / PANE_MS) as usize] = id; + tumbling_window_ids[(shift / TUMBLING_WINDOW_WIDTH_MS) as usize] = id; } asap_types::ir::Operator::ASAP(ASAPOp::SummaryMerge { .. }) => merge = id, asap_types::ir::Operator::ASAP(ASAPOp::SummaryEstimate { @@ -153,10 +153,10 @@ fn fixture() -> Fixture { } } let mut dag = compiled.dag; - let views = views_at(&mut dag, &pane_ids).unwrap(); + let views = views_at(&mut dag, &tumbling_window_ids).unwrap(); Fixture { dag, - panes: pane_ids, + tumbling_windows: tumbling_window_ids, views, merge, p50, @@ -202,17 +202,17 @@ fn summary(batch: &Batch) -> Arc { }) .expect("missing KLL") } -/// Pane `pane`'s state, built by `maintenance` (the newest pane's build) in -/// the ingestion run of `(pane, pane + 1]` minutes. -fn pane_state(maintenance: &CompiledPhysicalDAG, pane: i64) -> Batch { - // Twenty samples in each (start,end] one-minute pane; k=200 avoids +/// Tumbling window `tumbling_window`'s state, built by `maintenance` (the newest tumbling window's build) in +/// the ingestion run of `(tumbling_window, tumbling_window + 1]` minutes. +fn tumbling_window_state(maintenance: &CompiledPhysicalDAG, tumbling_window: i64) -> Batch { + // Twenty samples in each (start,end] one-minute tumbling window; k=200 avoids // compaction so quantiles and sample counts have deterministic oracles. let schema = Arc::new(table_schema()); let rows = (0..20) .map(|i| { vec![ - Value::Timestamp(pane * PANE_MS + 1 + i * 1000), - Value::Float64((pane * 20 + i) as f64), + Value::Timestamp(tumbling_window * TUMBLING_WINDOW_WIDTH_MS + 1 + i * 1000), + Value::Float64((tumbling_window * 20 + i) as f64), ] }) .collect(); @@ -229,8 +229,8 @@ fn pane_state(maintenance: &CompiledPhysicalDAG, pane: i64) -> Batch { maintenance, store, Scope::Ingestion { - window_start_ms: pane * PANE_MS, - window_end_ms: (pane + 1) * PANE_MS, + window_start_ms: tumbling_window * TUMBLING_WINDOW_WIDTH_MS, + window_end_ms: (tumbling_window + 1) * TUMBLING_WINDOW_WIDTH_MS, }, ); state[0][0].clone() @@ -294,12 +294,14 @@ impl PhysicalOperator for CountStarts { /// Actual codec bytes survive destruction of maintenance state; one shared /// native merge supplies p50, p99 and the population-count oracle per run. #[test] -fn five_panes_roundtrip_and_shared_merge_runs_once() { +fn five_tumbling_windows_roundtrip_and_shared_merge_runs_once() { let fixture = fixture(); - let maintenance = compile(&fixture.dag, &[], &[fixture.panes[0]]).unwrap(); - let panes: Vec = (0..6).map(|pane| pane_state(&maintenance, pane)).collect(); + let maintenance = compile(&fixture.dag, &[], &[fixture.tumbling_windows[0]]).unwrap(); + let tumbling_windows: Vec = (0..6) + .map(|tumbling_window| tumbling_window_state(&maintenance, tumbling_window)) + .collect(); drop(maintenance); - let schema = schema_of(&fixture.dag, fixture.panes[0]); + let schema = schema_of(&fixture.dag, fixture.tumbling_windows[0]); let merge = Operator::summary_merge(schema.clone(), 0, vec![]).unwrap(); let compiled = compile( &fixture.dag, @@ -307,15 +309,15 @@ fn five_panes_roundtrip_and_shared_merge_runs_once() { &[fixture.merge, fixture.p50, fixture.p99], ) .unwrap(); - let layout = asap_physical_asap_optimization::test_support::PaneLayout { + let layout = asap_physical_asap_optimization::test_support::TumblingWindowLayout { tumbling_window_width_ms: 60_000, - pane_origin_ms: Some(0), + tumbling_window_origin_ms: Some(0), }; assert!( - asap_physical_asap_optimization::test_support::validate_pane_coverage( + asap_physical_asap_optimization::test_support::validate_tumbling_window_coverage( &layout, Some(330_000), - &asap_physical_asap_optimization::test_support::WindowEdgeCoverage::PaneAligned + &asap_physical_asap_optimization::test_support::WindowEdgeCoverage::TumblingWindowAligned ) .is_err(), "moving window edges require residual computation" @@ -323,31 +325,34 @@ fn five_panes_roundtrip_and_shared_merge_runs_once() { for offset in [0, 1] { let restored = restore( schema.clone(), - &panes[offset..offset + 5] + &tumbling_windows[offset..offset + 5] .iter() .map(summary) .collect::>(), ); let evaluation_time_ms = (5 + offset as i64) * 60_000; - asap_physical_asap_optimization::test_support::validate_pane_coverage( + asap_physical_asap_optimization::test_support::validate_tumbling_window_coverage( &layout, Some(evaluation_time_ms), - &asap_physical_asap_optimization::test_support::WindowEdgeCoverage::PaneAligned, + &asap_physical_asap_optimization::test_support::WindowEdgeCoverage::TumblingWindowAligned, ) .unwrap(); - // View `i` holds the pane ending `i` minutes before the evaluation time. + // View `i` holds the tumbling window ending `i` minutes before the evaluation time. let bind = |count: usize| { - fixture.panes[..count].iter().enumerate().fold( + fixture.tumbling_windows[..count].iter().enumerate().fold( InMemoryDataStore::default(), |store, (i, &view)| { store - .with_materialized_view(view as usize, vec![panes[offset + 4 - i].clone()]) + .with_materialized_view( + view as usize, + vec![tumbling_windows[offset + 4 - i].clone()], + ) .unwrap() }, ) }; let scope = Scope::Query { evaluation_time_ms }; - // A five-pane deployment cannot bind only four materialized views. + // A five-tumbling-window deployment cannot bind only four materialized views. let missing = try_execute(&compiled, bind(4), scope.clone()) .err() .unwrap(); @@ -413,9 +418,9 @@ fn five_panes_roundtrip_and_shared_merge_runs_once() { /// Relabelled KLL parameters, a view bound in another schema and a missing /// view fail explicitly. #[test] -fn panes_reject_parameters_schema_and_missing_binding() { +fn tumbling_windows_reject_parameters_schema_and_missing_binding() { let fixture = fixture(); - let schema = schema_of(&fixture.dag, fixture.panes[0]); + let schema = schema_of(&fixture.dag, fixture.tumbling_windows[0]); let wrong = Value::Summary { family: family(200), state: Arc::new(DatasketchesKLLAccumulator::new(128)), @@ -432,14 +437,15 @@ fn panes_reject_parameters_schema_and_missing_binding() { let raw = Arc::new(table_schema()); let raw_rows = Batch::try_new(raw, vec![vec![Value::Timestamp(1), Value::Float64(1.)]]).unwrap(); - let store = fixture - .panes - .iter() - .fold(InMemoryDataStore::default(), |store, &view| { - store - .with_materialized_view(view as usize, vec![raw_rows.clone()]) - .unwrap() - }); + let store = + fixture + .tumbling_windows + .iter() + .fold(InMemoryDataStore::default(), |store, &view| { + store + .with_materialized_view(view as usize, vec![raw_rows.clone()]) + .unwrap() + }); let relabelled = try_execute(&compiled, store, query_scope()).err().unwrap(); assert!( relabelled.to_string().contains("another schema"), @@ -482,16 +488,18 @@ impl asap_executor::sources::DataStore for DefinitionStore { } } -/// A store keyed only by `(definition_id, time range)` answers the five-pane -/// query: the five panes share one definition and request the five +/// A store keyed only by `(definition_id, time range)` answers the five-tumbling-window +/// query: the five tumbling windows share one definition and request the five /// one-minute ranges that tile the window, and the answers equal those of -/// binding each pane's state to its node. +/// binding each tumbling window's state to its node. #[test] -fn a_store_keyed_by_definition_and_time_range_answers_the_panes() { +fn a_store_keyed_by_definition_and_time_range_answers_the_tumbling_windows() { let fixture = fixture(); - let maintenance = compile(&fixture.dag, &[], &[fixture.panes[0]]).unwrap(); - // The state of the pane of minute `m`, `(m, m + 1]` minutes. - let panes: Vec = (0..5).map(|m| pane_state(&maintenance, m)).collect(); + let maintenance = compile(&fixture.dag, &[], &[fixture.tumbling_windows[0]]).unwrap(); + // The state of the tumbling window of minute `m`, `(m, m + 1]` minutes. + let tumbling_windows: Vec = (0..5) + .map(|m| tumbling_window_state(&maintenance, m)) + .collect(); let definition = fixture.views[0].coverage().definition_id().to_owned(); assert!(fixture .views @@ -500,8 +508,14 @@ fn a_store_keyed_by_definition_and_time_range_answers_the_panes() { let states = (0..5) .map(|m| { ( - (definition.clone(), (m * PANE_MS, (m + 1) * PANE_MS)), - panes[m as usize].clone(), + ( + definition.clone(), + ( + m * TUMBLING_WINDOW_WIDTH_MS, + (m + 1) * TUMBLING_WINDOW_WIDTH_MS, + ), + ), + tumbling_windows[m as usize].clone(), ) }) .collect(); @@ -540,20 +554,21 @@ fn a_store_keyed_by_definition_and_time_range_answers_the_panes() { assert_eq!( requested, (0..5) - .map(|m| (m * PANE_MS, (m + 1) * PANE_MS)) + .map(|m| ( + m * TUMBLING_WINDOW_WIDTH_MS, + (m + 1) * TUMBLING_WINDOW_WIDTH_MS + )) .collect::>() ); - // View `i` holds the pane ending `i` minutes before the evaluation time. - let by_node = - fixture - .panes - .iter() - .enumerate() - .fold(InMemoryDataStore::default(), |store, (i, &view)| { - store - .with_materialized_view(view as usize, vec![panes[4 - i].clone()]) - .unwrap() - }); + // View `i` holds the tumbling window ending `i` minutes before the evaluation time. + let by_node = fixture.tumbling_windows.iter().enumerate().fold( + InMemoryDataStore::default(), + |store, (i, &view)| { + store + .with_materialized_view(view as usize, vec![tumbling_windows[4 - i].clone()]) + .unwrap() + }, + ); let by_node: Vec>> = physical_common::execute(&compiled, by_node, scope) .iter() .map(|batches| batches.iter().flat_map(Batch::to_rows).collect()) diff --git a/crates/integration-tests/tests/main.rs b/crates/integration-tests/tests/main.rs index c61f0cdab..bf81ee35d 100644 --- a/crates/integration-tests/tests/main.rs +++ b/crates/integration-tests/tests/main.rs @@ -19,7 +19,7 @@ mod having_shared_aggregate; mod identical_exact_queries; mod independent_plan_seed; mod joint_search_equivalence; -mod kll_pane_execution; +mod kll_tumbling_window_execution; mod materialization_e2e; mod materialized_views; mod memo_enumeration; @@ -56,4 +56,4 @@ mod stage1_logical_asap_optimization; mod stage2_physical_asap_optimization_sql; mod summary_coverage_e2e; mod time_range; -mod tumbling_pane_merge; +mod tumbling_window_merge; diff --git a/crates/integration-tests/tests/materialized_views.rs b/crates/integration-tests/tests/materialized_views.rs index 4504fff52..c6fad2f29 100644 --- a/crates/integration-tests/tests/materialized_views.rs +++ b/crates/integration-tests/tests/materialized_views.rs @@ -44,9 +44,9 @@ fn repeating_with(queries: &[&str], extra: &str) -> PlanningWorkload { } /// Stage 3's priced raw lookback of `dag` (the selected candidate's -/// physical DAG, pane chains unexpanded), per source: each query-time +/// physical DAG, tumbling-window chains unexpanded), per source: each query-time /// scan's `PhysicalASAPDAG::scan_window` with every chain kept across -/// evaluations reading its newest pane, as `asap-plan-selection` prices +/// evaluations reading its newest tumbling window, as `asap-plan-selection` prices /// it, the longest per source. fn stage3_raw_lookback_ms(dag: &PhysicalASAPDAG) -> Vec<(Source, Option)> { let kept_chains: HashSet<_> = dag diff --git a/crates/integration-tests/tests/memo_enumeration.rs b/crates/integration-tests/tests/memo_enumeration.rs index 61c28df7d..5ff0fdca6 100644 --- a/crates/integration-tests/tests/memo_enumeration.rs +++ b/crates/integration-tests/tests/memo_enumeration.rs @@ -10,11 +10,11 @@ //! Pass 1) and `PlanningRun::memo_steps` (the other steps). //! //! **Which round's memo.** The default search plans in rounds of growing -//! pane-width domains and records the memo of the round whose plan it +//! tumbling-window-width domains and records the memo of the round whose plan it //! selected, so its last step depends on the time budget (the seed-grid //! round when a debug build runs out of it; round 5 of the release run that //! made the doc's document, 354 groups and 1738 candidates). The ratio test -//! runs one round over the complete pane-width domain (a search with fewer +//! runs one round over the complete tumbling-window-width domain (a search with fewer //! than 4 evaluations has no progressive rounds), so its last step is the //! same on every machine. Stage 0 and Pass 1 are the same in every round. //! @@ -127,7 +127,7 @@ impl asap_accuracy::AccuracyModel for AcceptsNoOutput { } } -/// Rounds up to the complete pane-width domain, with a time limit a slow +/// Rounds up to the complete tumbling-window-width domain, with a time limit a slow /// machine's Pass 2 does not reach. Stage 3 may use the whole budget: when /// it validates no plan, it searches until the deadline; the memo steps are /// recorded either way. @@ -344,7 +344,7 @@ fn ratio_of_quantiles_production_memo() { // groups: no query has a guarantee through `/` and `+` over a KLL rank // error or a DDSketch relative one. The memo below is planned with a // model that prunes none and accepts no output, so its summaries stay - // and its last round, over the complete pane-width domain, is recorded. + // and its last round, over the complete tumbling-window-width domain, is recorded. let enforced = production_run(&ratio_of_quantiles(), PlanSearchSettings::default()); assert_eq!(candidate_count(pass1_memo(&enforced)), 7); let models = PlanningModels::builtin().with_accuracy(&AcceptsNoOutput); @@ -368,7 +368,7 @@ fn ratio_of_quantiles_production_memo() { ), (25, 9, 9, 4) ); - // The last step, over the complete pane-width domain. + // The last step, over the complete tumbling-window-width domain. let last = &steps.concrete_states; assert_eq!(last.groups.len(), 255); assert_eq!(candidate_count(last), 1243); diff --git a/crates/integration-tests/tests/planner_layering_common/mod.rs b/crates/integration-tests/tests/planner_layering_common/mod.rs index efa47f81b..45e59b423 100644 --- a/crates/integration-tests/tests/planner_layering_common/mod.rs +++ b/crates/integration-tests/tests/planner_layering_common/mod.rs @@ -413,13 +413,13 @@ pub fn run_pattern_b_fixture( } /// Three fixed Pattern-B computations used by cost/retention fixtures: -/// exact, whole-window KLL, and a KLL chain of sixty-second panes. +/// exact, whole-window KLL, and a KLL chain of sixty-second tumbling windows. pub fn pattern_b_computations(workload: &PlanningWorkload) -> Vec> { use asap_logical_asap_optimization::test_support::{ compose_logical_candidate, generate_per_computation_candidates, }; use asap_logical_asap_optimization::test_support::{ - pane_candidates, window_composition_pass2_candidates, + tumbling_window_candidates, window_composition_pass2_candidates, }; use asap_types::stages::HowToComputeAggregateMeasure; let roots = lower_promql(workload); @@ -443,13 +443,13 @@ pub fn pattern_b_computations(workload: &PlanningWorkload) -> Vec }) .unwrap(); let whole: Vec<_> = compose_logical_candidate(&local, &[kll]).unwrap(); - let panes = pane_candidates(&whole, &[Some(60_000)]); - assert_eq!(panes.len(), 1); - let chain = window_composition_pass2_candidates(&whole, &panes) + let tumbling_windows = tumbling_window_candidates(&whole, &[Some(60_000)]); + assert_eq!(tumbling_windows.len(), 1); + let chain = window_composition_pass2_candidates(&whole, &tumbling_windows) .unwrap() .remove(0) .roots - .expect("sixty-second panes compose"); + .expect("sixty-second tumbling windows compose"); vec![roots, whole, chain] } @@ -576,7 +576,7 @@ pub fn is_merge(payload: &Payload) -> bool { matches!(payload, Operator::ASAP(ASAPOp::SummaryMerge { .. })) } -/// The sketch build nodes (`SummaryAgg` or tumbling-pane `SummaryTumblingWindowChain` over a +/// The sketch build nodes (`SummaryAgg` or tumbling-window `SummaryTumblingWindowChain` over a /// sketch family) of `dag`, with their algorithm and parameters. pub fn sketch_builds(dag: &impl ExportedDag) -> Vec<(NodeId, SketchAlgorithm, SketchParams)> { dag.node_ids() @@ -907,8 +907,8 @@ pub enum WindowForm { ExponentialHistogram { horizon_ms: u64 }, } -/// The window summary of a build: a `SummaryTumblingWindowChain` keeps back-to-back panes of -/// its pane width (tumbling); a `SummaryAgg` is rebuilt from its query +/// The window summary of a build: a `SummaryTumblingWindowChain` keeps back-to-back tumbling windows of +/// its tumbling-window width (tumbling); a `SummaryAgg` is rebuilt from its query /// window. Sliding windows and Exponential Histograms are not planned yet /// (#628). pub fn window_form(dag: &impl ExportedDag, build: NodeId) -> WindowForm { @@ -925,8 +925,8 @@ pub fn window_form(dag: &impl ExportedDag, build: NodeId) -> WindowForm { /// How far back from an evaluation a materialized node's kept output /// reaches in event time, derived the way plan selection prices what it -/// keeps: a pane chain keeps its `N` -/// panes of width `w`, plus the one being built when maintained at +/// keeps: a tumbling-window chain keeps its `N` +/// tumbling windows of width `w`, plus the one being built when maintained at /// ingestion time; a whole-window build keeps its window, and the next one /// being built at ingestion time; each plus the offset of the time shifts /// below it. `None` for any other node. @@ -954,8 +954,8 @@ pub fn retention_ms(p: &Physical, node: NodeId) -> Option { tumbling_window_count, .. }) => { - let panes = u64::from(*tumbling_window_count) + u64::from(at_ingestion); - Some(panes * tumbling_window_width_ms + offset_below(below(node))) + let tumbling_windows = u64::from(*tumbling_window_count) + u64::from(at_ingestion); + Some(tumbling_windows * tumbling_window_width_ms + offset_below(below(node))) } Operator::ASAP(ASAPOp::SummaryAgg { .. }) => { let child = below(node)?; diff --git a/crates/integration-tests/tests/planner_layering_example1.rs b/crates/integration-tests/tests/planner_layering_example1.rs index ba84e0f5e..bc12132e0 100644 --- a/crates/integration-tests/tests/planner_layering_example1.rs +++ b/crates/integration-tests/tests/planner_layering_example1.rs @@ -25,14 +25,14 @@ fn pipeline() -> Run { } // Explicit rule fixtures: vary Q2's three documented algorithms while Q1 -// stays exact, and exercise source sharing and ten-second panes. This is not +// stays exact, and exercise source sharing and ten-second tumbling windows. This is not // a production workload search or an exhaustive memo archive. fn rule_fixture(workload: &asap_types::workload::PlanningWorkload) -> Run { use asap_logical_asap_optimization::test_support::{ compose_logical_candidate, generate_per_computation_candidates, }; use asap_logical_asap_optimization::test_support::{ - pane_candidates, window_composition_pass2_candidates, + tumbling_window_candidates, window_composition_pass2_candidates, }; use asap_types::stages::HowToComputeAggregateMeasure; let roots = lower_promql(workload); @@ -65,9 +65,9 @@ fn rule_fixture(workload: &asap_types::workload::PlanningWorkload) -> Run { } let whole = unshare(&compose_logical_candidate(&local, &choice).unwrap()); let mut windows = vec![whole.clone()]; - let panes = pane_candidates(&whole, &[Some(10_000), Some(10_000)]); + let tumbling_windows = tumbling_window_candidates(&whole, &[Some(10_000), Some(10_000)]); windows.extend( - window_composition_pass2_candidates(&whole, &panes) + window_composition_pass2_candidates(&whole, &tumbling_windows) .unwrap() .into_iter() .map(|candidate| candidate.roots.unwrap()), @@ -151,7 +151,7 @@ fn sketch_options(dag: &impl ExportedDag, nodes: &HashSet) -> BTreeSet bool { dag.node_ids().into_iter().all(|id| { !matches!( @@ -178,7 +178,7 @@ fn classify(dag: &impl ExportedDag, query_roots: &[NodeId]) -> (Q2Option, bool) /// The candidate with Q2 `option` whose queries share no node and whose /// summaries are built over whole windows (Pass 2 adds shared-input and -/// tumbling-pane variants of each). +/// tumbling-window variants of each). fn physical_with(run: &Run, option: Q2Option) -> &Physical { let mut found = run .physical @@ -258,7 +258,7 @@ fn stage0_query_language_frontends_queries_do_not_share_nodes() { /// Pass 1 gives 1 × 3 = 3 candidates without a shared input: Q2's top-k is /// exact, Count-Min + heap or CountSketch + heap. (Pass 2 adds shared-input -/// and tumbling-pane variants of each.) +/// and tumbling-window variants of each.) #[test] fn stage1_logical_asap_optimization_has_three_pass1_candidates() { let run = pipeline(); @@ -276,7 +276,7 @@ fn stage1_logical_asap_optimization_has_three_pass1_candidates() { } /// Q1 is exact in every candidate: no sketch build or estimate is reachable -/// from its root (its rate may be merged from exact tumbling panes). +/// from its root (its rate may be merged from exact tumbling windows). #[test] fn stage1_logical_asap_optimization_q1_is_always_exact() { let run = pipeline(); @@ -367,11 +367,11 @@ fn stage1_logical_asap_optimization_keeps_independent_and_shared_variants() { } /// Pass 2's window-composition rule builds Q1's rate and Q2's option in six -/// 10-s tumbling panes (the gcd of the 1-min window and the 10-s interval), +/// 10-s tumbling windows (the gcd of the 1-min window and the 10-s interval), /// each alone and both together, next to the whole-window candidates: 27 /// candidates in all. #[test] -fn stage1_logical_asap_optimization_window_composition_adds_10s_tumbling_panes() { +fn stage1_logical_asap_optimization_window_composition_adds_10s_tumbling_windows() { let run = pipeline(); let mut chains = BTreeSet::new(); for c in &run.logical { @@ -438,7 +438,7 @@ fn stage1_logical_asap_optimization_candidates_are_valid_and_uniquely_named() { /// No candidate is discarded before plan selection: each logical candidate /// keeps one physical candidate with nothing materialized, 3 of them -/// without a shared input or tumbling panes. +/// without a shared input or tumbling windows. #[test] fn stage2_physical_asap_optimization_keeps_every_logical_candidate() { let run = pipeline(); @@ -450,7 +450,7 @@ fn stage2_physical_asap_optimization_keeps_every_logical_candidate() { } /// Each physical candidate keeps its logical Q2 option, and one with -/// nothing materialized keeps its input sharing. Maintaining Q2's panes at +/// nothing materialized keeps its input sharing. Maintaining Q2's tumbling windows at /// ingestion time splits a scan Q1 reads at query time, one copy per phase. #[test] fn stage2_physical_asap_optimization_preserves_logical_choices() { @@ -556,11 +556,11 @@ fn stage2_physical_asap_optimization_summary_topk_is_build_then_estimate() { } } -/// Only tumbling panes can be maintained at ingestion time: the +/// Only tumbling windows can be maintained at ingestion time: the /// whole-window builds' fixed interval has no phase, and no summary is read -/// by both queries. Q1's rate panes are counter states, which need each +/// by both queries. Q1's rate tumbling windows are counter states, which need each /// series' samples in time order; continuously ingesting PromQL data is -/// taken to arrive in that order, so they are maintained too. Each pane +/// taken to arrive in that order, so they are maintained too. Each tumbling window /// chain is maintained or not independently: a logical candidate with `c` /// chains has `2^c` physical candidates. A shared input (the highest node /// both queries read: the scan, or the time range above it) is also built @@ -568,7 +568,7 @@ fn stage2_physical_asap_optimization_summary_topk_is_build_then_estimate() { /// with every chain at query time, since a chain maintained at ingestion /// time leaves one query reading the input at query time. #[test] -fn stage2_physical_asap_optimization_materializes_only_panes_at_ingestion() { +fn stage2_physical_asap_optimization_materializes_only_tumbling_windows_at_ingestion() { let run = pipeline(); let mut counter_maintained = 0; let mut shared_once = 0; @@ -580,7 +580,7 @@ fn stage2_physical_asap_optimization_materializes_only_panes_at_ingestion() { let Operator::ASAP(ASAPOp::SummaryTumblingWindowChain { family, .. }) = p.dag().payload(n.id) else { - panic!("{}: {:?} is not a pane chain", p.id, n.id) + panic!("{}: {:?} is not a tumbling-window chain", p.id, n.id) }; if matches!( family, @@ -616,7 +616,10 @@ fn stage2_physical_asap_optimization_materializes_only_panes_at_ingestion() { } } } - assert!(counter_maintained > 0, "Q1's rate panes are maintained"); + assert!( + counter_maintained > 0, + "Q1's rate tumbling windows are maintained" + ); assert!( shared_once > 0, "explicit shared-source fixtures are materialized" @@ -780,12 +783,12 @@ fn stage3_plan_selection_shared_input_ties_up_to_rounding_unless_built_once() { ); } -/// Tumbling panes rebuilt at every evaluation cost more than the whole -/// window: the panes read the same rows and update the same states, and -/// merging them adds work. Heap panes are invalid like their whole-window +/// Tumbling windows rebuilt at every evaluation cost more than the whole +/// window: the tumbling windows read the same rows and update the same states, and +/// merging them adds work. Heap tumbling windows are invalid like their whole-window /// builds (and their merged top-k is only approximate). #[test] -fn stage3_plan_selection_tumbling_panes_at_query_time_cost_more() { +fn stage3_plan_selection_tumbling_windows_at_query_time_cost_more() { let run = pipeline(); let selected = run.cost(&run.selection.selected).unwrap(); let invalid = run.invalid(); diff --git a/crates/integration-tests/tests/planner_layering_example2.rs b/crates/integration-tests/tests/planner_layering_example2.rs index c4812fd6a..a71a9c7e6 100644 --- a/crates/integration-tests/tests/planner_layering_example2.rs +++ b/crates/integration-tests/tests/planner_layering_example2.rs @@ -181,7 +181,7 @@ fn rule_fixture_computations( compose_logical_candidate, generate_per_computation_candidates, }; use asap_logical_asap_optimization::test_support::{ - pane_candidates, window_composition_pass2_candidates, + tumbling_window_candidates, window_composition_pass2_candidates, }; use asap_types::stages::HowToComputeAggregateMeasure; let local = generate_per_computation_candidates( @@ -235,12 +235,12 @@ fn rule_fixture_computations( if distinct_nodes(&shared) < distinct_nodes(&whole) { computations.push(shared); } - let panes = pane_candidates(&whole, &grid); - // Isolate one pane configuration at a time; no Cartesian products of + let tumbling_windows = tumbling_window_candidates(&whole, &grid); + // Isolate one tumbling-window configuration at a time; no Cartesian products of // independent window implementations are needed for the error tests. - for pane in panes { + for tumbling_window in tumbling_windows { computations.extend( - window_composition_pass2_candidates(&whole, &[pane]) + window_composition_pass2_candidates(&whole, &[tumbling_window]) .unwrap() .into_iter() .map(|candidate| candidate.roots.unwrap()), @@ -878,7 +878,7 @@ fn stage3_plan_selection_accepts_kmv_for_any_failure_probability_without_a_bound } /// The physical candidates among `ids` that build their sketch as a -/// tumbling-pane `SummaryTumblingWindowChain`. +/// tumbling-window `SummaryTumblingWindowChain`. fn tumbling<'a>(run: &Run, ids: Vec<&'a str>) -> Vec<&'a str> { ids.into_iter() .filter(|id| { @@ -890,15 +890,18 @@ fn tumbling<'a>(run: &Run, ids: Vec<&'a str>) -> Vec<&'a str> { .collect() } -/// The enlarged precision reaches a sketch carried by a tumbling-pane +/// The enlarged precision reaches a sketch carried by a tumbling-window /// `SummaryTumblingWindowChain` too (#713): Q1's HLL over a bound of 3,000 is valid only at -/// precision 18, above the 17 sized for ε alone, so a valid tumbling-pane +/// precision 18, above the 17 sized for ε alone, so a valid tumbling-window /// HLL candidate carries precision 18 in its chain. #[test] -fn pass1_sizing_enlarges_tumbling_pane_hll() { +fn pass1_sizing_enlarges_tumbling_window_hll() { let run = pipeline_with_distinct_bound(Some(3_000)); let hll = tumbling(&run, hll_only_candidates(&run)); - assert!(!hll.is_empty(), "a tumbling-pane HLL candidate answers Q1"); + assert!( + !hll.is_empty(), + "a tumbling-window HLL candidate answers Q1" + ); for id in hll { assert!(run.cost(id).is_some(), "{id}: {:?}", run.invalid().get(id)); } diff --git a/crates/integration-tests/tests/planner_layering_example3.rs b/crates/integration-tests/tests/planner_layering_example3.rs index 651719a65..c0c8ab6ed 100644 --- a/crates/integration-tests/tests/planner_layering_example3.rs +++ b/crates/integration-tests/tests/planner_layering_example3.rs @@ -74,7 +74,7 @@ fn run_b() -> Run { compose_logical_candidate, generate_per_computation_candidates, }; use asap_logical_asap_optimization::test_support::{ - pane_candidates, window_composition_pass2_candidates, + tumbling_window_candidates, window_composition_pass2_candidates, }; use asap_types::stages::HowToComputeAggregateMeasure; let workload = pattern_b(); @@ -92,8 +92,8 @@ fn run_b() -> Run { continue; } let whole: Vec<_> = compose_logical_candidate(&local, &[option]).unwrap(); - let panes = pane_candidates(&whole, &[Some(PATTERN_B_INTERVAL_MS)]); - let chain = window_composition_pass2_candidates(&whole, &panes) + let tumbling_windows = tumbling_window_candidates(&whole, &[Some(PATTERN_B_INTERVAL_MS)]); + let chain = window_composition_pass2_candidates(&whole, &tumbling_windows) .unwrap() .remove(0) .roots @@ -455,7 +455,7 @@ fn stage1_logical_asap_optimization_b_window_composition_adds_sliding_and_tumbli } /// Each summary option of Pass 1 (KLL and DDSketch, both mergeable) also -/// comes as 1-min tumbling panes, gcd(5 min, 1 min), merged five at a time +/// comes as 1-min tumbling windows, gcd(5 min, 1 min), merged five at a time /// before its p99 estimate; the whole-window builds are kept and read no /// merge. Exact has no summary to split: 2 options × 2 forms + exact = 5. #[test] @@ -591,38 +591,45 @@ fn stage3_plan_selection_b_selects_cheapest_valid() { assert_each_node_charged_per_consuming_query(&run); } -/// With nothing materialized, the 1-min KLL panes are all rebuilt at every +/// With nothing materialized, the 1-min KLL tumbling windows are all rebuilt at every /// evaluation: they read the same 5 minutes of samples and update the same -/// states as one whole-window KLL, and merging five panes per series adds +/// states as one whole-window KLL, and merging five tumbling windows per series adds /// 1M × 5 operations a minute. Each evaluation also holds its states while -/// it runs: the whole window's 1M KLLs, or the five panes' 5M smaller ones, +/// it runs: the whole window's 1M KLLs, or the five tumbling windows' 5M smaller ones, /// for the evaluation's latency. So whole-window KLL is selected at 2.363 per -/// second and the panes cost 3.611; exact costs 2.458. DDSketch, whole or in -/// panes, misses the rank target. +/// second and the tumbling windows cost 3.611; exact costs 2.458. DDSketch, whole or in +/// tumbling windows, misses the rank target. #[test] -fn stage3_plan_selection_b_panes_rebuilt_at_query_time_add_the_merge() { +fn stage3_plan_selection_b_tumbling_windows_rebuilt_at_query_time_add_the_merge() { let run = run_b(); let cost = |c: &Logical| run.cost(&run.physical_of(c).next().unwrap().id); - let with = |algorithm: &str, panes: bool| { + let with = |algorithm: &str, tumbling_windows: bool| { run.logical .iter() .find(|c| { query_option(&c.dag, &c.query_roots, 0) == algorithm && windowed_builds(c) .iter() - .any(|(_, _, f, _)| (*f != WindowForm::None) == panes) + .any(|(_, _, f, _)| (*f != WindowForm::None) == tumbling_windows) }) - .unwrap_or_else(|| panic!("{algorithm} panes={panes}")) + .unwrap_or_else(|| panic!("{algorithm} tumbling_windows={tumbling_windows}")) }; let whole = cost(with("Kll", false)).unwrap(); - let panes = cost(with("Kll", true)).unwrap(); + let tumbling_windows = cost(with("Kll", true)).unwrap(); assert!((whole - 2.362_500_025_2).abs() < 1e-9, "{whole}"); - assert!((panes - 3.611_233_350_8).abs() < 1e-9, "{panes}"); + assert!( + (tumbling_windows - 3.611_233_350_8).abs() < 1e-9, + "{tumbling_windows}" + ); assert_eq!( run.selection.selected, run.physical_of(with("Kll", false)).next().unwrap().id ); - for panes in [false, true] { - assert_eq!(cost(with("DDSketch", panes)), None, "panes={panes}"); + for tumbling_windows in [false, true] { + assert_eq!( + cost(with("DDSketch", tumbling_windows)), + None, + "tumbling_windows={tumbling_windows}" + ); } } diff --git a/crates/integration-tests/tests/planner_layering_example4.rs b/crates/integration-tests/tests/planner_layering_example4.rs index e40446600..a47c5fbfa 100644 --- a/crates/integration-tests/tests/planner_layering_example4.rs +++ b/crates/integration-tests/tests/planner_layering_example4.rs @@ -381,7 +381,7 @@ fn stage2_physical_asap_optimization_b_tumbling_kll_has_b1_and_b2() { /// B3 runs nothing at ingestion time; it keeps the tumbling KLLs it builds at query time. /// Implementer addition: kept for the readers' 5-min look-back, it is -/// priced as building one 1-min pane per evaluation, so it reads a fifth of +/// priced as building one 1-min tumbling window per evaluation, so it reads a fifth of /// B2's raw data and keeps a fifth of it on disk. #[test] fn stage2_physical_asap_optimization_b_b3_keeps_query_time_windows() { @@ -435,7 +435,7 @@ fn pattern_b_under(cost_model: CostModel) -> Run { } /// The candidates the cost models choose between: the exact plan, the KLL -/// rebuilt over the whole window at query time and B1 (the panes maintained at ingestion time). +/// rebuilt over the whole window at query time and B1 (the tumbling windows maintained at ingestion time). struct PatternBCandidates { exact: String, whole_window_kll: String, @@ -443,7 +443,7 @@ struct PatternBCandidates { } fn pattern_b_candidates(run: &Run) -> PatternBCandidates { - let panes = options_of(run, tumbling(), 1); + let tumbling_windows = options_of(run, tumbling(), 1); PatternBCandidates { exact: run .physical @@ -455,7 +455,7 @@ fn pattern_b_candidates(run: &Run) -> PatternBCandidates { whole_window_kll: options_of(run, Some(WindowForm::None), 1)[&NotMaterialized] .0 .clone(), - b1: panes[&MatAtIngestion].0.clone(), + b1: tumbling_windows[&MatAtIngestion].0.clone(), } } @@ -471,13 +471,13 @@ fn cluster(memory_gb: u64, objective: ClusterObjective) -> CostModel { /// Each cost model chooses what its deployment pays for. Cost model 1 /// (usage over time) picks the KLL rebuilt every minute: it uses memory only -/// while it runs, and B1 pays for 5.2 GB of panes every second. Cost model 2 +/// while it runs, and B1 pays for 5.2 GB of tumbling windows every second. Cost model 2 /// (provisioned capacity) picks the exact plan: every candidate needs one /// core for its evaluation, and the exact plan's peak memory (192 MB of /// per-series values) is the smallest, while the KLL's 1M states, each /// grown for its 20 samples, take 1 GB at once. Cost model 3 (own cluster, /// lowest latency) picks B1 when its 6.1 GB fit, since merging maintained -/// panes takes 7 ms against 126 ms for rebuilding; in 4 GB, B1 is invalid +/// tumbling windows takes 7 ms against 126 ms for rebuilding; in 4 GB, B1 is invalid /// and the KLL is fastest. #[test] fn stage3_plan_selection_b_cost_models_choose_differently() { diff --git a/crates/integration-tests/tests/range_evaluation.rs b/crates/integration-tests/tests/range_evaluation.rs index f4f00b6b8..2f97eeb72 100644 --- a/crates/integration-tests/tests/range_evaluation.rs +++ b/crates/integration-tests/tests/range_evaluation.rs @@ -1,8 +1,8 @@ //! #757: a query answered as a range request (`RangeEvaluation`), e.g. a //! dashboard panel that evaluates the last hour at 1-minute steps on every -//! refresh. Stage 1 and Stage 2 keep pane widths on the step grid, and +//! refresh. Stage 1 and Stage 2 keep tumbling-window widths on the step grid, and //! Stage 3 prices `span_ms / step_ms + 1` evaluations per request, credits -//! the panes kept across steps and requests, and retains kept panes for +//! the tumbling windows kept across steps and requests, and retains kept tumbling windows for //! the span plus the window. use crate::planner_layering_common; @@ -27,14 +27,14 @@ const LAST_HOUR_BY_MINUTE: RangeEvaluation = RangeEvaluation { }; /// Price explicitly named computations under the same models as production. -/// This fixture controls its pane width; a timed MILP archive is not a catalog +/// This fixture controls its tumbling-window width; a timed MILP archive is not a catalog /// of every legal materialization or a stable source of P1/P2 identifiers. fn run_explicit_for(workload: &PlanningWorkload, deployment: &DeploymentCapabilities) -> Run { use asap_logical_asap_optimization::test_support::{ compose_logical_candidate, generate_per_computation_candidates, }; use asap_logical_asap_optimization::test_support::{ - pane_candidates, window_composition_pass2_candidates, + tumbling_window_candidates, window_composition_pass2_candidates, }; use asap_types::ir::schema::SketchAlgorithm; use asap_types::stages::HowToComputeAggregateMeasure; @@ -64,10 +64,14 @@ fn run_explicit_for(workload: &PlanningWorkload, deployment: &DeploymentCapabili .entries() .map(|entry| entry.evaluation_grid_ms()) .collect::>(); - let panes = pane_candidates(build, &grids); - assert_eq!(panes.len(), 1, "one explicit pane computation"); + let tumbling_windows = tumbling_window_candidates(build, &grids); + assert_eq!( + tumbling_windows.len(), + 1, + "one explicit tumbling window computation" + ); candidates.push( - window_composition_pass2_candidates(build, &panes) + window_composition_pass2_candidates(build, &tumbling_windows) .unwrap() .remove(0) .roots @@ -151,8 +155,8 @@ fn close(actual: f64, expected: f64) -> bool { (actual - expected).abs() <= 1e-6 * expected.abs().max(1.0) } -/// Each physical candidate's pane chains: (pane width, materialization). -fn pane_chains(run: &Run) -> Vec<(String, u64, Materialization)> { +/// Each physical candidate's tumbling-window chains: (tumbling-window width, materialization). +fn tumbling_window_chains(run: &Run) -> Vec<(String, u64, Materialization)> { run.physical .iter() .flat_map(|p| { @@ -167,9 +171,9 @@ fn pane_chains(run: &Run) -> Vec<(String, u64, Materialization)> { .collect() } -/// The candidate whose single pane chain is materialized as `wanted`. +/// The candidate whose single tumbling-window chain is materialized as `wanted`. fn with_chain(run: &Run, wanted: Materialization) -> String { - pane_chains(run) + tumbling_window_chains(run) .into_iter() .find(|(_, _, m)| *m == wanted) .unwrap_or_else(|| panic!("no candidate with a {wanted:?} chain")) @@ -179,11 +183,11 @@ fn with_chain(run: &Run, wanted: Materialization) -> String { /// (a) The issue's acceptance example: Pattern B as a dashboard over the /// last hour at 1-minute steps prices 61 evaluations per request. Every /// query-time computation runs at every step, so the whole-window KLL -/// (2.3625 → 144.1125 cost/s), the panes rebuilt per step (3.6112 → +/// (2.3625 → 144.1125 cost/s), the tumbling windows rebuilt per step (3.6112 → /// 220.2852) and the exact plan (2.4576 → 149.9136) cost 61 times as much, -/// the memory each step holds while it runs included. The kept panes (B3) -/// are built once per request (one new 1-min pane) but merged at every -/// step, and keep 64 older panes instead of 4 (1 h + 5 min of panes): +/// the memory each step holds while it runs included. The kept tumbling windows (B3) +/// are built once per request (one new 1-min tumbling window) but merged at every +/// step, and keep 64 older tumbling windows instead of 4 (1 h + 5 min of tumbling windows): /// 436.59 → 6,987.05. B1 keeps 66 instead of 6: 654.54 → 7,202.31. The /// whole-window KLL is still selected. #[test] @@ -206,7 +210,7 @@ fn stage3_plan_selection_prices_61_evaluations_per_dashboard_request() { for id in ["P1", "P2", "P3"] { assert!(close(cost(&range, id), 61.0 * cost(&none, id)), "{id}"); } - // B3: 4 → 64 panes kept between requests; B1: 6 → 66 panes kept. + // B3: 4 → 64 tumbling windows kept between requests; B1: 6 → 66 tumbling windows kept. let keeps = |run: &Run, id: &str, bytes: &str| { assert!( run.selection.costs[id] @@ -224,36 +228,42 @@ fn stage3_plan_selection_prices_61_evaluations_per_dashboard_request() { } /// (b) For this explicit fixture, refreshing every minute at 30-second steps -/// supplies panes of +/// supplies tumbling windows of /// `gcd(5 min, 1 min, 30 s) = 30 s`, on the step grid, and Stage 2 keeps -/// them; without the range, the fixture uses 1 min panes. Both configurations +/// them; without the range, the fixture uses 1 min tumbling windows. Both configurations /// must divide the evaluation step; production also offers finer legal widths. #[test] -fn explicit_pane_candidates_follow_the_evaluation_step_grid() { +fn explicit_tumbling_window_candidates_follow_the_evaluation_step_grid() { let step_30s = RangeEvaluation { step_ms: 30_000, span_ms: 3_600_000, }; let range = run_explicit(&exact_panel("max_over_time(m[5m])", 60_000, Some(step_30s))); - let widths: Vec = pane_chains(&range).into_iter().map(|(_, w, _)| w).collect(); - assert!(!widths.is_empty(), "panes are offered"); + let widths: Vec = tumbling_window_chains(&range) + .into_iter() + .map(|(_, w, _)| w) + .collect(); + assert!(!widths.is_empty(), "tumbling windows are offered"); assert!(widths.iter().all(|&w| w == 30_000), "{widths:?}"); assert!(range.pruned.is_empty(), "{:?}", range.pruned); let none = run_explicit(&exact_panel("max_over_time(m[5m])", 60_000, None)); - let widths: Vec = pane_chains(&none).into_iter().map(|(_, w, _)| w).collect(); + let widths: Vec = tumbling_window_chains(&none) + .into_iter() + .map(|(_, w, _)| w) + .collect(); assert!( !widths.is_empty() && widths.iter().all(|&w| w == 60_000), "{widths:?}" ); } -/// (c) A plan that keeps panes reports how long the runtime must keep +/// (c) A plan that keeps tumbling windows reports how long the runtime must keep /// them: the window plus the range-evaluation span, 5 min + 1 h = -/// 3,900,000 ms, so the next refresh's earliest step finds its panes. +/// 3,900,000 ms, so the next refresh's earliest step finds its tumbling windows. /// Without the range it is the window, 300,000 ms. #[test] -fn stage3_plan_selection_reports_the_retention_of_kept_panes() { +fn stage3_plan_selection_reports_the_retention_of_kept_tumbling_windows() { let deployment = keeping_query_time_results(); let query = "max_over_time(m[5m])"; let range = run_explicit_for( @@ -294,7 +304,7 @@ fn stage3_plan_selection_reports_the_retention_of_kept_panes() { } /// (d) Without a range evaluation nothing changes: Pattern B's pinned -/// costs (#509 Example 3 and 4: whole-window KLL 2.3625, panes 3.6112, B3 +/// costs (#509 Example 3 and 4: whole-window KLL 2.3625, tumbling windows 3.6112, B3 /// 436.59, B1 5.232 GB kept) are as before, and a range of one step per /// request (`span_ms: 0`) prices exactly as no range. #[test] @@ -322,14 +332,14 @@ fn no_range_evaluation_leaves_every_price_unchanged() { /// The example where declaring the range makes Stage 3 prefer sharing: /// `max_over_time(m[5m])` every minute over 1M series. As one evaluation /// per minute, rebuilding the window (P1, 20.8281 cost/s) beats the kept -/// panes (P2-m2, 32.2630: 224 MB of 56-byte per-series maxima kept) and the -/// panes maintained at ingestion (P2-m1, 46.2340). As a dashboard over the +/// tumbling windows (P2-m2, 32.2630: 224 MB of 56-byte per-series maxima kept) and the +/// tumbling windows maintained at ingestion (P2-m1, 46.2340). As a dashboard over the /// last hour at 1-minute steps, rebuilding costs 61 times as much -/// (1,270.5151), while the kept panes add only the per-step merges and 1 h -/// of panes (460.0410), and B1 472.2760: the kept panes are selected, and without -/// query-time retention, the ingestion-time panes. +/// (1,270.5151), while the kept tumbling windows add only the per-step merges and 1 h +/// of tumbling windows (460.0410), and B1 472.2760: the kept tumbling windows are selected, and without +/// query-time retention, the ingestion-time tumbling windows. #[test] -fn stage3_plan_selection_prefers_kept_panes_once_the_range_is_declared() { +fn stage3_plan_selection_prefers_kept_tumbling_windows_once_the_range_is_declared() { let query = "max_over_time(m[5m])"; let deployment = keeping_query_time_results(); let none = run_explicit_for(&exact_panel(query, 60_000, None), &deployment); @@ -372,9 +382,9 @@ fn stage3_plan_selection_prefers_kept_panes_once_the_range_is_declared() { /// (e) A chain over an offset input reaches back its offset further: with /// no raw data kept, `max_over_time(m[60m] offset 10m)` every 10 minutes -/// keeps six 10-minute panes ending 10 minutes before each evaluation, so -/// its panes must be kept for 10 min + 60 min = 4,200,000 ms, and the kept -/// memory Stage 3 prices covers that seventh pane; without the offset the +/// keeps six 10-minute tumbling windows ending 10 minutes before each evaluation, so +/// its tumbling windows must be kept for 10 min + 60 min = 4,200,000 ms, and the kept +/// memory Stage 3 prices covers that seventh tumbling window; without the offset the /// retention is the window, 3,600,000 ms. #[test] fn an_offset_chain_is_retained_and_priced_for_its_offset() { diff --git a/crates/integration-tests/tests/reuse_rules.rs b/crates/integration-tests/tests/reuse_rules.rs index 5c37b436b..e4078f997 100644 --- a/crates/integration-tests/tests/reuse_rules.rs +++ b/crates/integration-tests/tests/reuse_rules.rs @@ -109,7 +109,7 @@ fn samples() -> Vec<(&'static str, &'static str, i64, f64)> { } /// Execute `root` at query time over the `samples` bound as raw data at its -/// `Scan`; each `TimeRange` keeps its own window (a tumbling pane's moved +/// `Scan`; each `TimeRange` keeps its own window (a tumbling window's moved /// back by the `TimeShift` below it). fn execute(root: &Rc) -> Vec { let timed = apply_materialization_timings( @@ -118,8 +118,10 @@ fn execute(root: &Rc) -> Vec { &mut NodeToTimedNodeCache::new(), ) .unwrap(); - // Tumbling panes run as their panes, each over its own window. - let timed = asap_types::ir::operator::pane_chain::expand_pane_chains(&timed).unwrap(); + // Tumbling windows run as their tumbling windows, each over its own window. + let timed = + asap_types::ir::operator::tumbling_window_chain::expand_tumbling_window_chains(&timed) + .unwrap(); let dag = compile_physical_asap_dag(&timed).unwrap(); let store = physical_common::raw_data_store(&dag, |_, schema| { samples() @@ -272,7 +274,7 @@ fn own_candidates<'a>( /// same per-job sums. #[test] fn nested_sum_merge_is_selected_and_answers_as_the_nested_sums() { - // Evaluated once, so no tumbling-pane candidates. + // Evaluated once, so no tumbling-window candidates. let query = "sum by (job) (sum_over_time(m[5m]))"; let memo = memo(&exact_queries(&[query]), QueryRecurrence::Unknown); let variants: Vec<_> = choices(&memo, &[OriginOfCandidateForMemoGroup::Pass1]) diff --git a/crates/integration-tests/tests/runtime_handoff.rs b/crates/integration-tests/tests/runtime_handoff.rs index 53e645416..42b8434fe 100644 --- a/crates/integration-tests/tests/runtime_handoff.rs +++ b/crates/integration-tests/tests/runtime_handoff.rs @@ -69,7 +69,7 @@ fn materialized_count(dag: &PhysicalASAPDAG) -> usize { .count() } -/// Without raw data Stage 3 selects ingestion-time panes; the facade's +/// Without raw data Stage 3 selects ingestion-time tumbling windows; the facade's /// export carries that selection rather than resetting every node to /// query time. #[tokio::test] @@ -79,7 +79,7 @@ async fn export_carries_the_selected_materialization() { let dag = output.physical_asap_dag().expect("exports"); assert!( materialized_count(&dag) > 0, - "the selected panes are stored" + "the selected tumbling windows are stored" ); assert!(dag .nodes @@ -109,7 +109,7 @@ async fn installable(workload: &PlanningWorkload) -> InstallablePlan { installable_with_report(workload).await.0 } -fn pane_chain_count(dag: &PhysicalASAPDAG) -> usize { +fn tumbling_window_chain_count(dag: &PhysicalASAPDAG) -> usize { dag.nodes .iter() .filter(|n| { @@ -121,19 +121,19 @@ fn pane_chain_count(dag: &PhysicalASAPDAG) -> usize { .count() } -/// The installable plan expands its selected pane width into contiguous -/// panes, each a materialized view refreshed at ingestion time, and +/// The installable plan expands its selected tumbling-window width into contiguous +/// tumbling windows, each a materialized view refreshed at ingestion time, and /// the executor compiles it. The stage export keeps the chain for costing /// and display. Its one query is answered by the DAG's one root. #[tokio::test] -async fn installable_plan_has_its_pane_chains_expanded() { +async fn installable_plan_has_its_tumbling_window_chains_expanded() { let workload = example4b(); let (installed, report) = installable_with_report(&workload).await; installed.validate().unwrap(); let plan = &installed; let dag = &installed.dag; let views = plan.materialized_views().unwrap(); - assert_eq!(pane_chain_count(dag), 0); + assert_eq!(tumbling_window_chain_count(dag), 0); assert_eq!(materialized_count(dag), views.len()); let roots: Vec = dag.roots.iter().map(|&r| r as u64).collect(); let compiled = compile(dag, &views, &roots).expect("the executor compiles it"); @@ -146,7 +146,7 @@ async fn installable_plan_has_its_pane_chains_expanded() { let refreshed: BTreeSet = views.iter().map(|v| v.node_id as u64).collect(); let slots: BTreeSet = compiled.input_slots().map(|(id, _)| id).collect(); assert_eq!(slots, refreshed); - assert_pane_coverage(plan, dag.roots[0], 0, 3_600_000); + assert_tumbling_window_coverage(plan, dag.roots[0], 0, 3_600_000); assert!( plan.raw_data_retention().unwrap().is_empty(), "no raw data kept" @@ -156,7 +156,7 @@ async fn installable_plan_has_its_pane_chains_expanded() { let staged = plan_for_stages(&workload).await; assert_eq!( - pane_chain_count(&staged), + tumbling_window_chain_count(&staged), 1, "the stage export keeps the chain" ); @@ -235,10 +235,10 @@ fn repeating(queries: &[&str]) -> PlanningWorkload { .unwrap() } -/// p99 and p50 over the same 60 minutes share one chain of panes: the -/// installable plan materializes one shared set of panes. +/// p99 and p50 over the same 60 minutes share one chain of tumbling windows: the +/// installable plan materializes one shared set of tumbling windows. #[tokio::test] -async fn two_queries_on_one_chain_store_its_panes_once() { +async fn two_queries_on_one_chain_store_its_tumbling_windows_once() { let plan = installable(&repeating(&[ "quantile_over_time(0.99, latency_ms[60m])", "quantile_over_time(0.5, latency_ms[60m])", @@ -259,7 +259,7 @@ async fn two_queries_on_one_chain_store_its_panes_once() { views_below(plan.dag.roots[1]) ); assert_eq!(views_below(plan.dag.roots[0]).len(), views.len()); - assert_pane_coverage(&plan, plan.dag.roots[0], 0, 3_600_000); + assert_tumbling_window_coverage(&plan, plan.dag.roots[0], 0, 3_600_000); } /// Planned with raw data kept (everything at query time), so each query's @@ -324,11 +324,11 @@ async fn raw_data_windows_of_unsupported_shapes_are_refused() { } /// Example 4b answered as the last hour at 10-minute steps (a range -/// evaluation): each selected pane view is kept for the hour its window reads +/// evaluation): each selected tumbling window view is kept for the hour its window reads /// plus the hour the range spans, 7,200,000 ms, as Stage 3 records for the /// chain (`Selection::tumbling_window_retention_ms`); without the range, for the hour. #[tokio::test] -async fn stored_panes_carry_stage3_pane_retention() { +async fn stored_tumbling_windows_carry_stage3_tumbling_window_retention() { let mut workload = example4b(); let plain = installable(&workload).await; assert!(plain @@ -341,7 +341,7 @@ async fn stored_panes_carry_stage3_pane_retention() { serde_json::json!({"step_ms": 600000, "span_ms": 3600000}); workload = serde_json::from_value(json).unwrap(); let ranged = installable(&workload).await; - assert_pane_coverage(&ranged, ranged.dag.roots[0], 0, 3_600_000); + assert_tumbling_window_coverage(&ranged, ranged.dag.roots[0], 0, 3_600_000); assert!(ranged .materialized_views() .unwrap() @@ -356,7 +356,7 @@ async fn stored_panes_carry_stage3_pane_retention() { .collect::>(), [7_200_000] ); - // A plan file that keeps a pane for less than it reads is refused. + // A plan file that keeps a tumbling window for less than it reads is refused. let mut short = ranged.clone(); let view = ranged.materialized_views().unwrap()[0].node_id; short @@ -374,20 +374,20 @@ async fn stored_panes_carry_stage3_pane_retention() { )); } -/// A chain over an offset input keeps its panes for its window plus the +/// A chain over an offset input keeps its tumbling windows for its window plus the /// offset: `[60m] offset 10m` reaches 70 minutes back (4,200,000 ms), the /// unshifted query's chain 60 minutes (3,600,000 ms). Each materialized view /// carries its own node's requirement; a runtime that keeps the twins' -/// identical panes once keeps them for the larger one. +/// identical tumbling windows once keeps them for the larger one. #[tokio::test] -async fn an_offset_chain_keeps_its_panes_for_its_window_plus_the_offset() { +async fn an_offset_chain_keeps_its_tumbling_windows_for_its_window_plus_the_offset() { let plan = installable(&repeating(&[ "quantile_over_time(0.99, latency_ms[60m])", "quantile_over_time(0.99, latency_ms[60m] offset 10m)", ])) .await; - assert_pane_coverage(&plan, plan.dag.roots[0], 0, 3_600_000); - assert_pane_coverage(&plan, plan.dag.roots[1], 600_000, 4_200_000); + assert_tumbling_window_coverage(&plan, plan.dag.roots[0], 0, 3_600_000); + assert_tumbling_window_coverage(&plan, plan.dag.roots[1], 600_000, 4_200_000); let below: Vec<_> = plan .dag .roots @@ -401,14 +401,19 @@ async fn an_offset_chain_keeps_its_panes_for_its_window_plus_the_offset() { .filter(|(_, nodes)| nodes.contains(&view.node_id)) .map(|(retention, _)| retention) .max() - .expect("materialized pane is consumed"); + .expect("materialized tumbling window is consumed"); assert_eq!(view.retention_ms(), required); } plan.validate().unwrap(); } -/// Any selected legal pane width must cover the requested interval exactly. -fn assert_pane_coverage(plan: &InstallablePlan, root: PhysicalASAPNodeId, start: i64, end: i64) { +/// Any selected legal tumbling-window width must cover the requested interval exactly. +fn assert_tumbling_window_coverage( + plan: &InstallablePlan, + root: PhysicalASAPNodeId, + start: i64, + end: i64, +) { let below = nodes_below(&plan.dag, root); let mut intervals: Vec<_> = plan .materialized_views() @@ -417,7 +422,7 @@ fn assert_pane_coverage(plan: &InstallablePlan, root: PhysicalASAPNodeId, start: .filter(|view| below.contains(&view.node_id)) .map(|view| { let [window] = view.inputs.raw_data_windows() else { - panic!("pane reads one raw interval: {:?}", view.inputs); + panic!("tumbling window reads one raw interval: {:?}", view.inputs); }; assert!(window.width_ms() > 0 && 600_000 % window.width_ms() == 0); ( diff --git a/crates/integration-tests/tests/sharing_issue_regressions.rs b/crates/integration-tests/tests/sharing_issue_regressions.rs index f2c56e230..d75b91580 100644 --- a/crates/integration-tests/tests/sharing_issue_regressions.rs +++ b/crates/integration-tests/tests/sharing_issue_regressions.rs @@ -81,10 +81,10 @@ fn exact_coarse_sum_reuses_fine_sum() { ); } -/// #924: the 1m sum reads the newest pane of the existing 2m chain. +/// #924: the 1m sum reads the newest tumbling window of the existing 2m chain. #[test] #[ignore = "flaky: depends on wall-clock search budgets (#1067)"] -fn nested_exact_sum_reuses_newest_panes() { +fn nested_exact_sum_reuses_newest_tumbling_windows() { let related = workload(924); let mut base = related.clone(); base.queries.truncate(1); @@ -207,7 +207,7 @@ fn sql_count_topk_has_a_fused_raw_row_heap() { fn exact_rollup_catalog_crosses_identical_per_series_average() { use asap_logical_asap_optimization::{ test_support::{generate_per_computation_candidates, try_populate_pass1}, - PaneWidthDomain, + TumblingWindowWidthDomain, }; use asap_types::stages::OriginOfCandidateForMemoGroup; let roots: Vec<_> = Planner::new(FuzzSettings::default()) @@ -224,7 +224,7 @@ fn exact_rollup_catalog_crosses_identical_per_series_average() { let expanded = asap_logical_asap_optimization::test_support::sharing_rules_and_shared_state_groups( memo, - PaneWidthDomain::SeedGrids, + TumblingWindowWidthDomain::SeedGrids, ) .unwrap(); let owner = expanded.query_roots[1].as_operator().unwrap().0 as usize; @@ -245,7 +245,7 @@ fn filtered_group_preserves_sql_empty_and_null_semantics() { use asap_executor::values::Value; use asap_logical_asap_optimization::{ test_support::{generate_per_computation_candidates, try_populate_pass1}, - PaneWidthDomain, + TumblingWindowWidthDomain, }; use asap_types::ir::{operator::AggIntent, NonASAPOp}; use asap_types::stages::OriginOfCandidateForMemoGroup; @@ -263,7 +263,7 @@ fn filtered_group_preserves_sql_empty_and_null_semantics() { let expanded = asap_logical_asap_optimization::test_support::sharing_rules_and_shared_state_groups( memo, - PaneWidthDomain::SeedGrids, + TumblingWindowWidthDomain::SeedGrids, ) .unwrap(); let (owner, candidate) = expanded.candidates.iter().enumerate().flat_map(|(owner, candidates)| candidates.iter().map(move |c| (owner,c))) @@ -403,7 +403,7 @@ fn sql_count_rollup_executes_with_integer_results() { use asap_executor::values::Value; use asap_logical_asap_optimization::{ test_support::{generate_per_computation_candidates, try_populate_pass1}, - PaneWidthDomain, + TumblingWindowWidthDomain, }; use asap_types::ir::ASAPOp; use asap_types::stages::OriginOfCandidateForMemoGroup; @@ -430,7 +430,7 @@ fn sql_count_rollup_executes_with_integer_results() { let expanded = asap_logical_asap_optimization::test_support::sharing_rules_and_shared_state_groups( memo, - PaneWidthDomain::SeedGrids, + TumblingWindowWidthDomain::SeedGrids, ) .unwrap(); let (owner, candidate) = expanded @@ -533,7 +533,7 @@ fn sql_sum_shares_nested_windows_and_slower_readers() { fn sketch_rollup_crosses_inner_range_function() { use asap_logical_asap_optimization::{ test_support::{generate_per_computation_candidates, try_populate_pass1}, - PaneWidthDomain, + TumblingWindowWidthDomain, }; use asap_types::stages::OriginOfCandidateForMemoGroup; let mut w = workload(923); @@ -556,7 +556,7 @@ fn sketch_rollup_crosses_inner_range_function() { let expanded = asap_logical_asap_optimization::test_support::sharing_rules_and_shared_state_groups( memo, - PaneWidthDomain::SeedGrids, + TumblingWindowWidthDomain::SeedGrids, ) .unwrap(); let owner = expanded.query_roots[1].as_operator().unwrap().0 as usize; diff --git a/crates/integration-tests/tests/sql_trailing_window.rs b/crates/integration-tests/tests/sql_trailing_window.rs index 6b4dd004e..81a229329 100644 --- a/crates/integration-tests/tests/sql_trailing_window.rs +++ b/crates/integration-tests/tests/sql_trailing_window.rs @@ -1,6 +1,6 @@ //! A SQL trailing window, `WHERE ts >= now() - INTERVAL 'w'` on the table's //! time index, plans like a PromQL range selector `m[w]`: Pass 2 builds its -//! summaries in tumbling panes, Stage 2 maintains them at ingestion time, +//! summaries in tumbling windows, Stage 2 maintains them at ingestion time, //! a deployment that keeps no raw data still gets a plan, and Stage 3 prices //! each scan by its window. @@ -105,7 +105,7 @@ async fn run(deployment: &DeploymentCapabilities) -> Run { compose_logical_candidate, generate_per_computation_candidates, }; use asap_logical_asap_optimization::test_support::{ - pane_candidates, window_composition_pass2_candidates, + tumbling_window_candidates, window_composition_pass2_candidates, }; use asap_types::stages::HowToComputeAggregateMeasure; let local = generate_per_computation_candidates( @@ -138,14 +138,16 @@ async fn run(deployment: &DeploymentCapabilities) -> Run { .entries() .map(|entry| entry.evaluation_grid_ms()) .collect::>(); - let panes = pane_candidates(&summary, &grids); - assert_eq!(panes.len(), WINDOWS_MS.len()); + let tumbling_windows = tumbling_window_candidates(&summary, &grids); + assert_eq!(tumbling_windows.len(), WINDOWS_MS.len()); let mut candidates = vec![exact, summary.clone()]; candidates.extend( - window_composition_pass2_candidates(&summary, &panes) + window_composition_pass2_candidates(&summary, &tumbling_windows) .unwrap() .into_iter() - .filter(|candidate| candidate.pane_candidates.len() == panes.len()) + .filter(|candidate| { + candidate.tumbling_window_candidates.len() == tumbling_windows.len() + }) .map(|candidate| candidate.roots.unwrap()), ); let settings = CostInputs::default(); @@ -159,10 +161,10 @@ async fn run(deployment: &DeploymentCapabilities) -> Run { ) } -/// Pass 2 offers each query's distinct-count sketch in tumbling panes, and -/// Stage 2 maintains a pane chain at ingestion time, as for `m[5m]`. +/// Pass 2 offers each query's distinct-count sketch in tumbling windows, and +/// Stage 2 maintains a tumbling-window chain at ingestion time, as for `m[5m]`. #[tokio::test] -async fn sql_windows_get_tumbling_panes_and_ingestion_time_builds() { +async fn sql_windows_get_tumbling_windows_and_ingestion_time_builds() { let run = run(&DeploymentCapabilities::default()).await; for (query, window_ms) in WINDOWS_MS.iter().enumerate() { let tumbling = run.logical.iter().any(|c| { @@ -173,7 +175,7 @@ async fn sql_windows_get_tumbling_panes_and_ingestion_time_builds() { }); assert!( tumbling, - "query {query} ({window_ms} ms) has no pane candidate" + "query {query} ({window_ms} ms) has no tumbling-window candidate" ); } let at_ingestion = run.physical.iter().any(|p| { @@ -184,7 +186,10 @@ async fn sql_windows_get_tumbling_panes_and_ingestion_time_builds() { ) && runs_at_ingestion(p, n.id) }) }); - assert!(at_ingestion, "no pane chain maintained at ingestion time"); + assert!( + at_ingestion, + "no tumbling-window chain maintained at ingestion time" + ); } /// Without raw data at query time, the ingestion-time builds still answer diff --git a/crates/integration-tests/tests/sql_two_percentiles.rs b/crates/integration-tests/tests/sql_two_percentiles.rs index beffb6e34..b868d1ed0 100644 --- a/crates/integration-tests/tests/sql_two_percentiles.rs +++ b/crates/integration-tests/tests/sql_two_percentiles.rs @@ -4,7 +4,7 @@ //! compiles, is priced, and answers the exact one as `percentile_cont` does //! and the sketched one within the sketch's bound. With Q2, the p90 per //! city over the last 20 minutes, Q1 and Q2 share the table scan, and Q2's -//! per-city pane chain serves Q1 through its newest panes rolled up across +//! per-city tumbling-window chain serves Q1 through its newest tumbling windows rolled up across //! cities. use crate::physical_common; @@ -165,7 +165,7 @@ fn within_bound(algorithm: Option<&SketchAlgorithm>, q: f64, answer: f64, values } /// `root`'s answer rows at [`EVALUATION_TIME_MS`] over [`rows`], every node at -/// query time, each pane chain as its panes. +/// query time, each tumbling-window chain as its tumbling windows. fn answers(root: &Rc) -> Vec> { answers_over(root, rows()) } @@ -185,8 +185,9 @@ fn answers_over( ] }) .collect(); - // The executor runs a pane chain as its panes. - let root = asap_types::ir::operator::pane_chain::expand_pane_chains(root).unwrap(); + // The executor runs a tumbling-window chain as its tumbling windows. + let root = asap_types::ir::operator::tumbling_window_chain::expand_tumbling_window_chains(root) + .unwrap(); physical_common::execute_raw_rows_at(&root, table, EVALUATION_TIME_MS) } @@ -298,7 +299,7 @@ async fn stage1(queries: &[&str]) -> Vec<(String, Vec)> { compose_logical_candidate, generate_per_computation_candidates, }; use asap_logical_asap_optimization::test_support::{ - pane_candidates, window_composition_pass2_candidates, + tumbling_window_candidates, window_composition_pass2_candidates, }; use asap_types::stages::HowToComputeAggregateMeasure; let workload = workload(queries); @@ -361,13 +362,15 @@ async fn stage1(queries: &[&str]) -> Vec<(String, Vec)> { for choice in choices { let roots = share(compose_logical_candidate(&local, &choice).unwrap()); candidates.push(roots.clone()); - let panes = pane_candidates(&roots, &grids); - if !panes.is_empty() { + let tumbling_windows = tumbling_window_candidates(&roots, &grids); + if !tumbling_windows.is_empty() { candidates.extend( - window_composition_pass2_candidates(&roots, &panes) + window_composition_pass2_candidates(&roots, &tumbling_windows) .unwrap() .into_iter() - .filter(|candidate| candidate.pane_candidates.len() == panes.len()) + .filter(|candidate| { + candidate.tumbling_window_candidates.len() == tumbling_windows.len() + }) .filter_map(|candidate| candidate.roots.ok()) .map(share), ); @@ -402,11 +405,11 @@ async fn q1_and_q2_share_the_table_scan() { ); } -/// Whether Q2's per-city pane chain (120 panes of 10 s) serves both of -/// Q1's percentiles in `roots`: Q1 merges each city's newest 30 panes (its +/// Whether Q2's per-city tumbling-window chain (120 tumbling windows of 10 s) serves both of +/// Q1's percentiles in `roots`: Q1 merges each city's newest 30 tumbling windows (its /// 5 minutes), then rolls the cities up into one state, which both of its /// estimates read. -fn newest_panes_rolled_up(roots: &[QueryRoot]) -> bool { +fn newest_tumbling_windows_rolled_up(roots: &[QueryRoot]) -> bool { let (q1, q2) = (operator(&roots[0]), operator(&roots[1])); let q2_chains: Vec<*const OperatorNode> = OperatorNode::reachable(q2) .iter() @@ -469,20 +472,21 @@ fn city_values(city: Option<&str>) -> Vec { } /// Q2's per-city chain serves both of Q1's percentiles through its newest -/// 30 panes rolled up across cities. Each query's answers are within the +/// 30 tumbling windows rolled up across cities. Each query's answers are within the /// sketch's bound of the exact `percentile_cont`, and over a window with /// no row Q1 still answers one NULL row, as the global SQL aggregate the /// rollup stands for. #[tokio::test] -async fn q2_pane_chain_serves_q1_through_newest_panes_and_a_rollup() { +async fn q2_tumbling_window_chain_serves_q1_through_newest_tumbling_windows_and_a_rollup() { let candidates = stage1(&[Q1, Q2]).await; for algorithm in [SketchAlgorithm::Kll, SketchAlgorithm::DDSketch] { let (id, roots) = candidates .iter() .find(|(_, roots)| { - newest_panes_rolled_up(roots) && sketch_of(operator(&roots[0])) == Some(algorithm) + newest_tumbling_windows_rolled_up(roots) + && sketch_of(operator(&roots[0])) == Some(algorithm) }) - .unwrap_or_else(|| panic!("no {algorithm:?} newest-panes rollup candidate")); + .unwrap_or_else(|| panic!("no {algorithm:?} newest-tumbling-windows rollup candidate")); let q1 = answers(operator(&roots[0])); let values = values_in(5 * MINUTE_MS, None); assert_eq!(q1.len(), 1, "{id}"); diff --git a/crates/integration-tests/tests/tumbling_pane_merge.rs b/crates/integration-tests/tests/tumbling_window_merge.rs similarity index 73% rename from crates/integration-tests/tests/tumbling_pane_merge.rs rename to crates/integration-tests/tests/tumbling_window_merge.rs index 72cf36487..7f5c7cff2 100644 --- a/crates/integration-tests/tests/tumbling_pane_merge.rs +++ b/crates/integration-tests/tests/tumbling_window_merge.rs @@ -1,7 +1,7 @@ -//! Tumbling panes merged by `SummaryMerge` answer the same per-series query -//! as one build over the whole window (#509 Example 3/4, Pattern B): panes +//! Tumbling windows merged by `SummaryMerge` answer the same per-series query +//! as one build over the whole window (#509 Example 3/4, Pattern B): tumbling windows //! written by hand, and the planner's window-composition rule's -//! `SummaryTumblingWindowChain`, expanded into the panes the executor runs. +//! `SummaryTumblingWindowChain`, expanded into the tumbling windows the executor runs. use crate::physical_common; use std::collections::BTreeMap; @@ -16,11 +16,11 @@ use asap_executor::values::{Batch, Value}; use asap_integration_tests::fixtures::lower_promql; use asap_integration_tests::planning::{explicit_local_candidates, explicit_summary_candidate}; use asap_logical_asap_optimization::test_support::{ - pane_candidates, window_composition_pass2_candidates, + tumbling_window_candidates, window_composition_pass2_candidates, window_composition_shared_chain_candidates, }; use asap_types::ir::operator::operator_properties::TimeShift; -use asap_types::ir::operator::pane_chain::expand_pane_chains; +use asap_types::ir::operator::tumbling_window_chain::expand_tumbling_window_chains; use asap_types::ir::physical_export::PhysicalASAPDAG; use asap_types::ir::properties::{AccuracyMetric, AccuracyTarget}; use asap_types::ir::schema::Schema; @@ -32,10 +32,10 @@ use asap_types::physical::{Materialization, Medium}; use asap_types::test_support::compile_physical_asap_dag_with_node_ids; const EVALUATION_MS: i64 = 300_000; -const PANE_MS: i64 = 60_000; -const PANES: i64 = 5; +const TUMBLING_WINDOW_WIDTH_MS: i64 = 60_000; +const TUMBLING_WINDOW_COUNT: i64 = 5; -/// An explicit supported single-build fixture for the pane merge rules. +/// An explicit supported single-build fixture for the tumbling-window merge rules. fn single_build(query: &str, accuracy: AccuracyTarget) -> Rc { let root = lower_promql(query, accuracy.clone()).unwrap(); let root = promql_rows::with_series_identity(&root).unwrap(); @@ -49,7 +49,7 @@ fn single_build(query: &str, accuracy: AccuracyTarget) -> Rc { }) .expect("an exact accumulator fixture") } else { - // Pin DDS so the actual positive-sample interpolation and pane merge + // Pin DDS so the actual positive-sample interpolation and tumbling-window merge // checks do not depend on the optimizer's preferred implementation. explicit_summary_candidate(root, asap_types::ir::schema::SketchAlgorithm::DDSketch) } @@ -63,26 +63,26 @@ fn summary_agg(root: &Rc) -> Rc { .expect("a summary build") } -/// Rewrite `root` so its summary is built in tumbling panes: pane `i` is +/// Rewrite `root` so its summary is built in tumbling windows: tumbling window `i` is /// `TimeRange(1m)` over `TimeShift(i·1m)` over the same `Scan`, and a -/// `SummaryMerge` combines the panes. Returns the root and the panes. -fn tumbling_panes(root: &Rc) -> (Rc, Vec>) { +/// `SummaryMerge` combines the tumbling windows. Returns the root and the tumbling windows. +fn tumbling_windows(root: &Rc) -> (Rc, Vec>) { let agg = summary_agg(root); let Some(NonASAPOp::TimeRange { kind, child, .. }) = agg.children()[0].non_asap() else { panic!("expected a raw time range under the summary"); }; - let panes: Vec<_> = (0..PANES) + let tumbling_windows: Vec<_> = (0..TUMBLING_WINDOW_COUNT) .map(|i| { let shifted = OperatorNode::new_shared(Operator::NonASAP(NonASAPOp::TimeShift { shift: TimeShift { - offset_ms: i * PANE_MS, + offset_ms: i * TUMBLING_WINDOW_WIDTH_MS, at: None, }, child: child.clone(), })) .unwrap(); let range = OperatorNode::new_shared(Operator::NonASAP(NonASAPOp::TimeRange { - range: std::time::Duration::from_millis(PANE_MS as u64), + range: std::time::Duration::from_millis(TUMBLING_WINDOW_WIDTH_MS as u64), kind: *kind, child: shifted, })) @@ -91,23 +91,24 @@ fn tumbling_panes(root: &Rc) -> (Rc, Vec) -> (Rc, Rc) { +/// into the tumbling windows the executor runs. +fn planned_tumbling_windows(root: &Rc) -> (Rc, Rc) { let roots = [QueryRoot::Operator(Rc::clone(root))]; - let rule_candidates = pane_candidates(&roots, &[Some(PANE_MS as u64)]); + let rule_candidates = + tumbling_window_candidates(&roots, &[Some(TUMBLING_WINDOW_WIDTH_MS as u64)]); let [rule_candidate] = rule_candidates.as_slice() else { panic!("one build to split: {rule_candidates:?}") }; @@ -116,7 +117,7 @@ fn planned_panes(root: &Rc) -> (Rc, Rc rule_candidate.tumbling_window_width_ms, rule_candidate.tumbling_window_count ), - (PANE_MS as u64, 5) + (TUMBLING_WINDOW_WIDTH_MS as u64, 5) ); let mut pass2_candidates = window_composition_pass2_candidates(&roots, &rule_candidates).unwrap(); @@ -128,9 +129,9 @@ fn planned_panes(root: &Rc) -> (Rc, Rc planned.validate_structure().unwrap(); assert_eq!( planned.guarantee, root.guarantee, - "merged panes keep the whole build's guarantee" + "merged tumbling windows keep the whole build's guarantee" ); - let expanded = expand_pane_chains(&planned).unwrap(); + let expanded = expand_tumbling_window_chains(&planned).unwrap(); expanded.validate_structure().unwrap(); (planned, expanded) } @@ -155,13 +156,13 @@ fn replace( type RawDataSample = (&'static str, i64, f64); /// Two series, four positive samples per minute in [0, 5m), each -/// `phase_ms` after its 15-s step (0: on the pane boundaries). +/// `phase_ms` after its 15-s step (0: on the tumbling-window boundaries). fn samples(phase_ms: i64) -> Vec { let mut rows = vec![]; for (s, series) in ["a", "b"].into_iter().enumerate() { - for minute in 0..PANES { + for minute in 0..TUMBLING_WINDOW_COUNT { for j in 0..4 { - let ts = minute * PANE_MS + j * 15_000 + phase_ms; + let ts = minute * TUMBLING_WINDOW_WIDTH_MS + j * 15_000 + phase_ms; rows.push((series, ts, (s * 100) as f64 + (minute * 4 + j + 1) as f64)); } } @@ -171,7 +172,7 @@ fn samples(phase_ms: i64) -> Vec { struct Exported { dag: PhysicalASAPDAG, - /// Each `SummaryAgg` node with its pane. + /// Each `SummaryAgg` node with its tumbling window. builds: Vec<(u64, i64)>, } @@ -179,9 +180,9 @@ fn export(root: &Rc, assignment: &MaterializationAssignment) -> Ex let timed = apply_materialization_timings(root, assignment, &mut NodeToTimedNodeCache::new()).unwrap(); let compiled = compile_physical_asap_dag_with_node_ids(&timed).unwrap(); - let pane = |node: &OperatorNode| -> i64 { + let tumbling_window = |node: &OperatorNode| -> i64 { match node.children()[0].non_asap() { - Some(NonASAPOp::TimeShift { shift, .. }) => shift.offset_ms / PANE_MS, + Some(NonASAPOp::TimeShift { shift, .. }) => shift.offset_ms / TUMBLING_WINDOW_WIDTH_MS, _ => 0, } }; @@ -189,7 +190,7 @@ fn export(root: &Rc, assignment: &MaterializationAssignment) -> Ex for node in &compiled.dag.nodes { let logical = compiled.node_ids.operator_node(node.id).unwrap(); if let Operator::ASAP(ASAPOp::SummaryAgg { child, .. }) = &logical.operator { - builds.push((node.id as u64, pane(child))); + builds.push((node.id as u64, tumbling_window(child))); } } Exported { @@ -252,7 +253,7 @@ fn query_scope() -> Scope { } } -/// Compile `root` with everything at query time (Pattern B2: panes rebuilt +/// Compile `root` with everything at query time (Pattern B2: tumbling windows rebuilt /// from raw rows at query time). fn compile_at_query_time( root: &Rc, @@ -268,23 +269,26 @@ fn execute(root: &Rc, samples: &[RawDataSample]) -> Vec run(&plan, raw_store(&exported.dag, samples), query_scope()) } -/// Build the panes in ingestion runs (stored `MatAtIngestion`, each with its +/// Build the tumbling windows in ingestion runs (stored `MatAtIngestion`, each with its /// own build timestamp), then merge them in the query run (Pattern B1). fn execute_retained( root: &Rc, - panes: &[Rc], + tumbling_windows: &[Rc], samples: &[RawDataSample], ) -> Vec> { let mut assignment = MaterializationAssignment::default(); - for pane in panes { - assignment.set(pane, Materialization::MatAtIngestion(Medium::Memory)); + for tumbling_window in tumbling_windows { + assignment.set( + tumbling_window, + Materialization::MatAtIngestion(Medium::Memory), + ); } execute_retained_as(root, &assignment, samples) } -/// The planner's pane chain maintained at ingestion time, as Stage 2's B1 +/// The planner's tumbling-window chain maintained at ingestion time, as Stage 2's B1 /// option assigns it (`MatAtIngestion` on the chain), then expanded: each -/// pane carries the chain's timing and materialization, so the panes are +/// tumbling window carries the chain's timing and materialization, so the tumbling windows are /// built in ingestion runs and merged in the query run. fn execute_maintained_chain( planned: &Rc, @@ -293,19 +297,19 @@ fn execute_maintained_chain( let chain = OperatorNode::reachable(planned) .into_iter() .find(|n| matches!(n.asap(), Some(ASAPOp::SummaryTumblingWindowChain { .. }))) - .expect("a pane chain"); + .expect("a tumbling-window chain"); let mut assignment = MaterializationAssignment::default(); assignment.set(&chain, Materialization::MatAtIngestion(Medium::Memory)); let timed = apply_materialization_timings(planned, &assignment, &mut NodeToTimedNodeCache::new()) .unwrap(); - let expanded = expand_pane_chains(&timed).unwrap(); - for pane in OperatorNode::reachable(&expanded) + let expanded = expand_tumbling_window_chains(&timed).unwrap(); + for tumbling_window in OperatorNode::reachable(&expanded) .iter() .filter(|n| matches!(n.asap(), Some(ASAPOp::SummaryAgg { .. }))) { assert_eq!( - pane.materialization, + tumbling_window.materialization, Materialization::MatAtIngestion(Medium::Memory) ); } @@ -320,16 +324,16 @@ fn execute_retained_as( let mut exported = export(root, assignment); let builds: Vec = exported.builds.iter().map(|&(build, _)| build).collect(); let views = views_at(&mut exported.dag, &builds).unwrap(); - // Each pane's ingestion run refreshes its view from the raw samples. + // Each tumbling window's ingestion run refreshes its view from the raw samples. let mut store = InMemoryDataStore::default(); - for &(build, pane) in &exported.builds { + for &(build, tumbling_window) in &exported.builds { let plan = compile(&exported.dag, &[], &[build]).unwrap(); - let end = EVALUATION_MS - pane * PANE_MS; + let end = EVALUATION_MS - tumbling_window * TUMBLING_WINDOW_WIDTH_MS; let rows = run( &plan, raw_store(&exported.dag, samples), Scope::Ingestion { - window_start_ms: end - PANE_MS, + window_start_ms: end - TUMBLING_WINDOW_WIDTH_MS, window_end_ms: end, }, ); @@ -357,38 +361,42 @@ fn values(rows: &[Vec]) -> Vec { } /// Returns the per-series values, which every execution path agrees on. -fn assert_pane_merge_matches_single_build( +fn assert_tumbling_window_merge_matches_single_build( query: &str, accuracy: AccuracyTarget, samples: &[RawDataSample], ) -> Vec { let single = single_build(query, accuracy); - let (panes, pane_builds) = tumbling_panes(&single); + let (tumbling_windows, tumbling_window_builds) = tumbling_windows(&single); let expected = execute(&single, samples); assert_eq!(expected.len(), 2, "{expected:?}"); assert!(expected.iter().all(|row| has_timestamp(row, EVALUATION_MS))); let result = values(&expected); let expected = render(&expected); assert_eq!( - render(&execute(&panes, samples)), + render(&execute(&tumbling_windows, samples)), expected, - "panes rebuilt at query time" + "tumbling windows rebuilt at query time" ); assert_eq!( - render(&execute_retained(&panes, &pane_builds, samples)), + render(&execute_retained( + &tumbling_windows, + &tumbling_window_builds, + samples + )), expected, - "retained panes" + "retained tumbling windows" ); - let (planned, expanded) = planned_panes(&single); + let (planned, expanded) = planned_tumbling_windows(&single); assert_eq!( render(&execute(&expanded, samples)), expected, - "the planner's pane chain, expanded" + "the planner's tumbling-window chain, expanded" ); assert_eq!( render(&execute_maintained_chain(&planned, samples)), expected, - "the planner's pane chain, maintained at ingestion time" + "the planner's tumbling-window chain, maintained at ingestion time" ); let unexpanded = compile_at_query_time(&planned).err().unwrap_or_default(); assert!( @@ -398,11 +406,11 @@ fn assert_pane_merge_matches_single_build( result } -/// Exact per-series Sum over 5 merged 1-minute panes equals one 5-minute +/// Exact per-series Sum over 5 merged 1-minute tumbling windows equals one 5-minute /// build, timestamped at the evaluation time. #[test] -fn exact_sum_over_tumbling_panes_matches_single_build() { - let sums = assert_pane_merge_matches_single_build( +fn exact_sum_over_tumbling_windows_matches_single_build() { + let sums = assert_tumbling_window_merge_matches_single_build( "sum_over_time(m[5m])", AccuracyTarget::exact(), // 1 ms past each step: the window `(0, 5m]` holds all 20 samples. @@ -411,11 +419,11 @@ fn exact_sum_over_tumbling_panes_matches_single_build() { assert_eq!(sums, vec![210., 2210.]); } -/// The selected quantile sketch over 5 merged 1-minute panes equals one +/// The selected quantile sketch over 5 merged 1-minute tumbling windows equals one /// 5-minute build, within the target of the exact p99 (20 and 120). #[test] -fn quantile_over_tumbling_panes_matches_single_build() { - let p99 = assert_pane_merge_matches_single_build( +fn quantile_over_tumbling_windows_matches_single_build() { + let p99 = assert_tumbling_window_merge_matches_single_build( "quantile_over_time(0.99, m[5m])", AccuracyTarget::max_error(0.05).accuracy_metric(AccuracyMetric::Relative), &samples(0), @@ -431,13 +439,16 @@ fn quantile_over_tumbling_panes_matches_single_build() { } } -/// Exact counter Rate over 5 merged 1-minute panes equals one 5-minute +/// Exact counter Rate over 5 merged 1-minute tumbling windows equals one 5-minute /// build: the merged increase is divided by the whole 5-minute window, not -/// by one pane's minute. +/// by one tumbling window's minute. #[test] -fn exact_rate_over_tumbling_panes_matches_single_build() { - let rates = - assert_pane_merge_matches_single_build("rate(m[5m])", AccuracyTarget::exact(), &samples(0)); +fn exact_rate_over_tumbling_windows_matches_single_build() { + let rates = assert_tumbling_window_merge_matches_single_build( + "rate(m[5m])", + AccuracyTarget::exact(), + &samples(0), + ); assert_eq!(rates.len(), 2); // 19 counted over 285 s of samples, extrapolated to the 5-min window. for rate in rates { @@ -445,12 +456,12 @@ fn exact_rate_over_tumbling_panes_matches_single_build() { } } -/// With samples off the pane boundaries, Increase extrapolates to the +/// With samples off the tumbling-window boundaries, Increase extrapolates to the /// start of the whole 5-minute window, as one build does, not to the start -/// of the newest pane. +/// of the newest tumbling window. #[test] -fn exact_increase_off_the_pane_boundaries_matches_single_build() { - let increases = assert_pane_merge_matches_single_build( +fn exact_increase_off_the_tumbling_window_boundaries_matches_single_build() { + let increases = assert_tumbling_window_merge_matches_single_build( "increase(m[5m])", AccuracyTarget::exact(), &samples(7_000), @@ -482,7 +493,7 @@ fn offset_counter_window_ends_at_the_offset() { let plain = whole_build_values_at(&format!("{function}(m[5m])"), EVALUATION_MS, &samples); let shifted = whole_build_values_at( &format!("{function}(m[5m] offset 1m)"), - EVALUATION_MS + PANE_MS, + EVALUATION_MS + TUMBLING_WINDOW_WIDTH_MS, &samples, ); assert_eq!(shifted, plain, "{function}"); @@ -501,32 +512,32 @@ fn anchored_counter_window_ends_at_the_anchor() { let at_300 = whole_build_values_at(&format!("{function}(m[5m])"), EVALUATION_MS, &samples); let at_240 = whole_build_values_at( &format!("{function}(m[5m])"), - EVALUATION_MS - PANE_MS, + EVALUATION_MS - TUMBLING_WINDOW_WIDTH_MS, &samples, ); assert_ne!(at_300, at_240, "{function}: the two windows differ"); let anchored = whole_build_values_at( &format!("{function}(m[5m] @ 300)"), - EVALUATION_MS + PANE_MS, + EVALUATION_MS + TUMBLING_WINDOW_WIDTH_MS, &samples, ); assert_eq!(anchored, at_300, "{function} @ 300"); let anchored_offset = whole_build_values_at( &format!("{function}(m[5m] @ 300 offset 1m)"), - EVALUATION_MS + PANE_MS, + EVALUATION_MS + TUMBLING_WINDOW_WIDTH_MS, &samples, ); assert_eq!(anchored_offset, at_240, "{function} @ 300 offset 1m"); } } -/// Panes whose ranges read the scan through two stacked `TimeShift`s (the -/// pane's shift over a zero shift) compile and answer as the single build: +/// Tumbling windows whose ranges read the scan through two stacked `TimeShift`s (the +/// tumbling window's shift over a zero shift) compile and answer as the single build: /// the per-entity build looks through every shift to its raw source. #[test] -fn panes_over_stacked_time_shifts_match_single_build() { +fn tumbling_windows_over_stacked_time_shifts_match_single_build() { let single = single_build("increase(m[5m])", AccuracyTarget::exact()); - let (panes, _) = tumbling_panes(&single); + let (tumbling_windows, _) = tumbling_windows(&single); let agg = summary_agg(&single); let Some(NonASAPOp::TimeRange { child: scan, .. }) = agg.children()[0].non_asap() else { panic!("expected a raw time range under the summary"); @@ -539,17 +550,17 @@ fn panes_over_stacked_time_shifts_match_single_build() { child: scan.clone(), })) .unwrap(); - let stacked = replace(&panes, scan, &zero_shift); + let stacked = replace(&tumbling_windows, scan, &zero_shift); let expected = render(&execute(&single, &samples(0))); assert_eq!(expected.len(), 2, "{expected:?}"); assert_eq!(render(&execute(&stacked, &samples(0))), expected); } -/// Exact counter Increase over 5 merged 1-minute panes equals one 5-minute -/// build: the merge joins consecutive panes across their boundaries. +/// Exact counter Increase over 5 merged 1-minute tumbling windows equals one 5-minute +/// build: the merge joins consecutive tumbling windows across their boundaries. #[test] -fn exact_increase_over_tumbling_panes_matches_single_build() { - let increases = assert_pane_merge_matches_single_build( +fn exact_increase_over_tumbling_windows_matches_single_build() { + let increases = assert_tumbling_window_merge_matches_single_build( "increase(m[5m])", AccuracyTarget::exact(), &samples(0), @@ -557,18 +568,18 @@ fn exact_increase_over_tumbling_panes_matches_single_build() { assert_eq!(increases.len(), 2); } -/// A middle pane with no samples, for one series or for every series: the -/// merge joins the panes on either side of the empty one, and Increase +/// A middle tumbling window with no samples, for one series or for every series: the +/// merge joins the tumbling windows on either side of the empty one, and Increase /// matches one 5-minute build. #[test] -fn exact_increase_over_an_empty_middle_pane_matches_single_build() { - let middle = 2 * PANE_MS..3 * PANE_MS; +fn exact_increase_over_an_empty_middle_tumbling_window_matches_single_build() { + let middle = 2 * TUMBLING_WINDOW_WIDTH_MS..3 * TUMBLING_WINDOW_WIDTH_MS; for emptied in [&["a"][..], &["a", "b"]] { let gapped: Vec = samples(0) .into_iter() .filter(|(series, ts, _)| !(emptied.contains(series) && middle.contains(ts))) .collect(); - let increases = assert_pane_merge_matches_single_build( + let increases = assert_tumbling_window_merge_matches_single_build( "increase(m[5m])", AccuracyTarget::exact(), &gapped, @@ -577,12 +588,12 @@ fn exact_increase_over_an_empty_middle_pane_matches_single_build() { } } -/// A counter over `[5m] offset 1m` split into the planner's panes, read +/// A counter over `[5m] offset 1m` split into the planner's tumbling windows, read /// at query time or maintained at ingestion time, matches one build: the -/// panes' shifts fold the outer offset, and the merged counter window ends +/// tumbling windows' shifts fold the outer offset, and the merged counter window ends /// at the evaluation time minus the offset. #[test] -fn a_counter_over_panes_with_an_outer_offset_matches_single_build() { +fn a_counter_over_tumbling_windows_with_an_outer_offset_matches_single_build() { let samples = samples(7_000); for function in ["rate", "increase"] { let single = single_build( @@ -591,40 +602,43 @@ fn a_counter_over_panes_with_an_outer_offset_matches_single_build() { ); let expected = render(&execute(&single, &samples)); assert_eq!(expected.len(), 2, "{function}: {expected:?}"); - let (planned, expanded) = planned_panes(&single); + let (planned, expanded) = planned_tumbling_windows(&single); assert_eq!( render(&execute(&expanded, &samples)), expected, - "{function}: expanded panes" + "{function}: expanded tumbling windows" ); assert_eq!( render(&execute_maintained_chain(&planned, &samples)), expected, - "{function}: panes maintained at ingestion time" + "{function}: tumbling windows maintained at ingestion time" ); } } -/// `root`'s summary built from panes `(shift, width)` merged by one +/// `root`'s summary built from tumbling windows `(shift, width)` merged by one /// `SummaryMerge`, both in minutes, or the IR's refusal of that merge. -fn panes_at(root: &Rc, panes: &[(i64, i64)]) -> Result, String> { +fn tumbling_windows_at( + root: &Rc, + tumbling_windows: &[(i64, i64)], +) -> Result, String> { let agg = summary_agg(root); let Some(NonASAPOp::TimeRange { kind, child, .. }) = agg.children()[0].non_asap() else { panic!("expected a raw time range under the summary"); }; - let children = panes + let children = tumbling_windows .iter() .map(|&(shift, width)| { let shifted = OperatorNode::new_shared(Operator::NonASAP(NonASAPOp::TimeShift { shift: TimeShift { - offset_ms: shift * PANE_MS, + offset_ms: shift * TUMBLING_WINDOW_WIDTH_MS, at: None, }, child: child.clone(), })) .unwrap(); let range = OperatorNode::new_shared(Operator::NonASAP(NonASAPOp::TimeRange { - range: std::time::Duration::from_millis((width * PANE_MS) as u64), + range: std::time::Duration::from_millis((width * TUMBLING_WINDOW_WIDTH_MS) as u64), kind: *kind, child: shifted, })) @@ -641,29 +655,29 @@ fn panes_at(root: &Rc, panes: &[(i64, i64)]) -> Result Vec> { pass2_candidates .iter() - .find(|c| c.pane_candidates.len() == 2 && c.shared_chains.is_empty() != shared) - .expect("both builds in panes") + .find(|c| { + c.tumbling_window_candidates.len() == 2 && c.shared_chains.is_empty() != shared + }) + .expect("both builds in tumbling windows") .roots .as_ref() .unwrap() @@ -704,10 +720,13 @@ fn newest_panes_of_a_shared_chain_match_a_separate_chain() { assert_eq!(newest, Some(Some(2))); let data = samples(0); for (query, (shared, separate)) in shared.iter().zip(&separate).enumerate() { - let expected = execute(&expand_pane_chains(separate).unwrap(), &data); + let expected = execute(&expand_tumbling_window_chains(separate).unwrap(), &data); assert!(!expected.is_empty()); assert_eq!( - render(&execute(&expand_pane_chains(shared).unwrap(), &data)), + render(&execute( + &expand_tumbling_window_chains(shared).unwrap(), + &data + )), render(&expected), "Q{}: the shared chain rebuilt at query time", query + 1 @@ -736,13 +755,13 @@ fn execute_ranges_at_their_windows( run(&plan, raw_store(&exported.dag, samples), scope) } -/// A 5-min quantile every 30 s (10 × 30-s panes) and a 5-min quantile -/// every minute (5 × 1-min panes): the rule offers one chain of 30-s -/// panes both read in full, each at its own interval. Expanded, each +/// A 5-min quantile every 30 s (10 × 30-s tumbling windows) and a 5-min quantile +/// every minute (5 × 1-min tumbling windows): the rule offers one chain of 30-s +/// tumbling windows both read in full, each at its own interval. Expanded, each /// query's answer from the shared chain equals one build over its window, /// at 5 min, and for the 30-s query also at 4.5 min, off the 1-min grid. #[test] -fn queries_at_different_intervals_read_one_chain_of_the_finer_panes() { +fn queries_at_different_intervals_read_one_chain_of_the_finer_tumbling_windows() { let accuracy = AccuracyTarget::max_error(0.05).accuracy_metric(AccuracyMetric::Relative); let singles = [ single_build("quantile_over_time(0.5, m[5m])", accuracy.clone()), @@ -752,16 +771,19 @@ fn queries_at_different_intervals_read_one_chain_of_the_finer_panes() { .iter() .map(|root| QueryRoot::Operator(Rc::clone(root))) .collect(); - let rule_candidates = pane_candidates(&roots, &[Some(30_000), Some(PANE_MS as u64)]); + let rule_candidates = tumbling_window_candidates( + &roots, + &[Some(30_000), Some(TUMBLING_WINDOW_WIDTH_MS as u64)], + ); let widths: Vec = rule_candidates .iter() .map(|c| c.tumbling_window_width_ms) .collect(); - assert_eq!(widths, [30_000, PANE_MS as u64]); + assert_eq!(widths, [30_000, TUMBLING_WINDOW_WIDTH_MS as u64]); let shared = window_composition_pass2_candidates(&roots, &rule_candidates) .unwrap() .into_iter() - .find(|c| c.pane_candidates.len() == 2 && !c.shared_chains.is_empty()) + .find(|c| c.tumbling_window_candidates.len() == 2 && !c.shared_chains.is_empty()) .expect("one shared chain"); // The whole set's shared chains, built alone, are the same candidates. let alone = window_composition_shared_chain_candidates(&roots, &rule_candidates); @@ -781,7 +803,7 @@ fn queries_at_different_intervals_read_one_chain_of_the_finer_panes() { .unwrap() .into_iter() .map(|root| match root { - QueryRoot::Operator(node) => expand_pane_chains(&node).unwrap(), + QueryRoot::Operator(node) => expand_tumbling_window_chains(&node).unwrap(), QueryRoot::Scalar(_) => panic!("an operator root"), }) .collect(); @@ -801,13 +823,13 @@ fn queries_at_different_intervals_read_one_chain_of_the_finer_panes() { } } -/// A 5-min quantile every minute (5 × 1-min panes) and a 3-min one every -/// 90 s (2 × 90-s panes): neither width divides the other, so the rule -/// offers one chain at their gcd, 10 × 30-s panes, the 3-min query merging +/// A 5-min quantile every minute (5 × 1-min tumbling windows) and a 3-min one every +/// 90 s (2 × 90-s tumbling windows): neither width divides the other, so the rule +/// offers one chain at their gcd, 10 × 30-s tumbling windows, the 3-min query merging /// the newest 6. Expanded, each query's answer from the shared chain equals /// one build over its window, at times on each query's own grid. #[test] -fn queries_at_60_and_90_seconds_read_one_chain_of_30_second_panes() { +fn queries_at_60_and_90_seconds_read_one_chain_of_30_second_tumbling_windows() { let accuracy = AccuracyTarget::max_error(0.05).accuracy_metric(AccuracyMetric::Relative); let singles = [ single_build("quantile_over_time(0.5, m[5m])", accuracy.clone()), @@ -817,16 +839,19 @@ fn queries_at_60_and_90_seconds_read_one_chain_of_30_second_panes() { .iter() .map(|root| QueryRoot::Operator(Rc::clone(root))) .collect(); - let rule_candidates = pane_candidates(&roots, &[Some(PANE_MS as u64), Some(90_000)]); + let rule_candidates = tumbling_window_candidates( + &roots, + &[Some(TUMBLING_WINDOW_WIDTH_MS as u64), Some(90_000)], + ); let widths: Vec<(u64, u32)> = rule_candidates .iter() .map(|c| (c.tumbling_window_width_ms, c.tumbling_window_count)) .collect(); - assert_eq!(widths, [(PANE_MS as u64, 5), (90_000, 2)]); + assert_eq!(widths, [(TUMBLING_WINDOW_WIDTH_MS as u64, 5), (90_000, 2)]); let shared = window_composition_pass2_candidates(&roots, &rule_candidates) .unwrap() .into_iter() - .find(|c| c.pane_candidates.len() == 2 && !c.shared_chains.is_empty()) + .find(|c| c.tumbling_window_candidates.len() == 2 && !c.shared_chains.is_empty()) .expect("one shared chain at the gcd"); assert_eq!( ( @@ -841,7 +866,7 @@ fn queries_at_60_and_90_seconds_read_one_chain_of_30_second_panes() { .unwrap() .into_iter() .map(|root| match root { - QueryRoot::Operator(node) => expand_pane_chains(&node).unwrap(), + QueryRoot::Operator(node) => expand_tumbling_window_chains(&node).unwrap(), QueryRoot::Scalar(_) => panic!("an operator root"), }) .collect(); diff --git a/crates/logical-asap-optimization/src/deployment_capabilities.rs b/crates/logical-asap-optimization/src/deployment_capabilities.rs index 220a4e988..945d72cb3 100644 --- a/crates/logical-asap-optimization/src/deployment_capabilities.rs +++ b/crates/logical-asap-optimization/src/deployment_capabilities.rs @@ -8,7 +8,7 @@ //! its state, and a readout of it when the executor can answer that //! statistic ([`StatisticKind`]) from the state. Stage 1 (Pass 1) offers //! no summary candidate whose family or readout is unsupported, so no -//! Pass 2 candidate has one either (Pass 2 rules share, split into panes +//! Pass 2 candidate has one either (Pass 2 rules share, split into tumbling windows //! or reuse existing builds; none adds a family or a readout). Exact //! accumulators and plain exact execution are always supported; //! * whether raw data samples are retained. If they are, Stage 3 counts them on @@ -19,9 +19,9 @@ //! rows. Stored data (at rest or mixed) with no raw data retained is a //! contradiction, refused as bad input ([`DeploymentCapabilities::validate`]); //! * whether query-time results are retained across evaluations. If they -//! are, Stage 2 also offers a tumbling-pane chain whose readers evaluate -//! together at its pane width as `MatAtQueryKeptAcrossEvaluations`: each -//! evaluation builds one new pane and keeps the others for the next ones, +//! are, Stage 2 also offers a tumbling-window chain whose readers evaluate +//! together at its tumbling-window width as `MatAtQueryKeptAcrossEvaluations`: each +//! evaluation builds one new tumbling window and keeps the others for the next ones, //! covering the readers' longest look-back. A chain two or more queries //! read keeps its `MatAtQuery` option too (shared within one evaluation), //! and Stage 3 picks the cheaper. If they are not, a query-time result is diff --git a/crates/logical-asap-optimization/src/lib.rs b/crates/logical-asap-optimization/src/lib.rs index 8a4bd9f6c..539f8bc05 100644 --- a/crates/logical-asap-optimization/src/lib.rs +++ b/crates/logical-asap-optimization/src/lib.rs @@ -83,13 +83,15 @@ pub use pass1::{ generate_per_computation_logical_memo_candidates, PerComputationCandidateError, PerComputationLogicalMemoCandidatesError, }; -// Pass 2: its entry, the state it keeps between pane-width rounds, and the -// pane-width domain of a round. +// Pass 2: its entry, the state it keeps between tumbling-window-width rounds, and the +// tumbling-window-width domain of a round. pub use pass2::logical_memo_candidates::{ generate_logical_asap_memo_candidates, LogicalASAPMemoCandidatesError, Pass2RoundState, }; pub use pass2::memo::candidates::CandidatePopulationError; -pub use pass2::memo::shared_configurations::{PaneWidthDomain, PaneWidthFrontier}; +pub use pass2::memo::shared_configurations::{ + TumblingWindowWidthDomain, TumblingWindowWidthFrontier, +}; pub use pass2::memo::state_groups::StateGroupExpansionError; // The CandidateLogicalASAPDAGs a memo stands for (developer tools' // reference pricing). diff --git a/crates/logical-asap-optimization/src/pass1/logical_memo_candidates.rs b/crates/logical-asap-optimization/src/pass1/logical_memo_candidates.rs index 08b452498..872c68173 100644 --- a/crates/logical-asap-optimization/src/pass1/logical_memo_candidates.rs +++ b/crates/logical-asap-optimization/src/pass1/logical_memo_candidates.rs @@ -154,7 +154,7 @@ fn size_for_failure_probability( mod tests { use super::*; use crate::pass2::memo::generate_dag_of_logical_memo_groups; - use crate::pass2::memo::shared_configurations::PaneWidthDomain; + use crate::pass2::memo::shared_configurations::TumblingWindowWidthDomain; use crate::test_support::{ lower_promql, shared_summary_state_groups, sharing_rules_and_shared_state_groups, }; @@ -314,7 +314,9 @@ mod tests { ], &data(Some(10_000)), ); - let memo = sharing_rules_and_shared_state_groups(memo, PaneWidthDomain::SeedGrids).unwrap(); + let memo = + sharing_rules_and_shared_state_groups(memo, TumblingWindowWidthDomain::SeedGrids) + .unwrap(); let found = kll_failure_probabilities( shared_summary_state_groups(&memo) .iter() diff --git a/crates/logical-asap-optimization/src/pass1/per_computation_candidates.rs b/crates/logical-asap-optimization/src/pass1/per_computation_candidates.rs index 337b1cec6..de93777bc 100644 --- a/crates/logical-asap-optimization/src/pass1/per_computation_candidates.rs +++ b/crates/logical-asap-optimization/src/pass1/per_computation_candidates.rs @@ -3842,12 +3842,12 @@ mod tests { } } - /// A quantile read from merged tumbling panes of a metric's samples is + /// A quantile read from merged tumbling windows of a metric's samples is /// still one of the samples (or between two), so it carries their sign /// as the same quantile over the raw window does; φ outside [0, 1] /// still carries none. #[test] - fn a_quantile_of_merged_panes_shares_its_raw_data_samples_sign() { + fn a_quantile_of_merged_tumbling_windows_shares_its_raw_data_samples_sign() { let inventory = inventory("quantile_over_time(0.99, m[100s])"); let estimate = (0..inventory.targets.len()) .flat_map(|t| composed_with(&inventory, t)) diff --git a/crates/logical-asap-optimization/src/pass2/asap_aware_cse.rs b/crates/logical-asap-optimization/src/pass2/asap_aware_cse.rs index 420379e93..bc14d4e68 100644 --- a/crates/logical-asap-optimization/src/pass2/asap_aware_cse.rs +++ b/crates/logical-asap-optimization/src/pass2/asap_aware_cse.rs @@ -3,7 +3,7 @@ //! `docs/design_docs/proposals/planner-layering.md` (Algorithm 2). //! //! The memo rules (`pass2::memo::rules`) are built on these helpers: the -//! window-composition rule for tumbling panes, and two reuse rules (#680), +//! window-composition rule for tumbling windows, and two reuse rules (#680), //! rollup and exact top-k reuse. Identical expressions are shared by memo //! group identity, and summary builds of one family at different sizes by //! the memo's shared-producer rule (`memo::rules::shared_producer`); both @@ -16,43 +16,43 @@ //! # Window-composition rule //! //! A query repeating every `E` over a window `W` can build its summary as -//! back-to-back tumbling panes of width `w = gcd(W, E)` (Algorithm 2's -//! `Tumbling(L)` for one query), merged before each read. A *pane -//! candidate* ([`pane_candidates`]) is a `SummaryAgg` over a range +//! back-to-back tumbling windows of width `w = gcd(W, E)` (Algorithm 2's +//! `Tumbling(L)` for one query), merged before each read. A *tumbling window +//! candidate* ([`tumbling_window_candidates`]) is a `SummaryAgg` over a range //! `TimeRange(W)` whose //! family merges in the family-capability table ([`FamilyCapability`]): //! exact accumulators included, and also families whose merged readouts //! are only approximate (heap top-k), whose readers then carry no //! guarantee and are left to plan selection's evidence. A family without a -//! merge (IRate) has no pane candidate. +//! merge (IRate) has no tumbling-window candidate. //! [`window_composition_pass2_candidates`] returns one Pass 2 candidate per -//! non-empty subset of the pane candidates: the build of each in the subset -//! becomes a `SummaryMerge` over one [`ASAPOp::SummaryTumblingWindowChain`] of `W / w` panes, -//! a compact node whatever the pane count, with the build's family and +//! non-empty subset of the tumbling-window candidates: the build of each in the subset +//! becomes a `SummaryMerge` over one [`ASAPOp::SummaryTumblingWindowChain`] of `W / w` tumbling windows, +//! a compact node whatever the tumbling-window count, with the build's family and //! size (a mergeable family's merged state has one build's guarantee), and //! every guarantee above is composed again. Nothing is materialized here: -//! the panes are rebuilt at each evaluation until Stage 2 keeps them. +//! the tumbling windows are rebuilt at each evaluation until Stage 2 keeps them. //! //! Nested windows share one chain: when a subset holds builds of one family //! (at any size) over the same input below their ranges, read the same way, -//! with the same output column names, whose panes differ in width or count, +//! with the same output column names, whose tumbling windows differ in width or count, //! the subset also comes as a Pass 2 candidate in which each such group -//! reads one chain ([`SharedPaneChain`]) over the longest window, sized for +//! reads one chain ([`SharedTumblingWindowChain`]) over the longest window, sized for //! every member ([`size_for_all_readers`]). Two grids are offered: the -//! finest member's own panes, for the members whose widths it divides +//! finest member's own tumbling windows, for the members whose widths it divides //! ([`nested_window_groups`]), and, when that differs, the gcd of all the -//! members' widths, even finer than every member's own panes +//! members' widths, even finer than every member's own tumbling windows //! ([`gcd_window_groups`]); Stage 3's pricing weighs the extra states and -//! merges of finer panes. Queries repeating at different intervals read the -//! same panes, each at its own cadence. Every window ends at the evaluation -//! time, so each shorter one is the chain's newest panes: its +//! merges of finer tumbling windows. Queries repeating at different intervals read the +//! same tumbling windows, each at its own cadence. Every window ends at the evaluation +//! time, so each shorter one is the chain's newest tumbling windows: its //! `SummaryMerge` merges only those (`newest_tumbling_windows`), and has the guarantee //! of one build over its own window. A build grouped by fewer keys also //! reads the chain of a finer grouping over a window at least its own -//! ([`rollup_window_groups`]): it merges its newest panes, then rolls the +//! ([`rollup_window_groups`]): it merges its newest tumbling windows, then rolls the //! groups up into its own, as the rollup rule does. Stage 2 never keeps a //! chain read at different intervals across evaluations from query time -//! (each reader would need one new pane per evaluation). Long-window segments, sliding +//! (each reader would need one new tumbling window per evaluation). Long-window segments, sliding //! windows and Exponential Histograms are not implemented. //! //! # Rollup rule @@ -137,7 +137,7 @@ use asap_accuracy::propagation::recompose_guarantees; /// `Project` that keeps the columns it read, by name. `None` when every metric's scans already /// agree, when two of them give one label different types or one is closed /// and the other not, when a node would change arity, or when a consumer -/// would have to narrow a summary state or a grouped or filtered pane chain +/// would have to narrow a summary state or a grouped or filtered tumbling-window chain /// would read a `Project`. pub fn with_union_scan_labels( roots: &[QueryRoot], @@ -224,7 +224,7 @@ pub fn with_union_scan_labels( Err(_) => return Ok(None), } } - // A grouped or filtered pane chain over a `Project` would hide + // A grouped or filtered tumbling-window chain over a `Project` would hide // its window from plan selection. _ if matches!(node.asap(), Some(ASAPOp::SummaryTumblingWindowChain { .. })) => { return Ok(None); @@ -276,10 +276,10 @@ fn unfiltered_series_scan(node: &OperatorNode) -> Option<(&Source, &Schema)> { /// Whether `node`, over a child with more labels, keeps them in its own /// output: a time range or shift of the rows, and a per-series build or -/// pane chain without a filter (keyed by the series identity, which +/// tumbling-window chain without a filter (keyed by the series identity, which /// determines every label) with its merges, estimates and finalizations. /// The executor reads a per-series build's raw rows from its `TimeRange` -/// directly, and plan selection reads a pane chain's window from the rows +/// directly, and plan selection reads a tumbling-window chain's window from the rows /// below it, so nothing may sit between them. fn passes_labels_on(node: &OperatorNode) -> bool { matches!( @@ -430,78 +430,78 @@ fn sized_for_all( } /// One summary build the window-composition rule can build as tumbling -/// panes: a `SummaryAgg` over a range `TimeRange(W)` whose family merges +/// tumbling windows: a `SummaryAgg` over a range `TimeRange(W)` whose family merges /// (the family-capability table: exact accumulators included, and families /// whose merged readouts are only approximate), read only by queries that -/// repeat at a fixed interval. Its panes are `tumbling_window_count = W / w` back-to-back +/// repeat at a fixed interval. Its tumbling windows are `tumbling_window_count = W / w` back-to-back /// windows of `w = gcd(W, E)` over the queries' intervals `E` (and, for a /// query with a range evaluation, its step), at least two. #[derive(Debug, Clone)] -pub struct PaneCandidate { - /// The `SummaryAgg` over `TimeRange(W)` built as panes. +pub struct TumblingWindowCandidate { + /// The `SummaryAgg` over `TimeRange(W)` built as tumbling windows. pub build: Rc, - /// The width `w` of one pane. + /// The width `w` of one tumbling window. pub tumbling_window_width_ms: u64, - /// The number of panes, `W / w`, at least two. + /// The number of tumbling windows, `W / w`, at least two. pub tumbling_window_count: u32, } -impl PaneCandidate { - /// The window `W` its panes cover, `tumbling_window_width_ms · tumbling_window_count`. +impl TumblingWindowCandidate { + /// The window `W` its tumbling windows cover, `tumbling_window_width_ms · tumbling_window_count`. pub fn window_ms(&self) -> u64 { self.tumbling_window_width_ms * u64::from(self.tumbling_window_count) } } /// A Pass 2 candidate of the window-composition rule: the candidate's roots -/// with the build of each of `pane_candidates` built as its tumbling panes, +/// with the build of each of `tumbling_window_candidates` built as its tumbling windows, /// merged before every read, and every guarantee composed again; the error /// when that fails, which leaves the other Pass 2 candidates unaffected. #[derive(Debug)] pub struct WindowCompositionPass2Candidate { - /// The builds built as tumbling panes, in [`pane_candidates`] order. + /// The builds built as tumbling windows, in [`tumbling_window_candidates`] order. /// Only tests read it. #[cfg_attr(not(any(test, feature = "test-support")), allow(dead_code))] - pub pane_candidates: Vec, - /// The chains each shared by several of `pane_candidates` + pub tumbling_window_candidates: Vec, + /// The chains each shared by several of `tumbling_window_candidates` /// ([`nested_window_groups`] or [`gcd_window_groups`]); empty when each /// build has its own chain. - pub shared_chains: Vec, - /// The candidate's roots with those builds in panes. + pub shared_chains: Vec, + /// The candidate's roots with those builds in tumbling windows. pub roots: Result, PerComputationCandidateError>, } -/// One pane chain several pane candidates read (window-composition rule): -/// panes of `tumbling_window_width_ms`, a width that divides every member's own pane +/// One tumbling-window chain several tumbling-window candidates read (window-composition rule): +/// tumbling windows of `tumbling_window_width_ms`, a width that divides every member's own tumbling window /// width, and `tumbling_window_count` of them, covering the longest member's window. -/// Each member merges the newest `W / tumbling_window_width_ms` panes of its window `W`. +/// Each member merges the newest `W / tumbling_window_width_ms` tumbling windows of its window `W`. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct SharedPaneChain { - /// Positions in the Pass 2 candidate's `pane_candidates`, two or more. +pub struct SharedTumblingWindowChain { + /// Positions in the Pass 2 candidate's `tumbling_window_candidates`, two or more. pub members: Vec, - /// The width of one pane of the shared chain. + /// The width of one tumbling window of the shared chain. pub tumbling_window_width_ms: u64, - /// The number of panes of the shared chain. + /// The number of tumbling windows of the shared chain. pub tumbling_window_count: u32, } -/// The most pane candidates one candidate may have: every non-empty subset +/// The most tumbling-window candidates one candidate may have: every non-empty subset /// is a Pass 2 candidate, and with shared grids up to three, about three /// million at 20. -pub(crate) const MAX_PANE_CANDIDATES: usize = 20; +pub(crate) const MAX_TUMBLING_WINDOW_CANDIDATES: usize = 20; -/// The pane candidates of the candidate with query roots `roots`, children +/// The tumbling-window candidates of the candidate with query roots `roots`, children /// first. `evaluation_grid_ms[q]` is the grid query `q`'s evaluations fall /// on: how often it repeats, or with a range evaluation the greatest common /// divisor of that and its step, and of its phase if declared /// (`QueryWorkloadEntry::evaluation_grid_ms`); `None` when it does not -/// repeat at a fixed interval. Each pane width divides every reader's grid, -/// so with panes anchored at the epoch every evaluation, every range step -/// included, ends on a pane boundary. -pub fn pane_candidates( +/// repeat at a fixed interval. Each tumbling-window width divides every reader's grid, +/// so with tumbling windows anchored at the epoch every evaluation, every range step +/// included, ends on a tumbling-window boundary. +pub fn tumbling_window_candidates( roots: &[QueryRoot], evaluation_grid_ms: &[Option], -) -> Vec { +) -> Vec { let mut nodes = Vec::new(); let mut seen = std::collections::HashSet::new(); for node in roots.iter().flat_map(root_operators) { @@ -520,7 +520,7 @@ pub fn pane_candidates( nodes .into_iter() .filter_map(|build| { - let window_ms = pane_window_ms(&build)?; + let window_ms = splittable_window_ms(&build)?; let readers: Vec = (0..roots.len()) .filter(|&q| reach[q].contains(&Rc::as_ptr(&build))) .collect(); @@ -532,7 +532,7 @@ pub fn pane_candidates( ); } let tumbling_window_count = u32::try_from(window_ms / width).ok()?; - (!readers.is_empty() && tumbling_window_count >= 1).then_some(PaneCandidate { + (!readers.is_empty() && tumbling_window_count >= 1).then_some(TumblingWindowCandidate { build, tumbling_window_width_ms: width, tumbling_window_count, @@ -545,7 +545,7 @@ pub fn pane_candidates( /// family merges, over a range `TimeRange(W)` over an instant vector or a /// SQL table's rows, read at the query's time or a fixed offset from it: no `@` anchor on any of /// the time shifts stacked below the range. -fn pane_window_ms(build: &OperatorNode) -> Option { +fn splittable_window_ms(build: &OperatorNode) -> Option { let Some(ASAPOp::SummaryAgg { child, family, .. }) = build.asap() else { return None; }; @@ -578,42 +578,42 @@ fn pane_window_ms(build: &OperatorNode) -> Option { } /// Every Pass 2 candidate of the window-composition rule for the candidate -/// with query roots `roots` and pane candidates `candidates` -/// ([`pane_candidates`]): one per non-empty subset of them, in +/// with query roots `roots` and tumbling-window candidates `candidates` +/// ([`tumbling_window_candidates`]): one per non-empty subset of them, in /// binary-counter order (the first candidate the lowest bit). Each build in /// the subset becomes a `SummaryMerge` over one [`ASAPOp::SummaryTumblingWindowChain`] of -/// its panes; every other build is untouched. A subset whose builds can +/// its tumbling windows; every other build is untouched. A subset whose builds can /// share chains is followed by one Pass 2 candidate per shared grid, in /// which each group reads one shared chain, each member merging its newest -/// panes: the finest member's panes (`nested_window_groups`), then the +/// tumbling windows: the finest member's tumbling windows (`nested_window_groups`), then the /// gcd of the members' widths when that groups them differently /// (`gcd_window_groups`); and by one error candidate per group whose /// sizing fails. Each grid groups every build of a subset at once: a /// shared gcd grid over only some of the builds that could share it is not /// listed. An error only for more than -/// `MAX_PANE_CANDIDATES` pane candidates; a Pass 2 candidate whose +/// `MAX_TUMBLING_WINDOW_CANDIDATES` tumbling-window candidates; a Pass 2 candidate whose /// composition fails carries its own error. pub fn window_composition_pass2_candidates( roots: &[QueryRoot], - candidates: &[PaneCandidate], + candidates: &[TumblingWindowCandidate], ) -> Result, PerComputationCandidateError> { - if candidates.len() > MAX_PANE_CANDIDATES { + if candidates.len() > MAX_TUMBLING_WINDOW_CANDIDATES { return Err(PerComputationCandidateError::TooManyCandidates { - counted: "summary builds that could be built in tumbling panes", + counted: "summary builds that could be built in tumbling windows", found: candidates.len(), - max: MAX_PANE_CANDIDATES, + max: MAX_TUMBLING_WINDOW_CANDIDATES, }); } - let rollable = rollable_pane_candidates(roots, candidates); + let rollable = rollable_tumbling_window_candidates(roots, candidates); let mut pass2_candidates = Vec::new(); for mask in 1u64..(1u64 << candidates.len()) { - let chosen: Vec = (0..candidates.len()) + let chosen: Vec = (0..candidates.len()) .filter(|&i| mask >> i & 1 == 1) .map(|i| candidates[i].clone()) .collect(); pass2_candidates.push(WindowCompositionPass2Candidate { - roots: in_panes(roots, &chosen, &[]), - pane_candidates: chosen.clone(), + roots: in_tumbling_windows(roots, &chosen, &[]), + tumbling_window_candidates: chosen.clone(), shared_chains: Vec::new(), }); let chosen_rollable: Vec = (0..candidates.len()) @@ -626,14 +626,14 @@ pub fn window_composition_pass2_candidates( } /// The Pass 2 candidates of [`window_composition_pass2_candidates`] in -/// which every one of `candidates` is in panes and reads a shared chain: +/// which every one of `candidates` is in tumbling windows and reads a shared chain: /// that function's candidates of the whole set with shared chains, in its /// order, without building the other subsets'. pub fn window_composition_shared_chain_candidates( roots: &[QueryRoot], - candidates: &[PaneCandidate], + candidates: &[TumblingWindowCandidate], ) -> Vec { - let rollable = rollable_pane_candidates(roots, candidates); + let rollable = rollable_tumbling_window_candidates(roots, candidates); let mut pass2_candidates = Vec::new(); push_every_shared_chain(roots, candidates, &rollable, &mut pass2_candidates); pass2_candidates @@ -643,17 +643,17 @@ pub fn window_composition_shared_chain_candidates( /// rollable or not, `chosen_rollable`) of [`window_composition_pass2_candidates`]. fn push_every_shared_chain( roots: &[QueryRoot], - chosen: &[PaneCandidate], + chosen: &[TumblingWindowCandidate], chosen_rollable: &[bool], pass2_candidates: &mut Vec, ) { - // Shared chains on the members' own finest panes, then, when it + // Shared chains on the members' own finest tumbling windows, then, when it // groups them differently, on the gcd of their widths. - let on_member_panes = nested_window_groups(chosen); - let on_gcd_panes = gcd_window_groups(chosen); - push_shared_chains(roots, chosen, &on_member_panes, pass2_candidates); - if on_gcd_panes != on_member_panes { - push_shared_chains(roots, chosen, &on_gcd_panes, pass2_candidates); + let on_member_tumbling_windows = nested_window_groups(chosen); + let on_gcd_tumbling_windows = gcd_window_groups(chosen); + push_shared_chains(roots, chosen, &on_member_tumbling_windows, pass2_candidates); + if on_gcd_tumbling_windows != on_member_tumbling_windows { + push_shared_chains(roots, chosen, &on_gcd_tumbling_windows, pass2_candidates); } // Coarser groupings read a finer grouping's chain, rolled up. push_shared_chains( @@ -669,8 +669,8 @@ fn push_every_shared_chain( /// each group whose sizing fails. Nothing when no group has a shared size. fn push_shared_chains( roots: &[QueryRoot], - chosen: &[PaneCandidate], - groups: &[SharedPaneChain], + chosen: &[TumblingWindowCandidate], + groups: &[SharedTumblingWindowChain], pass2_candidates: &mut Vec, ) { // Each group with a size that serves every member, and that build. @@ -681,29 +681,29 @@ fn push_shared_chains( Ok(None) => {} Err(error) => pass2_candidates.push(WindowCompositionPass2Candidate { roots: Err(error), - pane_candidates: chosen.to_vec(), + tumbling_window_candidates: chosen.to_vec(), shared_chains: vec![group.clone()], }), } } if !shared.is_empty() { pass2_candidates.push(WindowCompositionPass2Candidate { - roots: in_panes(roots, chosen, &shared), - pane_candidates: chosen.to_vec(), + roots: in_tumbling_windows(roots, chosen, &shared), + tumbling_window_candidates: chosen.to_vec(), shared_chains: shared.into_iter().map(|(group, _)| group).collect(), }); } } -/// The pane candidates (positions in `candidates`) that can read one -/// shared chain at the gcd of their pane widths, each group with its +/// The tumbling-window candidates (positions in `candidates`) that can read one +/// shared chain at the gcd of their tumbling-window widths, each group with its /// chain: every build over one input ([`same_build_over_one_input`]) in one -/// group, whatever its pane width, on panes of the gcd of the members' -/// widths over the longest window. Finer panes than any member builds mean +/// group, whatever its tumbling-window width, on tumbling windows of the gcd of the members' +/// widths over the longest window. Finer tumbling windows than any member builds mean /// more states and more merges; Stage 3's pricing decides whether sharing -/// pays. Groups of members with the same panes and window are left to the +/// pays. Groups of members with the same tumbling windows and window are left to the /// other rules. -fn gcd_window_groups(candidates: &[PaneCandidate]) -> Vec { +fn gcd_window_groups(candidates: &[TumblingWindowCandidate]) -> Vec { let mut groups: Vec> = Vec::new(); for (i, candidate) in candidates.iter().enumerate() { match groups @@ -717,13 +717,16 @@ fn gcd_window_groups(candidates: &[PaneCandidate]) -> Vec { groups .into_iter() .filter_map(|members| { - let panes = |i: usize| { + let tumbling_windows = |i: usize| { ( candidates[i].tumbling_window_width_ms, candidates[i].tumbling_window_count, ) }; - if members.iter().all(|&i| panes(i) == panes(members[0])) { + if members + .iter() + .all(|&i| tumbling_windows(i) == tumbling_windows(members[0])) + { return None; } let tumbling_window_width_ms = members.iter().fold(0, |width, &i| { @@ -735,7 +738,7 @@ fn gcd_window_groups(candidates: &[PaneCandidate]) -> Vec { let longest = longest(candidates, &members); let tumbling_window_count = u32::try_from(candidates[longest].window_ms() / tumbling_window_width_ms).ok()?; - Some(SharedPaneChain { + Some(SharedTumblingWindowChain { members, tumbling_window_width_ms, tumbling_window_count, @@ -744,23 +747,23 @@ fn gcd_window_groups(candidates: &[PaneCandidate]) -> Vec { .collect() } -/// The pane candidates (positions in `candidates`) that can read one +/// The tumbling-window candidates (positions in `candidates`) that can read one /// shared chain, each group with its chain: builds of one family /// ([`same_family`], at any size) that read the same input below their /// ranges the same way (item and weight, grouping and filter), with the /// same output column names (each reader reads the shared chain's columns /// by position). Their windows end at the same time, so each is the newest -/// panes of the longest. The shared panes are the group's finest member's -/// own panes: a build joins a group only when that width divides its own -/// pane width (the gcd of the widths, never finer than a chain some member +/// tumbling windows of the longest. The shared tumbling windows are the group's finest member's +/// own tumbling windows: a build joins a group only when that width divides its own +/// tumbling-window width (the gcd of the widths, never finer than a chain some member /// builds anyway), and otherwise starts its own group. Members at different -/// repeat intervals read the same panes, each at its own cadence. One chain +/// repeat intervals read the same tumbling windows, each at its own cadence. One chain /// over the longest window, sized for every reader /// ([`size_for_all_readers`]), serves them all. Groups of members with the -/// same panes and window are left to the other rules. -fn nested_window_groups(candidates: &[PaneCandidate]) -> Vec { - // Finest panes first: each build joins the first group over its input - // whose (finest) pane width divides its own, so a build whose panes no +/// same tumbling windows and window are left to the other rules. +fn nested_window_groups(candidates: &[TumblingWindowCandidate]) -> Vec { + // Finest tumbling windows first: each build joins the first group over its input + // whose (finest) tumbling-window width divides its own, so a build whose tumbling windows no // group's divide starts its own group instead of breaking another. let mut by_width: Vec = (0..candidates.len()).collect(); by_width.sort_by_key(|&i| candidates[i].tumbling_window_width_ms); @@ -781,13 +784,16 @@ fn nested_window_groups(candidates: &[PaneCandidate]) -> Vec { let mut shared = groups .into_iter() .filter_map(|mut members| { - let panes = |i: usize| { + let tumbling_windows = |i: usize| { ( candidates[i].tumbling_window_width_ms, candidates[i].tumbling_window_count, ) }; - if members.iter().all(|&i| panes(i) == panes(members[0])) { + if members + .iter() + .all(|&i| tumbling_windows(i) == tumbling_windows(members[0])) + { return None; } let tumbling_window_width_ms = candidates[members[0]].tumbling_window_width_ms; @@ -795,7 +801,7 @@ fn nested_window_groups(candidates: &[PaneCandidate]) -> Vec { let tumbling_window_count = u32::try_from(candidates[longest].window_ms() / tumbling_window_width_ms).ok()?; members.sort_unstable(); - Some(SharedPaneChain { + Some(SharedTumblingWindowChain { members, tumbling_window_width_ms, tumbling_window_count, @@ -810,10 +816,13 @@ fn nested_window_groups(candidates: &[PaneCandidate]) -> Vec { /// Whether each of `candidates` may be read rolled up from a finer /// grouping's chain: a sketch build over a SQL relation's rows (a PromQL -/// rollup over newest panes is not offered yet) whose every reader in +/// rollup over newest tumbling windows is not offered yet) whose every reader in /// `roots` is an estimate whose merged readout keeps a guarantee, as for /// the rollup rule. -fn rollable_pane_candidates(roots: &[QueryRoot], candidates: &[PaneCandidate]) -> Vec { +fn rollable_tumbling_window_candidates( + roots: &[QueryRoot], + candidates: &[TumblingWindowCandidate], +) -> Vec { let mut nodes = Vec::new(); let mut seen = HashSet::new(); for node in roots.iter().flat_map(root_operators) { @@ -843,20 +852,23 @@ fn rollable_pane_candidates(roots: &[QueryRoot], candidates: &[PaneCandidate]) - .collect() } -/// The pane candidates (positions in `candidates`) that can read one -/// chain of a finer grouping's panes, each group with its chain: a host, +/// The tumbling-window candidates (positions in `candidates`) that can read one +/// chain of a finer grouping's tumbling windows, each group with its chain: a host, /// and every candidate whose group keys are the host's or fewer -/// ([`rolls_up_into`]), whose window is at most the host's and whose pane -/// width the host's divides. Each member merges the newest panes of its +/// ([`rolls_up_into`]), whose window is at most the host's and whose tumbling window +/// width the host's divides. Each member merges the newest tumbling windows of its /// window, and a coarser member then rolls the host's groups up into its /// own (a regrouping `SummaryMerge`): `p90 per city over 20 minutes` in -/// 10 s panes serves `p90 over 5 minutes` as the newest 30 panes merged +/// 10 s tumbling windows serves `p90 over 5 minutes` as the newest 30 tumbling windows merged /// across cities. A coarser member is one `rollable[i]` allows -/// ([`rollable_pane_candidates`]). Hosts are tried by most group keys, +/// ([`rollable_tumbling_window_candidates`]). Hosts are tried by most group keys, /// then longest window, and each candidate joins one group. Only groups /// with a coarser member are listed; the others are /// [`nested_window_groups`]'s. -fn rollup_window_groups(candidates: &[PaneCandidate], rollable: &[bool]) -> Vec { +fn rollup_window_groups( + candidates: &[TumblingWindowCandidate], + rollable: &[bool], +) -> Vec { let mut hosts: Vec = (0..candidates.len()).collect(); hosts.sort_by_key(|&i| { ( @@ -892,7 +904,7 @@ fn rollup_window_groups(candidates: &[PaneCandidate], rollable: &[bool]) -> Vec< for &i in &members { grouped[i] = true; } - groups.push(SharedPaneChain { + groups.push(SharedTumblingWindowChain { members, tumbling_window_width_ms: candidates[host].tumbling_window_width_ms, tumbling_window_count: candidates[host].tumbling_window_count, @@ -918,7 +930,7 @@ fn group_key_count(build: &OperatorNode) -> usize { /// or the same but grouped by fewer of `host`'s keys, of a family whose /// states merge across groups ([`FamilyCapability::merges_groups`]), so a /// rollup of `host`'s states answers it. -fn rolls_up_into(member: &PaneCandidate, host: &PaneCandidate) -> bool { +fn rolls_up_into(member: &TumblingWindowCandidate, host: &TumblingWindowCandidate) -> bool { if same_build_over_one_input(member, host) { return true; } @@ -957,10 +969,10 @@ fn rolls_up_into(member: &PaneCandidate, host: &PaneCandidate) -> bool { && filter == host_filter } -/// Whether two pane candidates build one family (at any size) over the +/// Whether two tumbling-window candidates build one family (at any size) over the /// same input below their ranges, read the same way, with the same output /// column names. -fn same_build_over_one_input(a: &PaneCandidate, b: &PaneCandidate) -> bool { +fn same_build_over_one_input(a: &TumblingWindowCandidate, b: &TumblingWindowCandidate) -> bool { let ( Some(ASAPOp::SummaryAgg { child: range, @@ -1002,7 +1014,7 @@ fn same_build_over_one_input(a: &PaneCandidate, b: &PaneCandidate) -> bool { /// The member of `group` (positions in `candidates`) with the longest /// window. -fn longest(candidates: &[PaneCandidate], group: &[usize]) -> usize { +fn longest(candidates: &[TumblingWindowCandidate], group: &[usize]) -> usize { *group .iter() .max_by_key(|&&i| candidates[i].window_ms()) @@ -1013,7 +1025,7 @@ fn longest(candidates: &[PaneCandidate], group: &[usize]) -> usize { /// made of: the longest member's, sized for every member /// ([`sized_for_all`]); `None` when their sizes have no shared size. fn shared_chain_build( - candidates: &[PaneCandidate], + candidates: &[TumblingWindowCandidate], group: &[usize], ) -> Result>, PerComputationCandidateError> { // Of the longest windows, the finest grouping: a rollup group's host. @@ -1034,17 +1046,17 @@ fn shared_chain_build( } /// `roots` with the build of each of `candidates` built as its tumbling -/// panes, and every guarantee composed again. The members of each -/// [`SharedPaneChain`] (with its [`shared_chain_build`]) read that one -/// chain: each merges the newest panes covering its own window. -fn in_panes( +/// tumbling windows, and every guarantee composed again. The members of each +/// [`SharedTumblingWindowChain`] (with its [`shared_chain_build`]) read that one +/// chain: each merges the newest tumbling windows covering its own window. +fn in_tumbling_windows( roots: &[QueryRoot], - candidates: &[PaneCandidate], - shared_chains: &[(SharedPaneChain, Rc)], + candidates: &[TumblingWindowCandidate], + shared_chains: &[(SharedTumblingWindowChain, Rc)], ) -> Result, PerComputationCandidateError> { let mut replaced: HashMap<*const OperatorNode, Rc> = HashMap::new(); for (shared, build) in shared_chains { - let chain = pane_chain(&PaneCandidate { + let chain = tumbling_window_chain(&TumblingWindowCandidate { build: Rc::clone(build), tumbling_window_width_ms: shared.tumbling_window_width_ms, tumbling_window_count: shared.tumbling_window_count, @@ -1063,7 +1075,7 @@ fn in_panes( merge } else { regrouped(member, &merge).ok_or(PerComputationCandidateError::Unsupported( - "a coarser pane build whose keys the shared chain does not hold", + "a coarser tumbling-window build whose keys the shared chain does not hold", ))? }; replaced.insert(Rc::as_ptr(member), read); @@ -1075,7 +1087,7 @@ fn in_panes( { entry.insert(Rc::new(OperatorNode::new(Operator::ASAP( ASAPOp::SummaryMerge { - children: vec![pane_chain(candidate)?], + children: vec![tumbling_window_chain(candidate)?], newest_tumbling_windows: None, group_by: None, }, @@ -1104,13 +1116,13 @@ fn in_panes( } /// `roots` with the `SummaryMerge` `merge` (over one [`ASAPOp::SummaryTumblingWindowChain`] -/// of `roots`) reading the newest panes of another chain over the same -/// input: `tumbling_window_count` panes of `tumbling_window_width_ms` of family `family` (a size +/// of `roots`) reading the newest tumbling windows of another chain over the same +/// input: `tumbling_window_count` tumbling windows of `tumbling_window_width_ms` of family `family` (a size /// that serves every reader of the chain). The merge reads the newest -/// panes covering its own window (its newest panes, or its whole chain), +/// tumbling windows covering its own window (its newest tumbling windows, or its whole chain), /// which `tumbling_window_width_ms` divides and the new chain covers; every guarantee /// is composed again. -pub(crate) fn with_pane_chain( +pub(crate) fn with_tumbling_window_chain( roots: &[QueryRoot], merge: &Rc, family: FieldDataType, @@ -1123,7 +1135,7 @@ pub(crate) fn with_pane_chain( .. }) = merge.asap() else { - unreachable!("a merge over a pane chain"); + unreachable!("a merge over a tumbling-window chain"); }; let own = &children[0]; let Some(ASAPOp::SummaryTumblingWindowChain { @@ -1137,9 +1149,9 @@ pub(crate) fn with_pane_chain( .. }) = own.asap() else { - unreachable!("a merge over a pane chain"); + unreachable!("a merge over a tumbling-window chain"); }; - // The merge's window: its newest panes, or the whole chain. + // The merge's window: its newest tumbling windows, or the whole chain. let window_ms = own_width * u64::from(own_newest.unwrap_or(*own_count)); let newest = u32::try_from(window_ms / tumbling_window_width_ms) .expect("a reader's window is at most the chain's"); @@ -1237,9 +1249,11 @@ pub(crate) fn with_resized_build( Ok(recompose_guarantees(&out)?) } -/// The [`ASAPOp::SummaryTumblingWindowChain`] of `candidate`'s panes, with the build's +/// The [`ASAPOp::SummaryTumblingWindowChain`] of `candidate`'s tumbling windows, with the build's /// output names. -fn pane_chain(candidate: &PaneCandidate) -> Result, PerComputationCandidateError> { +fn tumbling_window_chain( + candidate: &TumblingWindowCandidate, +) -> Result, PerComputationCandidateError> { let Some(ASAPOp::SummaryAgg { child: range, family, @@ -1249,7 +1263,7 @@ fn pane_chain(candidate: &PaneCandidate) -> Result, PerComputat filter, }) = candidate.build.asap() else { - unreachable!("a pane candidate's build is a SummaryAgg"); + unreachable!("a tumbling-window candidate's build is a SummaryAgg"); }; let mut chain = OperatorNode::new(Operator::ASAP(ASAPOp::SummaryTumblingWindowChain { child: Rc::clone(range.children()[0]), @@ -2351,10 +2365,10 @@ mod tests { } /// A count over a window, read from a Count-Min built in tumbling - /// panes, keeps the guarantee of one build over the window: Pass 1 + /// tumbling windows, keeps the guarantee of one build over the window: Pass 1 /// offers Count-Min for counts, and its merged counters add. #[test] - fn a_count_from_count_min_panes_keeps_its_guarantee() { + fn a_count_from_count_min_tumbling_windows_keeps_its_guarantee() { use crate::pass1::per_computation_candidates::{ compose_logical_candidate, generate_per_computation_candidates, }; @@ -2383,18 +2397,18 @@ mod tests { }) .collect(); let roots: Vec = compose_logical_candidate(&inventory, &choice).unwrap(); - let candidates = pane_candidates(&roots, &[Some(60_000)]); + let candidates = tumbling_window_candidates(&roots, &[Some(60_000)]); let pass2_candidates = window_composition_pass2_candidates(&roots, &candidates).unwrap(); let [pass2_candidate] = pass2_candidates.as_slice() else { panic!("one Pass 2 candidate: {pass2_candidates:?}") }; - let (QueryRoot::Operator(whole), QueryRoot::Operator(paned)) = + let (QueryRoot::Operator(whole), QueryRoot::Operator(windowed)) = (&roots[0], &pass2_candidate.roots.as_ref().unwrap()[0]) else { panic!("operator roots") }; assert!(whole.guarantee.is_some()); - assert_eq!(paned.guarantee, whole.guarantee); + assert_eq!(windowed.guarantee, whole.guarantee); } /// The composed candidate of `queries` (each at its own ε, δ = 0.01) @@ -2478,9 +2492,9 @@ mod tests { .expect("a constant bound") } - /// The pane candidates of `roots` as (pane width, pane count). - fn panes(roots: &[QueryRoot], interval_ms: &[Option]) -> Vec<(u64, u32)> { - pane_candidates(roots, interval_ms) + /// The tumbling-window candidates of `roots` as (tumbling-window width, tumbling-window count). + fn tumbling_windows(roots: &[QueryRoot], interval_ms: &[Option]) -> Vec<(u64, u32)> { + tumbling_window_candidates(roots, interval_ms) .iter() .map(|rule_candidate| { ( @@ -2491,42 +2505,42 @@ mod tests { .collect() } - /// A 5-min window evaluated every 2 min, 90 s or 1 min is built in panes - /// of gcd(window, interval). A once-per-window reader has one pane, - /// which can reuse the newest pane of a longer chain. + /// A 5-min window evaluated every 2 min, 90 s or 1 min is built in tumbling windows + /// of gcd(window, interval). A once-per-window reader has one tumbling window, + /// which can reuse the newest tumbling window of a longer chain. #[test] - fn pane_width_is_the_gcd_of_the_window_and_the_interval() { + fn tumbling_window_width_is_the_gcd_of_the_window_and_the_interval() { let roots = chose( &[("quantile_over_time(0.99, m[5m])", 0.01)], SketchAlgorithm::Kll, ); - assert_eq!(panes(&roots, &[Some(120_000)]), [(60_000, 5)]); - assert_eq!(panes(&roots, &[Some(90_000)]), [(30_000, 10)]); - assert_eq!(panes(&roots, &[Some(60_000)]), [(60_000, 5)]); - assert_eq!(panes(&roots, &[Some(300_000)]), [(300_000, 1)]); - assert!(panes(&roots, &[None]).is_empty()); + assert_eq!(tumbling_windows(&roots, &[Some(120_000)]), [(60_000, 5)]); + assert_eq!(tumbling_windows(&roots, &[Some(90_000)]), [(30_000, 10)]); + assert_eq!(tumbling_windows(&roots, &[Some(60_000)]), [(60_000, 5)]); + assert_eq!(tumbling_windows(&roots, &[Some(300_000)]), [(300_000, 1)]); + assert!(tumbling_windows(&roots, &[None]).is_empty()); } - /// Past their caps, the pane rule and the reuse subsets name how many + /// Past their caps, the tumbling-window rule and the reuse subsets name how many /// candidates they found. #[test] - fn too_many_pane_or_reuse_candidates_name_the_count_found() { + fn too_many_tumbling_window_or_reuse_candidates_name_the_count_found() { let roots = chose( &[("quantile_over_time(0.99, m[5m])", 0.01)], SketchAlgorithm::Kll, ); - let panes: Vec<_> = pane_candidates(&roots, &[Some(60_000)]) + let tumbling_windows: Vec<_> = tumbling_window_candidates(&roots, &[Some(60_000)]) .into_iter() .cycle() - .take(MAX_PANE_CANDIDATES + 1) + .take(MAX_TUMBLING_WINDOW_CANDIDATES + 1) .collect(); - let error = window_composition_pass2_candidates(&roots, &panes).unwrap_err(); + let error = window_composition_pass2_candidates(&roots, &tumbling_windows).unwrap_err(); assert_eq!( error.to_string(), format!( - "found {} summary builds that could be built in tumbling panes, more than \ - {MAX_PANE_CANDIDATES}", - MAX_PANE_CANDIDATES + 1 + "found {} summary builds that could be built in tumbling windows, more than \ + {MAX_TUMBLING_WINDOW_CANDIDATES}", + MAX_TUMBLING_WINDOW_CANDIDATES + 1 ) ); let error = subsets(&[0u8; MAX_REUSE_CANDIDATES + 1], "rollup").unwrap_err(); @@ -2539,11 +2553,11 @@ mod tests { ); } - /// A range read at an `@` anchor has no panes, however many shifts + /// A range read at an `@` anchor has no tumbling windows, however many shifts /// stack above the anchored one: `m[5m] @ 100` with an extra /// `offset 1m` shift between the range and the anchor is still anchored. #[test] - fn an_anchor_below_stacked_shifts_has_no_panes() { + fn an_anchor_below_stacked_shifts_has_no_tumbling_windows() { use asap_types::ir::operator::TimeShift; let roots = chose( &[("quantile_over_time(0.99, m[5m] @ 100)", 0.01)], @@ -2556,7 +2570,7 @@ mod tests { .into_iter() .find(|n| matches!(n.asap(), Some(ASAPOp::SummaryAgg { .. }))) .expect("a build"); - assert_eq!(pane_window_ms(&build), None, "one anchored shift"); + assert_eq!(splittable_window_ms(&build), None, "one anchored shift"); // Put an `offset 1m` shift between the range and its anchored shift. let restacked = build .with_new_children(|range| { @@ -2580,13 +2594,17 @@ mod tests { ) }) .unwrap(); - assert_eq!(pane_window_ms(&restacked), None, "anchor below an offset"); + assert_eq!( + splittable_window_ms(&restacked), + None, + "anchor below an offset" + ); } - /// Exact accumulators whose states merge get panes; IRate, which reads + /// Exact accumulators whose states merge get tumbling windows; IRate, which reads /// the last two samples of the window, does not. #[test] - fn exact_accumulators_that_merge_get_panes() { + fn exact_accumulators_that_merge_get_tumbling_windows() { use asap_types::stages::HowToComputeAggregateMeasure; let accumulator = |c: &HowToComputeAggregateMeasure| { matches!(c, HowToComputeAggregateMeasure::ExactAccumulator(_)) @@ -2598,10 +2616,14 @@ mod tests { "max_over_time(m[5m])", ] { let roots = chose_where(&[(query, 0.01)], accumulator); - assert_eq!(panes(&roots, &[Some(60_000)]), [(60_000, 5)], "{query}"); + assert_eq!( + tumbling_windows(&roots, &[Some(60_000)]), + [(60_000, 5)], + "{query}" + ); } let roots = chose_where(&[("irate(m[5m])", 0.01)], accumulator); - assert!(panes(&roots, &[Some(60_000)]).is_empty()); + assert!(tumbling_windows(&roots, &[Some(60_000)]).is_empty()); } /// The rule's candidate reads a `SummaryMerge` over one `SummaryTumblingWindowChain` of @@ -2609,24 +2631,27 @@ mod tests { /// build's output names, and keeps the whole build's guarantee (a KLL /// merges into the state of one build over the union). #[test] - fn panes_replace_the_build_with_a_merged_chain() { + fn tumbling_windows_replace_the_build_with_a_merged_chain() { let roots = chose( &[("quantile_over_time(0.99, m[5m])", 0.01)], SketchAlgorithm::Kll, ); - let rule_candidates = pane_candidates(&roots, &[Some(60_000)]); + let rule_candidates = tumbling_window_candidates(&roots, &[Some(60_000)]); let pass2_candidates = window_composition_pass2_candidates(&roots, &rule_candidates).unwrap(); let [pass2_candidate] = pass2_candidates.as_slice() else { panic!("one Pass 2 candidate: {pass2_candidates:?}") }; assert_eq!( - positions(&rule_candidates, &pass2_candidate.pane_candidates, |c| &c - .build), + positions( + &rule_candidates, + &pass2_candidate.tumbling_window_candidates, + |c| &c.build + ), [0] ); - let panes = pass2_candidate.roots.as_ref().unwrap(); - let QueryRoot::Operator(root) = &panes[0] else { + let tumbling_windows = pass2_candidate.roots.as_ref().unwrap(); + let QueryRoot::Operator(root) = &tumbling_windows[0] else { panic!("operator root") }; root.validate_structure().unwrap(); @@ -2671,9 +2696,9 @@ mod tests { /// Two builds that could be split give three candidates: each alone and /// both, in binary-counter order. Their windows (5 and 2 min) nest on - /// 1-min panes, so both together are also offered as one shared chain. + /// 1-min tumbling windows, so both together are also offered as one shared chain. #[test] - fn every_non_empty_subset_of_the_pane_candidates_is_a_pass2_candidate() { + fn every_non_empty_subset_of_the_tumbling_window_candidates_is_a_pass2_candidate() { let roots = chose( &[ ("quantile_over_time(0.99, m[5m])", 0.01), @@ -2681,7 +2706,7 @@ mod tests { ], SketchAlgorithm::Kll, ); - let rule_candidates = pane_candidates(&roots, &[Some(60_000), Some(60_000)]); + let rule_candidates = tumbling_window_candidates(&roots, &[Some(60_000), Some(60_000)]); assert_eq!(rule_candidates.len(), 2); let subsets: Vec<_> = window_composition_pass2_candidates(&roots, &rule_candidates) .unwrap() @@ -2689,7 +2714,9 @@ mod tests { .map(|v| { assert!(v.roots.is_ok()); ( - positions(&rule_candidates, &v.pane_candidates, |c| &c.build), + positions(&rule_candidates, &v.tumbling_window_candidates, |c| { + &c.build + }), v.shared_chains.clone(), ) }) @@ -2702,7 +2729,7 @@ mod tests { (vec![0, 1], vec![]), ( vec![0, 1], - vec![SharedPaneChain { + vec![SharedTumblingWindowChain { members: vec![0, 1], tumbling_window_width_ms: 60_000, tumbling_window_count: 5 @@ -2712,7 +2739,7 @@ mod tests { ); } - /// The pane chains in `roots`, one per distinct node. + /// The tumbling-window chains in `roots`, one per distinct node. fn chains(roots: &[QueryRoot]) -> Vec> { let mut found: Vec> = Vec::new(); for root in roots { @@ -2742,13 +2769,13 @@ mod tests { } /// A 1-min and a 5-min KLL quantile over one metric, both every 10 s, - /// are 6 and 30 panes of 10 s; the 1-min window is the newest 6 of the + /// are 6 and 30 tumbling windows of 10 s; the 1-min window is the newest 6 of the /// 5-min one. Besides each query's own chain, the rule offers one chain - /// of 30 panes read by both: the 5-min query merges every pane, the + /// of 30 tumbling windows read by both: the 5-min query merges every tumbling window, the /// 1-min one only the newest 6, and each keeps the guarantee of its own /// whole-window build (a KLL merges exactly). #[test] - fn nested_windows_on_equal_panes_share_one_chain() { + fn nested_windows_on_equal_tumbling_windows_share_one_chain() { let roots = chose( &[ ("quantile_over_time(0.5, m[1m])", 0.01), @@ -2756,9 +2783,9 @@ mod tests { ], SketchAlgorithm::Kll, ); - let rule_candidates = pane_candidates(&roots, &[Some(10_000), Some(10_000)]); + let rule_candidates = tumbling_window_candidates(&roots, &[Some(10_000), Some(10_000)]); assert_eq!( - panes(&roots, &[Some(10_000), Some(10_000)]), + tumbling_windows(&roots, &[Some(10_000), Some(10_000)]), [(10_000, 6), (10_000, 30)] ); let pass2_candidates = @@ -2772,7 +2799,7 @@ mod tests { }; assert_eq!( shared.shared_chains, - [SharedPaneChain { + [SharedTumblingWindowChain { members: vec![0, 1], tumbling_window_width_ms: 10_000, tumbling_window_count: 30 @@ -2801,13 +2828,13 @@ mod tests { // The per-query chains stay candidates: two chains, each read in full. let separate = pass2_candidates .iter() - .find(|c| c.pane_candidates.len() == 2 && c.shared_chains.is_empty()) - .expect("both builds in their own panes"); + .find(|c| c.tumbling_window_candidates.len() == 2 && c.shared_chains.is_empty()) + .expect("both builds in their own tumbling windows"); assert_eq!(chains(separate.roots.as_ref().unwrap()).len(), 2); } /// A 15-min quantile every minute and the same quantile every 5 min - /// are 15 × 1-min and 3 × 5-min panes. The rule also offers one chain + /// are 15 × 1-min and 3 × 5-min tumbling windows. The rule also offers one chain /// at the finer width, 15 × 1 min, that both read in full, each at its /// own interval; a 1-hour quantile every 5 min (12 × 5 min) reads the /// newest 60 of one 60 × 1-min chain with them. @@ -2821,8 +2848,11 @@ mod tests { SketchAlgorithm::Kll, ); let grids = [Some(60_000), Some(300_000)]; - assert_eq!(panes(&roots, &grids), [(60_000, 15), (300_000, 3)]); - let rule_candidates = pane_candidates(&roots, &grids); + assert_eq!( + tumbling_windows(&roots, &grids), + [(60_000, 15), (300_000, 3)] + ); + let rule_candidates = tumbling_window_candidates(&roots, &grids); let pass2_candidates = window_composition_pass2_candidates(&roots, &rule_candidates).unwrap(); let shared = pass2_candidates @@ -2831,7 +2861,7 @@ mod tests { .expect("a shared-chain candidate"); assert_eq!( shared.shared_chains, - [SharedPaneChain { + [SharedTumblingWindowChain { members: vec![0, 1], tumbling_window_width_ms: 60_000, tumbling_window_count: 15 @@ -2863,7 +2893,7 @@ mod tests { ], SketchAlgorithm::Kll, ); - let rule_candidates = pane_candidates(&roots, &grids); + let rule_candidates = tumbling_window_candidates(&roots, &grids); let shared = window_composition_pass2_candidates(&roots, &rule_candidates) .unwrap() .into_iter() @@ -2882,10 +2912,10 @@ mod tests { )); } - /// A build whose panes the group's finest panes do not divide gets its + /// A build whose tumbling windows the group's finest tumbling windows do not divide gets its /// own chain without breaking the group: 20-min and 10-min quantiles - /// every 2 min (2-min panes) share one chain, and a 15-min quantile - /// every 3 min (3-min panes) stays apart. + /// every 2 min (2-min tumbling windows) share one chain, and a 15-min quantile + /// every 3 min (3-min tumbling windows) stays apart. #[test] fn a_build_on_an_incompatible_grid_does_not_break_a_shared_chain() { let roots = chose( @@ -2898,18 +2928,18 @@ mod tests { ); let grids = [Some(120_000), Some(120_000), Some(180_000)]; assert_eq!( - panes(&roots, &grids), + tumbling_windows(&roots, &grids), [(120_000, 10), (120_000, 5), (180_000, 5)] ); - let rule_candidates = pane_candidates(&roots, &grids); + let rule_candidates = tumbling_window_candidates(&roots, &grids); let shared = window_composition_pass2_candidates(&roots, &rule_candidates) .unwrap() .into_iter() - .find(|c| c.pane_candidates.len() == 3 && !c.shared_chains.is_empty()) - .expect("a shared chain among all three builds in panes"); + .find(|c| c.tumbling_window_candidates.len() == 3 && !c.shared_chains.is_empty()) + .expect("a shared chain among all three builds in tumbling windows"); assert_eq!( shared.shared_chains, - [SharedPaneChain { + [SharedTumblingWindowChain { members: vec![0, 1], tumbling_window_width_ms: 120_000, tumbling_window_count: 10 @@ -2919,15 +2949,15 @@ mod tests { } /// The shared chains (width, count) of every Pass 2 candidate that - /// paned all of `rule_candidates`. + /// windowed all of `rule_candidates`. fn shared_grids( roots: &[QueryRoot], - rule_candidates: &[PaneCandidate], + rule_candidates: &[TumblingWindowCandidate], ) -> Vec> { window_composition_pass2_candidates(roots, rule_candidates) .unwrap() .into_iter() - .filter(|c| c.pane_candidates.len() == rule_candidates.len()) + .filter(|c| c.tumbling_window_candidates.len() == rule_candidates.len()) .map(|c| { c.shared_chains .iter() @@ -2942,7 +2972,7 @@ mod tests { .collect() } - /// Panes neither member's divide (3-min and 5-min panes of a 15-min + /// Tumbling windows neither member's divide (3-min and 5-min tumbling windows of a 15-min /// window) are shared at their gcd, 1 min, as one more candidate beside /// the per-query chains; no member's own width serves both. #[test] @@ -2955,20 +2985,23 @@ mod tests { SketchAlgorithm::Kll, ); let grids = [Some(180_000), Some(300_000)]; - assert_eq!(panes(&roots, &grids), [(180_000, 5), (300_000, 3)]); - let rule_candidates = pane_candidates(&roots, &grids); + assert_eq!( + tumbling_windows(&roots, &grids), + [(180_000, 5), (300_000, 3)] + ); + let rule_candidates = tumbling_window_candidates(&roots, &grids); assert_eq!( shared_grids(&roots, &rule_candidates), [vec![], vec![(60_000, 15)]] ); } - /// A 15-min quantile every minute (15 × 1-min panes) and a 6-min one - /// every 90 s (4 × 90-s panes): one chain of 30-s panes over 15 min + /// A 15-min quantile every minute (15 × 1-min tumbling windows) and a 6-min one + /// every 90 s (4 × 90-s tumbling windows): one chain of 30-s tumbling windows over 15 min /// serves both, the 6-min query merging the newest 12. It comes beside /// the per-query chains, and Stage 3 prices which is cheaper. #[test] - fn queries_at_60_and_90_seconds_share_one_chain_of_30_second_panes() { + fn queries_at_60_and_90_seconds_share_one_chain_of_30_second_tumbling_windows() { let roots = chose( &[ ("quantile_over_time(0.5, m[15m])", 0.01), @@ -2977,8 +3010,11 @@ mod tests { SketchAlgorithm::Kll, ); let grids = [Some(60_000), Some(90_000)]; - assert_eq!(panes(&roots, &grids), [(60_000, 15), (90_000, 4)]); - let rule_candidates = pane_candidates(&roots, &grids); + assert_eq!( + tumbling_windows(&roots, &grids), + [(60_000, 15), (90_000, 4)] + ); + let rule_candidates = tumbling_window_candidates(&roots, &grids); assert_eq!( shared_grids(&roots, &rule_candidates), [vec![], vec![(30_000, 30)]] @@ -2999,7 +3035,7 @@ mod tests { /// When the members' finest width divides the others, the gcd is that /// width: one shared candidate, not two equal ones. With 1-min, 2-min - /// and 3-min panes, the divisible group (1 and 2 min, the 3-min build + /// and 3-min tumbling windows, the divisible group (1 and 2 min, the 3-min build /// joining at 1 min too) is already the gcd grid. #[test] fn the_gcd_grid_is_not_listed_twice_when_a_member_width_is_the_gcd() { @@ -3012,7 +3048,7 @@ mod tests { SketchAlgorithm::Kll, ); let grids = [Some(60_000), Some(120_000), Some(180_000)]; - let rule_candidates = pane_candidates(&roots, &grids); + let rule_candidates = tumbling_window_candidates(&roots, &grids); assert_eq!( shared_grids(&roots, &rule_candidates), [vec![], vec![(60_000, 12)]] @@ -3040,7 +3076,7 @@ mod tests { }; let strict = family(&builds(&roots[0])[0]); assert_ne!(strict, family(&builds(&roots[1])[0])); - let rule_candidates = pane_candidates(&roots, &[Some(10_000), Some(10_000)]); + let rule_candidates = tumbling_window_candidates(&roots, &[Some(10_000), Some(10_000)]); let pass2_candidates = window_composition_pass2_candidates(&roots, &rule_candidates).unwrap(); let shared = pass2_candidates @@ -3236,7 +3272,7 @@ mod tests { /// The rule reuses only a finer computation the workload already has: /// keys that are not a superset, another metric, window or measure, or - /// a lone query, give no pane candidate. + /// a lone query, give no tumbling-window candidate. #[test] fn rollup_needs_a_finer_computation_of_the_same_rows() { for queries in [ diff --git a/crates/logical-asap-optimization/src/pass2/logical_memo_candidates.rs b/crates/logical-asap-optimization/src/pass2/logical_memo_candidates.rs index de3c2e298..1720253d3 100644 --- a/crates/logical-asap-optimization/src/pass2/logical_memo_candidates.rs +++ b/crates/logical-asap-optimization/src/pass2/logical_memo_candidates.rs @@ -9,7 +9,7 @@ use crate::pass2::memo::rules::{ finer_aggregate, rollup, scan_labels, shared_configuration_readers, summary_inputs, summary_results, topk, }; -use crate::pass2::memo::shared_configurations::PaneWidthDomain; +use crate::pass2::memo::shared_configurations::TumblingWindowWidthDomain; use crate::pass2::memo::state_groups::{ add_shared_summary_state_groups, check_unexpanded_memo, StateGroupExpansionError, }; @@ -19,18 +19,18 @@ use asap_types::stages::PerComputationLogicalMemoCandidates; use asap_types::stages::{CandidateGenerationTimeout, LogicalASAPMemoCandidates}; use asap_types::workload::QueryWorkloadEntry; -/// What Pass 2 keeps between pane-width rounds: the memo after local -/// window composition and before the other rules, and the pane widths it +/// What Pass 2 keeps between tumbling-window-width rounds: the memo after local +/// window composition and before the other rules, and the tumbling-window widths it /// already holds. Each round adds only the newly exposed widths to it, /// then reruns the other rules on a copy. #[derive(Debug, Clone)] pub struct Pass2RoundState { unexpanded_memo: MemoCandidates, - previous_domain: Option, + previous_domain: Option, } impl Pass2RoundState { - /// The state before the first round: Pass 1's memo, no pane widths. + /// The state before the first round: Pass 1's memo, no tumbling-window widths. pub fn new(pass1: PerComputationLogicalMemoCandidates) -> Self { Self { unexpanded_memo: pass1.memo, @@ -53,21 +53,21 @@ pub enum LogicalASAPMemoCandidatesError { #[error(transparent)] LocalWindowComposition(CandidateGenerationTimeout), /// A later rule or the shared summary-state groups failed. The - /// [`Pass2RoundState`] already holds this round's pane widths and stays + /// [`Pass2RoundState`] already holds this round's tumbling-window widths and stays /// usable for the next round. #[error(transparent)] Expansion(#[from] StateGroupExpansionError), } -/// Stage 1, Pass 2 for one pane-width round: adds to `round`'s memo the -/// local pane candidates for the widths of `domain` that earlier rounds did +/// Stage 1, Pass 2 for one tumbling-window-width round: adds to `round`'s memo the +/// local tumbling-window candidates for the widths of `domain` that earlier rounds did /// not generate (rule 1), records `domain` in `round`, then runs rules 2–9 /// and `add_shared_summary_state_groups` on a copy and returns it. /// `entries[i]` is query `i`'s workload entry. Stops once `deadline` passes. pub fn generate_logical_asap_memo_candidates( round: &mut Pass2RoundState, entries: &[QueryWorkloadEntry], - domain: PaneWidthDomain, + domain: TumblingWindowWidthDomain, deadline: Option, ) -> Result { // 1. Local window composition. @@ -90,7 +90,7 @@ pub fn generate_logical_asap_memo_candidates( /// Pass 2's rules 2–9, in order, on a memo after local window composition. pub(crate) fn apply_sharing_rules( memo: &mut MemoCandidates, - domain: PaneWidthDomain, + domain: TumblingWindowWidthDomain, deadline: Option, ) -> Result<(), StateGroupExpansionError> { check_generation_deadline(deadline)?; diff --git a/crates/logical-asap-optimization/src/pass2/memo.rs b/crates/logical-asap-optimization/src/pass2/memo.rs index 2e0fcaee8..79d991c99 100644 --- a/crates/logical-asap-optimization/src/pass2/memo.rs +++ b/crates/logical-asap-optimization/src/pass2/memo.rs @@ -14,7 +14,7 @@ pub(crate) mod rules; pub(crate) mod shared_configurations; /// Concrete summary-state groups exposed by sharing rules. pub(crate) mod state_groups; -/// Local pane candidates with selected-reader grid configurations. +/// Local tumbling-window candidates with selected-reader grid configurations. pub(crate) mod window_candidates; use std::collections::{HashMap, HashSet}; diff --git a/crates/logical-asap-optimization/src/pass2/memo/rules/rollup.rs b/crates/logical-asap-optimization/src/pass2/memo/rules/rollup.rs index b34846714..7c871bef3 100644 --- a/crates/logical-asap-optimization/src/pass2/memo/rules/rollup.rs +++ b/crates/logical-asap-optimization/src/pass2/memo/rules/rollup.rs @@ -223,7 +223,7 @@ pub(crate) fn add_rollup_candidates( Ok(added) } -/// Existing pane rollup rules applied to producer pairs on already generated +/// Existing tumbling window rollup rules applied to producer pairs on already generated /// legal local grids. No whole-workload choice combinations are constructed. pub(crate) fn add_window_rollup_candidates( memo: &mut MemoCandidates, @@ -231,7 +231,7 @@ pub(crate) fn add_window_rollup_candidates( ) -> Result { check_generation_deadline(deadline)?; use crate::pass2::asap_aware_cse::{ - pane_candidates, window_composition_shared_chain_candidates, + tumbling_window_candidates, window_composition_shared_chain_candidates, }; let mut grids = vec![std::collections::BTreeSet::new(); memo.groups.len()]; for (group, candidates) in memo.candidates.iter().enumerate() { @@ -278,11 +278,14 @@ pub(crate) fn add_window_rollup_candidates( QueryRoot::Operator(Rc::clone(&coarse.root_node)), QueryRoot::Operator(Rc::clone(&fine.root_node)), ]; - let panes = pane_candidates(&roots, &[Some(*grid), Some(*grid)]); - if panes.len() != 2 { + let tumbling_windows = + tumbling_window_candidates(&roots, &[Some(*grid), Some(*grid)]); + if tumbling_windows.len() != 2 { continue; } - for rewritten in window_composition_shared_chain_candidates(&roots, &panes) { + for rewritten in + window_composition_shared_chain_candidates(&roots, &tumbling_windows) + { check_generation_deadline(deadline)?; if rewritten.shared_chains.is_empty() { continue; @@ -334,7 +337,7 @@ mod tests { use crate::pass1::per_computation_candidates::generate_per_computation_candidates; use crate::pass2::memo::generate_dag_of_logical_memo_groups; use crate::pass2::memo::groups::try_populate_pass1; - use crate::pass2::memo::shared_configurations::PaneWidthDomain; + use crate::pass2::memo::shared_configurations::TumblingWindowWidthDomain; use crate::test_support::{lower_promql, sharing_rules_and_shared_state_groups}; use asap_types::ir::properties::AccuracyTarget; use asap_types::ir::QueryRoot; @@ -356,7 +359,7 @@ mod tests { .unwrap(); let memo = sharing_rules_and_shared_state_groups( try_populate_pass1(initial, &inventory).unwrap(), - PaneWidthDomain::Complete, + TumblingWindowWidthDomain::Complete, ) .unwrap(); let root_of = |query: usize| memo.query_roots[query].as_operator().unwrap().0 as usize; diff --git a/crates/logical-asap-optimization/src/pass2/memo/rules/shared_configuration_readers.rs b/crates/logical-asap-optimization/src/pass2/memo/rules/shared_configuration_readers.rs index 99fc7f393..cd975db9e 100644 --- a/crates/logical-asap-optimization/src/pass2/memo/rules/shared_configuration_readers.rs +++ b/crates/logical-asap-optimization/src/pass2/memo/rules/shared_configuration_readers.rs @@ -1,4 +1,4 @@ -//! Expand compatible readers at finite shared sizes and pane horizons before +//! Expand compatible readers at finite shared sizes and tumbling-window horizons before //! interning concrete states. Local candidates remain available. use crate::pass2::memo::check_generation_deadline; use std::collections::{HashMap, HashSet}; @@ -12,10 +12,10 @@ use asap_types::ir::{ASAPOp, Operator, OperatorNode, QueryRoot}; use super::shared_producer::{ add_shared_producer_candidates, spans_groups, Reader, SharedProducer, }; -use crate::pass2::asap_aware_cse::{with_pane_chain, with_resized_build}; +use crate::pass2::asap_aware_cse::{with_resized_build, with_tumbling_window_chain}; use crate::pass2::memo::shared_configurations::{ - pane_configurations_before, shared_size, state_size_configurations, PaneConfiguration, - PaneRequirement, PaneWidthDomain, + shared_size, state_size_configurations, tumbling_window_configurations_before, + TumblingWindowConfiguration, TumblingWindowRequirement, TumblingWindowWidthDomain, }; use crate::pass2::memo::state_groups::{same_range_input, simple_build, StateGroupExpansionError}; use asap_types::stages::MemoCandidates; @@ -69,18 +69,18 @@ struct StateNeed { /// The reader candidate's `accuracy_budget_parts`: the shares its build /// is sized for. accuracy_budget_parts: usize, - /// The reader's normalized pane layout, when its set shares panes: equal + /// The reader's normalized tumbling-window layout, when its set shares tumbling windows: equal /// layouts of one set have one index, so a configuration rewrites them once. normalized_layout: Option, } -/// A shared build's need: one pane layout (its index among the set's pane +/// A shared build's need: one tumbling-window layout (its index among the set's tumbling window /// configurations, for request deduplication) or one size, with the shares /// of the budget its build is sized for ([`covered_parts`]). enum StateCovering { - Panes { + TumblingWindows { configuration_index: usize, - configuration: PaneConfiguration, + configuration: TumblingWindowConfiguration, accuracy_budget_parts: usize, }, Size { @@ -89,13 +89,13 @@ enum StateCovering { }, } -/// Summary builds of one family over one input at different sizes or pane +/// Summary builds of one family over one input at different sizes or tumbling window /// layouts: each reader is rewritten to a build at each covering size or /// layout that serves it, and equal concrete builds become one state group /// later ([`super::super::state_groups`]). struct SummaryStates { deduplicate_requests: bool, - domain: PaneWidthDomain, + domain: TumblingWindowWidthDomain, /// Every candidate's graph signature by group, so no rewrite repeats one. known: HashMap<(usize, String), Vec>>, rewrite_count: usize, @@ -105,7 +105,7 @@ struct SummaryStates { pub(crate) fn add_state_configuration_candidates_in_domain( memo: &mut MemoCandidates, - domain: PaneWidthDomain, + domain: TumblingWindowWidthDomain, deadline: Option, ) -> Result<(), StateGroupExpansionError> { expand_configurations(memo, true, domain, deadline).map(|_| ()) @@ -114,7 +114,7 @@ pub(crate) fn add_state_configuration_candidates_in_domain( fn expand_configurations( memo: &mut MemoCandidates, deduplicate_requests: bool, - domain: PaneWidthDomain, + domain: TumblingWindowWidthDomain, deadline: Option, ) -> Result { let mut states = SummaryStates { @@ -168,7 +168,7 @@ impl SharedProducer for SummaryStates { matches!(node.asap(), Some(ASAPOp::SummaryMerge {children, group_by: None, ..}) if children.len() == 1 && Rc::ptr_eq(&children[0], &build)) }).collect(); - // The existing rewrite changes one plain merge. Other pane uses + // The existing rewrite changes one plain merge. Other tumbling window uses // need their own explicit transformation, not guessed semantics. if merges.len() != 1 { continue; @@ -221,11 +221,11 @@ impl SharedProducer for SummaryStates { set: &[Reader], deadline: Option, ) -> Result, StateGroupExpansionError> { - let pane_requirements: Option> = set + let tumbling_window_requirements: Option> = set .iter() - .map(|reader| pane_requirement(&reader.need)) + .map(|reader| tumbling_window_requirement(&reader.need)) .collect(); - let Some(requirements) = pane_requirements else { + let Some(requirements) = tumbling_window_requirements else { let requirements = set .iter() .map(|reader| match &reader.need.family { @@ -254,11 +254,11 @@ impl SharedProducer for SummaryStates { .collect()); }; Ok( - pane_configurations_before(&requirements, self.domain, deadline)? + tumbling_window_configurations_before(&requirements, self.domain, deadline)? .into_iter() .enumerate() .map( - |(configuration_index, configuration)| StateCovering::Panes { + |(configuration_index, configuration)| StateCovering::TumblingWindows { configuration_index, accuracy_budget_parts: covered_parts(&configuration.family, set), configuration, @@ -276,9 +276,8 @@ impl SharedProducer for SummaryStates { ) -> Result { let need = &reader.need; let serves = match covering { - StateCovering::Panes { configuration, .. } => { - configuration.serves(&pane_requirement(need).expect("pane set")) - } + StateCovering::TumblingWindows { configuration, .. } => configuration + .serves(&tumbling_window_requirement(need).expect("tumbling window set")), StateCovering::Size { family, .. } => serves_size(family, &need.family), }; if !serves { @@ -287,7 +286,7 @@ impl SharedProducer for SummaryStates { let original = memo.candidates[reader.group][need.candidate].clone(); let roots = [QueryRoot::Operator(Rc::clone(&original.root_node))]; let result = match covering { - StateCovering::Panes { + StateCovering::TumblingWindows { configuration_index, configuration, .. @@ -305,7 +304,7 @@ impl SharedProducer for SummaryStates { return Ok(0); } self.rewrite_count += 1; - with_pane_chain( + with_tumbling_window_chain( &roots, need.merge.as_ref().unwrap(), configuration.family.clone(), @@ -352,7 +351,7 @@ impl SharedProducer for SummaryStates { // The rewritten build is sized for the covering's shares, at least // the reader's own. candidate.accuracy_budget_parts = match covering { - StateCovering::Panes { + StateCovering::TumblingWindows { accuracy_budget_parts, .. } @@ -366,10 +365,10 @@ impl SharedProducer for SummaryStates { } } -/// Numbers the normalized pane layouts of `set`'s readers, when it shares -/// panes: different original layouts can produce the same requested +/// Numbers the normalized tumbling-window layouts of `set`'s readers, when it shares +/// tumbling windows: different original layouts can produce the same requested /// reader/configuration, so each is normalized once and its rewrite made once, -/// instead of once for every existing pane width. Numbers continue across +/// instead of once for every existing tumbling-window width. Numbers continue across /// sets (`layouts`), so a configuration index of one set never matches /// another's. fn assign_normalized_layouts( @@ -380,7 +379,7 @@ fn assign_normalized_layouts( ) -> Result<(), StateGroupExpansionError> { let Some(requirements) = set .iter() - .map(|reader| pane_requirement(&reader.need)) + .map(|reader| tumbling_window_requirement(&reader.need)) .collect::>>() else { return Ok(()); @@ -395,7 +394,7 @@ fn assign_normalized_layouts( let need = &reader.need; let original = &memo.candidates[reader.group][need.candidate]; let roots = [QueryRoot::Operator(Rc::clone(&original.root_node))]; - let normalized = with_pane_chain( + let normalized = with_tumbling_window_chain( &roots, need.merge.as_ref().unwrap(), need.family.clone(), @@ -431,7 +430,7 @@ fn graph_signature(node: &Rc) -> Result Option { +fn tumbling_window_requirement(reader: &StateNeed) -> Option { let ASAPOp::SummaryTumblingWindowChain { tumbling_window_width_ms, tumbling_window_count, @@ -447,7 +446,7 @@ fn pane_requirement(reader: &StateNeed) -> Option { else { return None; }; - Some(PaneRequirement { + Some(TumblingWindowRequirement { tumbling_window_width_ms: *tumbling_window_width_ms, window_ms: tumbling_window_width_ms.checked_mul(u64::from( newest_tumbling_windows.unwrap_or(*tumbling_window_count), @@ -538,9 +537,9 @@ mod tests { use asap_types::workload::{QueryRecurrence, RepeatedDemand, RepetitionInterval}; // Request deduplication preserves the entire declared finite configuration - // set while avoiding repeated rewrites through every initial pane layout. + // set while avoiding repeated rewrites through every initial tumbling-window layout. #[test] - fn equivalent_pane_layouts_expand_each_configuration_once() { + fn equivalent_tumbling_window_layouts_expand_each_configuration_once() { let target = AccuracyTarget::max_error(0.01).failure_probability(0.001); let roots: Vec<_> = [0.95, 0.99] .into_iter() @@ -566,10 +565,15 @@ mod tests { crate::pass2::memo::window_candidates::add_local_window_composition(&mut memo, &entries); let mut unoptimized = memo.clone(); let optimized_rewrites = - expand_configurations(&mut memo, true, PaneWidthDomain::Complete, None).unwrap(); - let exhaustive_rewrites = - expand_configurations(&mut unoptimized, false, PaneWidthDomain::Complete, None) + expand_configurations(&mut memo, true, TumblingWindowWidthDomain::Complete, None) .unwrap(); + let exhaustive_rewrites = expand_configurations( + &mut unoptimized, + false, + TumblingWindowWidthDomain::Complete, + None, + ) + .unwrap(); assert!( optimized_rewrites < exhaustive_rewrites, "{optimized_rewrites} versus {exhaustive_rewrites}" @@ -655,7 +659,7 @@ mod tests { "{parts_of_size:?}" ); let before = memo.candidates.iter().map(Vec::len).sum::(); - expand_configurations(&mut memo, true, PaneWidthDomain::SeedGrids, None).unwrap(); + expand_configurations(&mut memo, true, TumblingWindowWidthDomain::SeedGrids, None).unwrap(); assert!(memo.candidates.iter().map(Vec::len).sum::() > before); for candidate in memo.candidates.iter().flatten() { for k in kll_sizes(candidate) { diff --git a/crates/logical-asap-optimization/src/pass2/memo/shared_configurations.rs b/crates/logical-asap-optimization/src/pass2/memo/shared_configurations.rs index 7a462325d..2a2240572 100644 --- a/crates/logical-asap-optimization/src/pass2/memo/shared_configurations.rs +++ b/crates/logical-asap-optimization/src/pass2/memo/shared_configurations.rs @@ -10,7 +10,7 @@ use std::collections::BTreeSet; /// Width domain used by the same candidate rules. Seed grids are only a /// feasible-plan heuristic; optimality must use the complete domain. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub enum PaneWidthDomain { +pub enum TumblingWindowWidthDomain { #[default] Complete, SeedGrids, @@ -23,17 +23,17 @@ pub enum PaneWidthDomain { } #[cfg(test)] -pub(crate) fn pane_width_domain( +pub(crate) fn tumbling_window_width_domain( grids: impl IntoIterator, - domain: PaneWidthDomain, + domain: TumblingWindowWidthDomain, ) -> Vec { - pane_width_domain_before(grids, domain, None) + tumbling_window_width_domain_before(grids, domain, None) .expect("generation without a deadline cannot time out") } -pub(crate) fn pane_width_domain_before( +pub(crate) fn tumbling_window_width_domain_before( grids: impl IntoIterator, - domain: PaneWidthDomain, + domain: TumblingWindowWidthDomain, deadline: Option, ) -> Result, asap_types::stages::CandidateGenerationTimeout> { super::check_generation_deadline(deadline)?; @@ -44,10 +44,10 @@ pub(crate) fn pane_width_domain_before( widths.insert(width); } } - if domain == PaneWidthDomain::Complete { + if domain == TumblingWindowWidthDomain::Complete { let mut complete = BTreeSet::new(); for grid in widths { - complete.extend(pane_width_divisors_before(grid, deadline)?); + complete.extend(tumbling_window_width_divisors_before(grid, deadline)?); } return Ok(complete.into_iter().collect()); } @@ -68,7 +68,7 @@ pub(crate) fn pane_width_domain_before( } widths.extend(additional); } - if let PaneWidthDomain::Expanding { max_parts } = domain { + if let TumblingWindowWidthDomain::Expanding { max_parts } = domain { // Work is proportional to the requested frontier, not the complete // divisor domain. Do not introduce the finest reciprocal width early. for grid in grids { @@ -88,7 +88,7 @@ pub(crate) fn pane_width_domain_before( /// Integer-domain frontier, independent of expensive candidate graph expansion. /// Complete divisor lists are computed once under the caller's deadline. -pub struct PaneWidthFrontier { +pub struct TumblingWindowWidthFrontier { grids: Vec, complete: Vec>, previous: Vec>, @@ -96,7 +96,7 @@ pub struct PaneWidthFrontier { finished: bool, } -impl PaneWidthFrontier { +impl TumblingWindowWidthFrontier { pub fn new( grids: impl IntoIterator, deadline: Option, @@ -109,7 +109,7 @@ impl PaneWidthFrontier { .collect(); let complete = grids .iter() - .map(|&grid| pane_width_divisors_before(grid, deadline)) + .map(|&grid| tumbling_window_width_divisors_before(grid, deadline)) .collect::, _>>()?; let previous = grids.iter().map(|&grid| vec![grid]).collect(); Ok(Self { @@ -126,7 +126,8 @@ impl PaneWidthFrontier { pub fn next_domain( &mut self, deadline: Option, - ) -> Result, asap_types::stages::CandidateGenerationTimeout> { + ) -> Result, asap_types::stages::CandidateGenerationTimeout> + { while !self.finished { super::check_generation_deadline(deadline)?; let current: Vec> = self @@ -144,11 +145,11 @@ impl PaneWidthFrontier { self.max_parts = self.max_parts.saturating_mul(2); if current == self.complete { self.finished = true; - return Ok(Some(PaneWidthDomain::Complete)); + return Ok(Some(TumblingWindowWidthDomain::Complete)); } if current != self.previous { self.previous = current; - return Ok(Some(PaneWidthDomain::Expanding { + return Ok(Some(TumblingWindowWidthDomain::Expanding { max_parts: self.max_parts / 2, })); } @@ -158,19 +159,19 @@ impl PaneWidthFrontier { } #[derive(Debug, Clone)] -pub(crate) struct PaneRequirement { +pub(crate) struct TumblingWindowRequirement { pub tumbling_window_width_ms: u64, pub window_ms: u64, pub family: FieldDataType, } #[derive(Debug, Clone, PartialEq)] -pub(crate) struct PaneConfiguration { +pub(crate) struct TumblingWindowConfiguration { pub tumbling_window_width_ms: u64, pub longest_window_ms: u64, pub family: FieldDataType, } -impl PaneConfiguration { - pub fn serves(&self, reader: &PaneRequirement) -> bool { +impl TumblingWindowConfiguration { + pub fn serves(&self, reader: &TumblingWindowRequirement) -> bool { self.tumbling_window_width_ms > 0 && reader .tumbling_window_width_ms @@ -190,12 +191,12 @@ impl PaneConfiguration { /// The time representation fixes the search resolution; no representative-width /// sampling or arbitrary candidate cap changes this domain. #[cfg(test)] -pub(crate) fn pane_width_divisors(grid_ms: u64) -> Vec { - pane_width_divisors_before(grid_ms, None) +pub(crate) fn tumbling_window_width_divisors(grid_ms: u64) -> Vec { + tumbling_window_width_divisors_before(grid_ms, None) .expect("generation without a deadline cannot time out") } -fn pane_width_divisors_before( +fn tumbling_window_width_divisors_before( grid_ms: u64, deadline: Option, ) -> Result, asap_types::stages::CandidateGenerationTimeout> { @@ -220,24 +221,26 @@ fn pane_width_divisors_before( /// Complete divisor grids, distinct required horizons, and joined state sizes. /// This enumerates configurations of one compatible producer, not workload plans. #[cfg(test)] -fn pane_configurations(readers: &[PaneRequirement]) -> Vec { - pane_configurations_in_domain(readers, PaneWidthDomain::Complete) +fn tumbling_window_configurations( + readers: &[TumblingWindowRequirement], +) -> Vec { + tumbling_window_configurations_in_domain(readers, TumblingWindowWidthDomain::Complete) } #[cfg(test)] -fn pane_configurations_in_domain( - readers: &[PaneRequirement], - domain: PaneWidthDomain, -) -> Vec { - pane_configurations_before(readers, domain, None) +fn tumbling_window_configurations_in_domain( + readers: &[TumblingWindowRequirement], + domain: TumblingWindowWidthDomain, +) -> Vec { + tumbling_window_configurations_before(readers, domain, None) .expect("generation without a deadline cannot time out") } -pub(crate) fn pane_configurations_before( - readers: &[PaneRequirement], - domain: PaneWidthDomain, +pub(crate) fn tumbling_window_configurations_before( + readers: &[TumblingWindowRequirement], + domain: TumblingWindowWidthDomain, deadline: Option, -) -> Result, asap_types::stages::CandidateGenerationTimeout> { +) -> Result, asap_types::stages::CandidateGenerationTimeout> { super::check_generation_deadline(deadline)?; if readers .iter() @@ -245,7 +248,7 @@ pub(crate) fn pane_configurations_before( { return Ok(Vec::new()); } - let grids = pane_width_domain_before( + let grids = tumbling_window_width_domain_before( readers.iter().map(|reader| { greatest_common_divisor(reader.tumbling_window_width_ms, reader.window_ms) }), @@ -255,7 +258,7 @@ pub(crate) fn pane_configurations_before( let mut configurations = Vec::new(); for &tumbling_window_width_ms in &grids { super::check_generation_deadline(deadline)?; - let eligible: Vec<&PaneRequirement> = readers + let eligible: Vec<&TumblingWindowRequirement> = readers .iter() .filter(|r| { r.tumbling_window_width_ms @@ -289,12 +292,12 @@ pub(crate) fn pane_configurations_before( super::check_generation_deadline(deadline)?; for family in &sizes { super::check_generation_deadline(deadline)?; - let configuration = PaneConfiguration { + let configuration = TumblingWindowConfiguration { tumbling_window_width_ms, longest_window_ms, family: family.clone(), }; - let admitted: Vec<&&PaneRequirement> = eligible + let admitted: Vec<&&TumblingWindowRequirement> = eligible .iter() .filter(|r| configuration.serves(r)) .collect(); @@ -461,20 +464,20 @@ mod tests { // Equal physical capacities still carry distinct recorded error contracts; // only the joined contract may serve the stricter probability requirement. #[test] - fn pane_configuration_keeps_failure_probability_contract() { - let weaker = PaneConfiguration { + fn tumbling_window_configuration_keeps_failure_probability_contract() { + let weaker = TumblingWindowConfiguration { tumbling_window_width_ms: 1000, longest_window_ms: 5000, family: kll(None), }; - let strict = PaneRequirement { + let strict = TumblingWindowRequirement { tumbling_window_width_ms: 1000, window_ms: 5000, family: kll(Some(0.001)), }; assert!(!weaker.serves(&strict)); - let configurations = pane_configurations(&[ - PaneRequirement { + let configurations = tumbling_window_configurations(&[ + TumblingWindowRequirement { family: kll(None), ..strict.clone() }, @@ -492,34 +495,40 @@ mod tests { // just the coarsest width or GCDs of the readers' original configurations. #[test] fn width_frontier_skips_empty_rounds_and_detects_actual_completion() { - let mut frontier = PaneWidthFrontier::new([97], None).unwrap(); + let mut frontier = TumblingWindowWidthFrontier::new([97], None).unwrap(); assert_eq!( frontier.next_domain(None).unwrap(), - Some(PaneWidthDomain::Complete) + Some(TumblingWindowWidthDomain::Complete) ); assert_eq!(frontier.next_domain(None).unwrap(), None); - let mut frontier = PaneWidthFrontier::new([12], None).unwrap(); + let mut frontier = TumblingWindowWidthFrontier::new([12], None).unwrap(); assert_eq!( frontier.next_domain(None).unwrap(), - Some(PaneWidthDomain::Expanding { max_parts: 4 }) + Some(TumblingWindowWidthDomain::Expanding { max_parts: 4 }) ); assert_eq!( frontier.next_domain(None).unwrap(), - Some(PaneWidthDomain::Expanding { max_parts: 8 }) + Some(TumblingWindowWidthDomain::Expanding { max_parts: 8 }) ); assert_eq!( frontier.next_domain(None).unwrap(), - Some(PaneWidthDomain::Complete) + Some(TumblingWindowWidthDomain::Complete) + ); + assert!( + TumblingWindowWidthFrontier::new([u64::MAX], Some(std::time::Instant::now())).is_err() ); - assert!(PaneWidthFrontier::new([u64::MAX], Some(std::time::Instant::now())).is_err()); } #[test] fn expanding_widths_are_nested_and_eventually_complete() { let grids = [60, 90]; - let mut previous = pane_width_domain(grids, PaneWidthDomain::SeedGrids); + let mut previous = + tumbling_window_width_domain(grids, TumblingWindowWidthDomain::SeedGrids); for max_parts in [2, 4, 8, 16, 32, 64, 128] { - let next = pane_width_domain(grids, PaneWidthDomain::Expanding { max_parts }); + let next = tumbling_window_width_domain( + grids, + TumblingWindowWidthDomain::Expanding { max_parts }, + ); assert!(previous.iter().all(|width| next.contains(width))); assert!(next .iter() @@ -528,17 +537,20 @@ mod tests { } assert_eq!( previous, - pane_width_domain(grids, PaneWidthDomain::Complete) + tumbling_window_width_domain(grids, TumblingWindowWidthDomain::Complete) ); } #[test] - fn expanding_domain_defers_fine_panes_and_obeys_deadline() { - let widths = pane_width_domain([60_000], PaneWidthDomain::Expanding { max_parts: 4 }); + fn expanding_domain_defers_fine_tumbling_windows_and_obeys_deadline() { + let widths = tumbling_window_width_domain( + [60_000], + TumblingWindowWidthDomain::Expanding { max_parts: 4 }, + ); assert_eq!(widths, vec![15_000, 20_000, 30_000, 60_000]); - assert!(pane_width_domain_before( + assert!(tumbling_window_width_domain_before( [u64::MAX], - PaneWidthDomain::Expanding { + TumblingWindowWidthDomain::Expanding { max_parts: u64::MAX }, Some(std::time::Instant::now()), @@ -547,13 +559,13 @@ mod tests { } #[test] - fn pane_width_domain_contains_every_legal_divisor() { - let reader = PaneRequirement { + fn tumbling_window_width_domain_contains_every_legal_divisor() { + let reader = TumblingWindowRequirement { tumbling_window_width_ms: 12, window_ms: 60, family: kll(None), }; - let widths: BTreeSet<_> = pane_configurations(&[reader]) + let widths: BTreeSet<_> = tumbling_window_configurations(&[reader]) .into_iter() .map(|configuration| configuration.tumbling_window_width_ms) .collect(); @@ -567,7 +579,7 @@ mod tests { let expected: Vec<_> = (1..=grid) .filter(|width| grid.is_multiple_of(*width)) .collect(); - assert_eq!(pane_width_divisors(grid), expected); + assert_eq!(tumbling_window_width_divisors(grid), expected); } } @@ -577,16 +589,18 @@ mod tests { #[ignore = "flaky: depends on a wall-clock deadline (#1067)"] fn large_grid_enumeration_honors_deadline() { let deadline = std::time::Instant::now() + std::time::Duration::from_millis(2); - assert!( - pane_width_domain_before([u64::MAX], PaneWidthDomain::Complete, Some(deadline),) - .is_err() - ); + assert!(tumbling_window_width_domain_before( + [u64::MAX], + TumblingWindowWidthDomain::Complete, + Some(deadline), + ) + .is_err()); } // Public finite enumeration rejects invalid grids instead of dividing by zero. #[test] - fn invalid_pane_grid_has_no_configurations() { - assert!(pane_configurations(&[PaneRequirement { + fn invalid_tumbling_window_grid_has_no_configurations() { + assert!(tumbling_window_configurations(&[TumblingWindowRequirement { tumbling_window_width_ms: 0, window_ms: 5000, family: kll(None) diff --git a/crates/logical-asap-optimization/src/pass2/memo/state_groups.rs b/crates/logical-asap-optimization/src/pass2/memo/state_groups.rs index 103d7d1dc..c58f89d22 100644 --- a/crates/logical-asap-optimization/src/pass2/memo/state_groups.rs +++ b/crates/logical-asap-optimization/src/pass2/memo/state_groups.rs @@ -1,7 +1,7 @@ //! Opt-in concrete summary-state groups for the candidate-connection planner. //! -//! Simple summary builds and pane chains directly over canonical input groups -//! gain finite compatible sizes and pane horizons before exact state interning. +//! Simple summary builds and tumbling-window chains directly over canonical input groups +//! gain finite compatible sizes and tumbling-window horizons before exact state interning. //! Concrete state identity includes capacity, guarantee, update/filter semantics, //! and materialization. Supported private projections/filters gain explicit input groups. @@ -475,12 +475,12 @@ mod tests { added_reader_candidates: usize, } - /// Pass 2 after local window composition, over the complete pane-width + /// Pass 2 after local window composition, over the complete tumbling-window-width /// domain, as the Pass 2 entry runs it. fn expand(memo: MemoCandidates) -> Result { let memo = crate::test_support::sharing_rules_and_shared_state_groups( memo, - super::super::shared_configurations::PaneWidthDomain::Complete, + super::super::shared_configurations::TumblingWindowWidthDomain::Complete, )?; Ok(expanded(memo)) } @@ -506,7 +506,7 @@ mod tests { // linear state lookup and compare every generated candidate and state. #[test] fn indexed_state_interning_preserves_linear_catalog() { - let memo = with_local_panes( + let memo = with_local_tumbling_windows( workload(&[ ("quantile_over_time(0.95, latency[5m])", 0.01), ("quantile_over_time(0.99, latency[15m])", 0.02), @@ -514,7 +514,7 @@ mod tests { ]), &[6, 6, 6], ); - let domain = super::super::shared_configurations::PaneWidthDomain::Complete; + let domain = super::super::shared_configurations::TumblingWindowWidthDomain::Complete; let mut memo = memo; crate::pass2::logical_memo_candidates::apply_sharing_rules(&mut memo, domain, None) .unwrap(); @@ -561,7 +561,7 @@ mod tests { assert!(matches!( crate::pass2::logical_memo_candidates::apply_sharing_rules( &mut memo, - super::super::shared_configurations::PaneWidthDomain::Complete, + super::super::shared_configurations::TumblingWindowWidthDomain::Complete, Some(Instant::now()) ), Err(StateGroupExpansionError::DeadlineExceeded(_)) @@ -685,8 +685,10 @@ mod tests { assert_eq!(expanded.memo.groups.len(), count); } - fn with_local_panes(mut memo: MemoCandidates, grids: &[u64]) -> MemoCandidates { - use crate::pass2::asap_aware_cse::{pane_candidates, window_composition_pass2_candidates}; + fn with_local_tumbling_windows(mut memo: MemoCandidates, grids: &[u64]) -> MemoCandidates { + use crate::pass2::asap_aware_cse::{ + tumbling_window_candidates, window_composition_pass2_candidates, + }; let boundaries: HashMap<_, _> = memo .groups @@ -698,11 +700,11 @@ mod tests { let grid = grids[memo.groups[group].queries[0]]; for candidate in memo.candidates[group].clone() { let roots = vec![QueryRoot::Operator(Rc::clone(&candidate.root_node))]; - let panes = pane_candidates(&roots, &[Some(grid)]); - if panes.len() != 1 { + let tumbling_windows = tumbling_window_candidates(&roots, &[Some(grid)]); + if tumbling_windows.len() != 1 { continue; } - let rewritten = window_composition_pass2_candidates(&roots, &panes) + let rewritten = window_composition_pass2_candidates(&roots, &tumbling_windows) .unwrap() .remove(0) .roots @@ -710,9 +712,9 @@ mod tests { let QueryRoot::Operator(node) = &rewritten[0] else { unreachable!() }; - // Pane rewriting recomposes guarantees; restore the original + // Tumbling window rewriting recomposes guarantees; restore the original // canonical scan boundary rather than making it a private input. - let input = panes[0].build.children()[0].children()[0].clone(); + let input = tumbling_windows[0].build.children()[0].children()[0].clone(); let chain = OperatorNode::reachable(node) .into_iter() .find(|node| { @@ -720,21 +722,21 @@ mod tests { }) .unwrap(); let node = replace_node(node, chain.children()[0], &input); - let mut pane = candidate; - pane.root_node = Rc::clone(&node); - pane.input_groups = boundary_groups(&node, &boundaries); - pane.origin = OriginOfCandidateForMemoGroup::TumblingWindows; - memo.candidates[group].push(pane); + let mut tumbling_window = candidate; + tumbling_window.root_node = Rc::clone(&node); + tumbling_window.input_groups = boundary_groups(&node, &boundaries); + tumbling_window.origin = OriginOfCandidateForMemoGroup::TumblingWindows; + memo.candidates[group].push(tumbling_window); } } memo } - // Different label projections can consume one wider pane state through + // Different label projections can consume one wider tumbling-window state through // typed reader projections; no original scan needs to contain the union. #[test] - fn disjoint_scan_labels_share_a_union_pane_state_with_typed_readers() { - let original = with_local_panes( + fn disjoint_scan_labels_share_a_union_tumbling_window_state_with_typed_readers() { + let original = with_local_tumbling_windows( workload(&[ ("avg by (job) (quantile_over_time(0.95, latency[5m]))", 0.01), ( @@ -824,8 +826,8 @@ mod tests { // Widening shifts identity's position but must retain its full typed field // and remap the source's unique key rather than treating it as a plain label. #[test] - fn widened_pane_inputs_preserve_series_identity_and_keys() { - let memo = with_local_panes( + fn widened_tumbling_window_inputs_preserve_series_identity_and_keys() { + let memo = with_local_tumbling_windows( workload_with_identity( &[ ("avg by (job) (quantile_over_time(0.95, latency[5m]))", 0.01), @@ -857,7 +859,7 @@ mod tests { .any(|field| field.name == *name) }) }) - .expect("identity-carrying union pane state"); + .expect("identity-carrying union tumbling-window state"); let input = state.logical_node.children()[0]; assert!(input.schema.has_promql_series_identity()); let identity = input @@ -928,7 +930,7 @@ mod tests { let mut memo = workload_with_identity(&queries, true); crate::pass2::logical_memo_candidates::apply_sharing_rules( &mut memo, - super::super::shared_configurations::PaneWidthDomain::Complete, + super::super::shared_configurations::TumblingWindowWidthDomain::Complete, None, ) .unwrap(); @@ -973,7 +975,7 @@ mod tests { // Label widening must not erase source predicates in order to create reuse. #[test] fn scan_label_widening_keeps_different_filters_separate() { - let original = with_local_panes( + let original = with_local_tumbling_windows( workload(&[ ( "avg by (job) (quantile_over_time(0.95, latency{env=\"a\"}[5m]))", @@ -993,23 +995,23 @@ mod tests { .all(|id| expanded.memo.groups[id.0 as usize].queries.len() == 1)); } - // Real pane rewrites expose one identical chain to distinct quantile readers. + // Real tumbling window rewrites expose one identical chain to distinct quantile readers. #[test] - fn identical_local_pane_chains_share_state_without_removing_local_choices() { - let original = with_local_panes( + fn identical_local_tumbling_window_chains_share_state_without_removing_local_choices() { + let original = with_local_tumbling_windows( workload(&[ ("quantile_over_time(0.95, latency[5m])", 0.01), ("quantile_over_time(0.99, latency[5m])", 0.01), ]), &[60_000, 60_000], ); - let local_panes = original + let local_tumbling_windows = original .candidates .iter() .flatten() .filter(|candidate| candidate.origin == OriginOfCandidateForMemoGroup::TumblingWindows) .count(); - assert!(local_panes >= 2); + assert!(local_tumbling_windows >= 2); let expanded = expand(original.clone()).unwrap(); let chains: Vec<_> = expanded .state_groups @@ -1050,8 +1052,8 @@ mod tests { // A finer grid and longer horizon serves a shorter reader through newest_tumbling_windows. #[test] - fn different_pane_grids_and_horizons_gain_explicit_newest_pane_readers() { - let original = with_local_panes( + fn different_tumbling_window_grids_and_horizons_gain_explicit_newest_tumbling_window_readers() { + let original = with_local_tumbling_windows( workload(&[ ("quantile_over_time(0.95, latency[5m])", 0.01), ("quantile_over_time(0.99, latency[15m])", 0.02), @@ -1279,7 +1281,7 @@ mod tests { .any(|after| Rc::ptr_eq(&before.root_node, &after.root_node)))); } - // A global SQL-window quantile can consume newest panes of a longer, + // A global SQL-window quantile can consume newest tumbling windows of a longer, // finer-grouped chain through the existing regrouping rule. #[test] fn grouped_window_rollup_exposes_one_finer_chain() { @@ -1323,7 +1325,7 @@ mod tests { let generated = generate_per_computation_candidates(&generate_dag_of_logical_memo_groups(&roots)) .unwrap(); - let memo = with_local_panes( + let memo = with_local_tumbling_windows( try_populate_pass1(initial, &generated).unwrap(), &[10_000, 10_000], ); diff --git a/crates/logical-asap-optimization/src/pass2/memo/window_candidates.rs b/crates/logical-asap-optimization/src/pass2/memo/window_candidates.rs index 154e14002..ef505371d 100644 --- a/crates/logical-asap-optimization/src/pass2/memo/window_candidates.rs +++ b/crates/logical-asap-optimization/src/pass2/memo/window_candidates.rs @@ -1,10 +1,14 @@ -//! Local pane candidates generated from compatible reader grids. Selected +//! Local tumbling-window candidates generated from compatible reader grids. Selected //! connections, not the union of potential consumers, determine eligibility. #[cfg(test)] -use super::shared_configurations::pane_width_divisors; -use super::shared_configurations::{pane_width_domain_before, PaneWidthDomain}; +use super::shared_configurations::tumbling_window_width_divisors; +use super::shared_configurations::{ + tumbling_window_width_domain_before, TumblingWindowWidthDomain, +}; use super::{check_generation_deadline, CandidateGenerationTimeout}; -use crate::pass2::asap_aware_cse::{pane_candidates, window_composition_pass2_candidates}; +use crate::pass2::asap_aware_cse::{ + tumbling_window_candidates, window_composition_pass2_candidates, +}; use asap_types::ir::flat::flatten; use asap_types::ir::{OperatorNode, QueryRoot}; use asap_types::stages::{ @@ -22,14 +26,14 @@ use std::time::Instant; /// chosen consumers, including ingestion alignment and range evaluation steps. #[cfg(any(test, feature = "test-support"))] pub fn add_local_window_composition(memo: &mut Memo, entries: &[QueryWorkloadEntry]) { - add_local_window_composition_in_domain(memo, entries, PaneWidthDomain::Complete); + add_local_window_composition_in_domain(memo, entries, TumblingWindowWidthDomain::Complete); } #[cfg(any(test, feature = "test-support"))] pub fn add_local_window_composition_in_domain( memo: &mut Memo, entries: &[QueryWorkloadEntry], - domain: PaneWidthDomain, + domain: TumblingWindowWidthDomain, ) { try_add_local_window_composition_in_domain(memo, entries, domain, None) .expect("generation without a deadline cannot time out"); @@ -40,24 +44,24 @@ pub fn add_local_window_composition_in_domain( pub(crate) fn try_add_local_window_composition_in_domain( memo: &mut Memo, entries: &[QueryWorkloadEntry], - domain: PaneWidthDomain, + domain: TumblingWindowWidthDomain, deadline: Option, ) -> Result<(), CandidateGenerationTimeout> { extend_local_window_composition(memo, entries, None, domain, deadline) } -/// Extend a pre-state-expansion memo with only newly exposed pane widths. +/// Extend a pre-state-expansion memo with only newly exposed tumbling-window widths. /// `previous` must describe widths already generated for this memo. pub(crate) fn extend_local_window_composition( memo: &mut Memo, entries: &[QueryWorkloadEntry], - previous: Option, - domain: PaneWidthDomain, + previous: Option, + domain: TumblingWindowWidthDomain, deadline: Option, ) -> Result<(), CandidateGenerationTimeout> { check_generation_deadline(deadline)?; // Flatten each existing/generated graph once. Repeatedly flattening the - // growing candidate list makes a complete pane-width domain quadratic. + // growing candidate list makes a complete tumbling-window-width domain quadratic. let mut known: HashMap<(usize, String), Vec> = HashMap::new(); for (group, candidates) in memo.candidates.iter().enumerate() { check_generation_deadline(deadline)?; @@ -69,7 +73,7 @@ pub(crate) fn extend_local_window_composition( } } for (group, candidate) in - local_pane_candidates_delta(memo, entries, previous, domain, deadline)? + local_tumbling_window_candidates_delta(memo, entries, previous, domain, deadline)? { check_generation_deadline(deadline)?; let Ok(signature) = candidate_signature(&candidate) else { @@ -93,31 +97,36 @@ pub(crate) fn extend_local_window_composition( Ok(()) } -/// Expand legal pane widths while preserving canonical memo inputs. +/// Expand legal tumbling-window widths while preserving canonical memo inputs. #[cfg(test)] -fn local_pane_candidates( +fn local_tumbling_window_candidates( memo: &Memo, entries: &[QueryWorkloadEntry], ) -> Vec<(usize, CandidateForMemoGroup)> { - local_pane_candidates_before(memo, entries, PaneWidthDomain::Complete, None) - .expect("generation without a deadline cannot time out") + local_tumbling_window_candidates_before( + memo, + entries, + TumblingWindowWidthDomain::Complete, + None, + ) + .expect("generation without a deadline cannot time out") } #[cfg(test)] -fn local_pane_candidates_before( +fn local_tumbling_window_candidates_before( memo: &Memo, entries: &[QueryWorkloadEntry], - domain: PaneWidthDomain, + domain: TumblingWindowWidthDomain, deadline: Option, ) -> Result, CandidateGenerationTimeout> { - local_pane_candidates_delta(memo, entries, None, domain, deadline) + local_tumbling_window_candidates_delta(memo, entries, None, domain, deadline) } -fn local_pane_candidates_delta( +fn local_tumbling_window_candidates_delta( memo: &Memo, entries: &[QueryWorkloadEntry], - previous: Option, - domain: PaneWidthDomain, + previous: Option, + domain: TumblingWindowWidthDomain, deadline: Option, ) -> Result, CandidateGenerationTimeout> { check_generation_deadline(deadline)?; @@ -125,7 +134,7 @@ fn local_pane_candidates_delta( let mut own: Vec<(usize, CandidateForMemoGroup)> = Vec::new(); for (g, group) in memo.groups.iter().enumerate() { check_generation_deadline(deadline)?; - let grids = pane_width_domain_before( + let grids = tumbling_window_width_domain_before( group .queries .iter() @@ -135,7 +144,7 @@ fn local_pane_candidates_delta( )?; let previous_widths = previous .map(|previous| { - pane_width_domain_before( + tumbling_window_width_domain_before( group .queries .iter() @@ -170,11 +179,12 @@ fn local_pane_candidates_delta( }) .collect::>(); let roots = [QueryRoot::Operator(Rc::clone(&candidate.root_node))]; - let panes = pane_candidates(&roots, &[Some(grid)]); - if panes.is_empty() { + let tumbling_windows = tumbling_window_candidates(&roots, &[Some(grid)]); + if tumbling_windows.is_empty() { continue; } - let Ok(pass2_candidates) = window_composition_pass2_candidates(&roots, &panes) + let Ok(pass2_candidates) = + window_composition_pass2_candidates(&roots, &tumbling_windows) else { continue; }; @@ -189,7 +199,7 @@ fn local_pane_candidates_delta( // Guarantee recomposition can clone unchanged source scans. // Restore only an unambiguous original scan with the same full // source/schema/execution contract; scans have intrinsic exact - // semantics. Otherwise the pane's canonical input disappears. + // semantics. Otherwise the tumbling window's canonical input disappears. let mut restored = HashMap::new(); for rewritten in OperatorNode::reachable(node) { check_generation_deadline(deadline)?; @@ -258,15 +268,15 @@ fn restore_scan_boundaries( // Union of complete divisor domains covers individual readers and every // compatible subset: common widths belong to each reader's divisor domain. -// Nongridded potential consumers do not veto a repeated reader's panes. +// Nongridded potential consumers do not veto a repeated reader's tumbling windows. #[cfg(test)] fn compatible_reader_grids(group: &MemoGroup, entries: &[QueryWorkloadEntry]) -> Vec { - super::shared_configurations::pane_width_domain( + super::shared_configurations::tumbling_window_width_domain( group .queries .iter() .filter_map(|&query| entries.get(query)?.evaluation_grid_ms()), - PaneWidthDomain::Complete, + TumblingWindowWidthDomain::Complete, ) } @@ -358,7 +368,7 @@ mod tests { assert!(try_add_local_window_composition_in_domain( &mut memo, &entries, - PaneWidthDomain::Complete, + TumblingWindowWidthDomain::Complete, Some(Instant::now()) ) .is_err()); @@ -374,14 +384,14 @@ mod tests { let mut incremental = base.clone(); let mut previous = None; for domain in [ - PaneWidthDomain::SeedGrids, - PaneWidthDomain::Expanding { max_parts: 4 }, - PaneWidthDomain::Expanding { max_parts: 8 }, - PaneWidthDomain::Complete, + TumblingWindowWidthDomain::SeedGrids, + TumblingWindowWidthDomain::Expanding { max_parts: 4 }, + TumblingWindowWidthDomain::Expanding { max_parts: 8 }, + TumblingWindowWidthDomain::Complete, ] { extend_local_window_composition(&mut incremental, &entries, previous, domain, None) .unwrap(); - assert!(local_pane_candidates_delta( + assert!(local_tumbling_window_candidates_delta( &incremental, &entries, Some(domain), @@ -431,11 +441,11 @@ mod tests { (memo, entries) } - // A nongridded potential reader cannot eliminate another reader's panes. + // A nongridded potential reader cannot eliminate another reader's tumbling windows. #[test] - fn nongridded_potential_reader_preserves_local_pane_candidates() { + fn nongridded_potential_reader_preserves_local_tumbling_window_candidates() { let (memo, entries) = fixture(&[repeated(60_000), QueryRecurrence::Unknown]); - let candidates = local_pane_candidates(&memo, &entries); + let candidates = local_tumbling_window_candidates(&memo, &entries); assert!(!candidates.is_empty()); assert!(candidates.iter().all( |(_, candidate)| candidate.origin == OriginOfCandidateForMemoGroup::TumblingWindows @@ -453,9 +463,9 @@ mod tests { let group = &memo.groups[memo.query_roots[0].as_operator().unwrap().0 as usize]; assert_eq!( compatible_reader_grids(group, &entries), - pane_width_divisors(120_000) + tumbling_window_width_divisors(120_000) .into_iter() - .chain(pane_width_divisors(180_000)) + .chain(tumbling_window_width_divisors(180_000)) .collect::>() .into_iter() .collect::>() diff --git a/crates/logical-asap-optimization/src/test_support.rs b/crates/logical-asap-optimization/src/test_support.rs index 2114b1a3c..64115fbd7 100644 --- a/crates/logical-asap-optimization/src/test_support.rs +++ b/crates/logical-asap-optimization/src/test_support.rs @@ -11,7 +11,7 @@ pub use crate::pass1::per_computation_candidates::{ compose_logical_candidate, generate_per_computation_candidates, PerComputationCandidates, }; pub use crate::pass2::asap_aware_cse::{ - pane_candidates, rollup_candidates, rollup_pass2_candidates, + rollup_candidates, rollup_pass2_candidates, tumbling_window_candidates, window_composition_pass2_candidates, window_composition_shared_chain_candidates, with_union_scan_labels, }; @@ -35,7 +35,7 @@ use asap_types::workload::{ /// [`generate_logical_asap_memo_candidates`](crate::pass2::logical_memo_candidates::generate_logical_asap_memo_candidates). pub fn sharing_rules_and_shared_state_groups( mut memo: asap_types::stages::MemoCandidates, - domain: crate::pass2::memo::shared_configurations::PaneWidthDomain, + domain: crate::pass2::memo::shared_configurations::TumblingWindowWidthDomain, ) -> Result< asap_types::stages::MemoCandidates, crate::pass2::memo::state_groups::StateGroupExpansionError, diff --git a/crates/physical-asap-optimization/Cargo.toml b/crates/physical-asap-optimization/Cargo.toml index 49acbe7e7..5baeb003a 100644 --- a/crates/physical-asap-optimization/Cargo.toml +++ b/crates/physical-asap-optimization/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" [dependencies] asap-accuracy = { path = "../accuracy" } asap-types = { path = "../types" } -# The pane retention of a realized plan (`AssignmentRealizer::realize`). +# The tumbling-window retention of a realized plan (`AssignmentRealizer::realize`). asap-cost = { path = "../cost" } rmp-serde = "1" serde = { version = "1", features = ["derive"] } @@ -22,7 +22,7 @@ thiserror = "2" [features] # Exposes `test_support` (single-candidate realization, operator -# implementation, pane coverage, and `test_support::catalog`: catalog +# implementation, tumbling window coverage, and `test_support::catalog`: catalog # internals plan selection's unit tests exercise with its solver), for other # crates' tests. Tests also enumerate whole workloads with the reference pricer. test-support = ["reference-pricer"] diff --git a/crates/physical-asap-optimization/src/catalog/configurations.rs b/crates/physical-asap-optimization/src/catalog/configurations.rs index a778334f0..3bc3cca9b 100644 --- a/crates/physical-asap-optimization/src/catalog/configurations.rs +++ b/crates/physical-asap-optimization/src/catalog/configurations.rs @@ -27,7 +27,7 @@ use asap_types::stages::{ /// [`PhysicalCandidate::may_keep_across_evaluations_per_sole_reader_query`] for `node` configured /// with `materialization`: for each query in `entries`, whether /// `summary_materialization_eligibility` with that query as the only reader -/// allows keeping it across evaluations. Empty unless `node` is a pane chain, +/// allows keeping it across evaluations. Empty unless `node` is a tumbling-window chain, /// `materialization` is `MatAtQueryKeptAcrossEvaluations(Memory)` and the /// deployment keeps query-time results. pub fn may_keep_across_evaluations_per_sole_reader_query( diff --git a/crates/physical-asap-optimization/src/catalog/realize.rs b/crates/physical-asap-optimization/src/catalog/realize.rs index dbbc14bb6..81d7c39a0 100644 --- a/crates/physical-asap-optimization/src/catalog/realize.rs +++ b/crates/physical-asap-optimization/src/catalog/realize.rs @@ -243,7 +243,7 @@ mod tests { use asap_logical_asap_optimization::test_support::{ entry, lower_promql, sharing_rules_and_shared_state_groups, }; - use asap_logical_asap_optimization::PaneWidthDomain; + use asap_logical_asap_optimization::TumblingWindowWidthDomain; use asap_types::ir::properties::AccuracyTarget; use asap_types::stages::{ LogicalASAPMemoCandidates, PhysicalCandidateId, SelectedEdge, SelectedNodeId, @@ -299,7 +299,8 @@ mod tests { .unwrap(); let memo = try_populate_pass1(initial, &per_computation).unwrap(); let logical = LogicalASAPMemoCandidates { - memo: sharing_rules_and_shared_state_groups(memo, PaneWidthDomain::Complete).unwrap(), + memo: sharing_rules_and_shared_state_groups(memo, TumblingWindowWidthDomain::Complete) + .unwrap(), }; let entries = vec![entry(QueryRecurrence::Unknown)]; let data = DataWorkload::default(); diff --git a/crates/physical-asap-optimization/src/lib.rs b/crates/physical-asap-optimization/src/lib.rs index 01c25bf83..d80a6654f 100644 --- a/crates/physical-asap-optimization/src/lib.rs +++ b/crates/physical-asap-optimization/src/lib.rs @@ -34,9 +34,9 @@ //! kept across evaluations (#509 Example 4's B3), offered when the //! deployment keeps query-time results (the caller's //! `query_time_results_retained`) for a -//! tumbling-pane chain that builds one new pane per evaluation -//! (`builds_one_pane_per_evaluation`): each evaluation builds the -//! newest pane and reuses the others. Both query-time options are offered +//! tumbling-window chain that builds one new tumbling window per evaluation +//! (`builds_one_tumbling_window_per_evaluation`): each evaluation builds the +//! newest tumbling window and reuses the others. Both query-time options are offered //! when both apply, and Stage 3 prices each. //! //! A build at ingestion time needs every build below it at ingestion time too @@ -61,9 +61,9 @@ //! as Prometheus assumes (it rejects out-of-order samples). Table (SQL) //! sources and mixed data, whose at-rest part may load in any order, are //! not taken to be ordered; -//! * it is a tumbling-pane chain whose readers all evaluate on its pane +//! * it is a tumbling-window chain whose readers all evaluate on its tumbling window //! boundaries, anchored at the epoch (each fixed interval a multiple of the -//! pane width, and each known phase on that grid), or a whole-window build whose readers +//! tumbling-window width, and each known phase on that grid), or a whole-window build whose readers //! all run at one fixed interval with agreeing declared phases, over a //! window that is not shifted and whose evaluations all end on the grid //! of its width anchored at the epoch (the interval and a declared phase @@ -81,11 +81,11 @@ //! [`RangeEvaluation`](asap_types::workload::RangeEvaluation) evaluates at //! every `step_ms` from `end − span_ms` forward to each request's end. //! Every step reads the -//! panes of a tumbling-pane chain, so each pane width must divide the step +//! tumbling windows of a tumbling-window chain, so each tumbling-window width must divide the step //! of every range-evaluated reader. Stage 1 offers only such widths //! (`QueryWorkloadEntry::evaluation_grid_ms`); a candidate with a chain off //! a reader's step grid fails with -//! [`PhysicalASAPOptimizationError::PaneWidthOffRangeStep`] and the caller +//! [`PhysicalASAPOptimizationError::TumblingWindowWidthOffRangeStep`] and the caller //! prunes it with that reason. //! //! ## Physical operator implementation @@ -140,7 +140,10 @@ use asap_types::workload::{ DataArrival, DataWorkload, Predictability, QueryRecurrence, QueryWorkloadEntry, RepeatedDemand, }; use split_by_timing::split_shared_by_timing; -use summary_window::{plan_pane_phase, validate_pane_coverage, PaneLayout, WindowEdgeCoverage}; +use summary_window::{ + plan_tumbling_window_phase, validate_tumbling_window_coverage, TumblingWindowLayout, + WindowEdgeCoverage, +}; use thiserror::Error; #[derive(Debug, Error)] @@ -153,17 +156,17 @@ pub enum PhysicalASAPOptimizationError { Timing(#[from] ExecutionDataStateError), #[error("exact top-k has no Sort + Limit implementation: {0}")] ExactTopK(String), - /// Bad candidate for the workload: a tumbling-pane chain whose pane + /// Bad candidate for the workload: a tumbling-window chain whose tumbling window /// width does not divide the step of a query that reads it as a range - /// request, so some step would end inside a pane and could not read - /// the panes. Stage 1 offers only panes on every reader's step grid; + /// request, so some step would end inside a tumbling window and could not read + /// the tumbling windows. Stage 1 offers only tumbling windows on every reader's step grid; /// the candidate is pruned with this reason. #[error( - "pane width {tumbling_window_width_ms} ms does not divide the {step_ms} ms step of \ + "tumbling-window width {tumbling_window_width_ms} ms does not divide the {step_ms} ms step of \ a range evaluation that reads it" )] - PaneWidthOffRangeStep { - /// The chain's pane width. + TumblingWindowWidthOffRangeStep { + /// The chain's tumbling-window width. tumbling_window_width_ms: u64, /// The reader's range-evaluation step. step_ms: u64, @@ -192,21 +195,21 @@ pub(crate) enum IngestionPruneReason { /// A whole-window build whose evaluations do not all end on the grid of /// its window's width anchored at the epoch, where the backend builds /// it (e.g. `m[20s]` every 20 s at phase 5 s, every 30 s, or `m[15m]` - /// every 5 minutes, which panes serve): the interval or the declared + /// every 5 minutes, which tumbling windows serve): the interval or the declared /// phase is not a multiple of the window. WindowOffEpochGrid, /// A whole-window build read by a range evaluation of more than one /// step per request: it keeps one window per request, not one per step. RangeEvaluatedWholeWindow, - /// A tumbling-pane chain read at a known phase off its pane boundaries, - /// which are anchored at the epoch (e.g. 20 s panes read at phase 5 s): - /// built once each, its panes cannot end at every evaluation. - PanesOffPhase, + /// A tumbling-window chain read at a known phase off its tumbling-window boundaries, + /// which are anchored at the epoch (e.g. 20 s tumbling windows read at phase 5 s): + /// built once each, its tumbling windows cannot end at every evaluation. + TumblingWindowsOffPhase, /// A tumbling-window chain read at a fixed interval that its width does /// not divide (e.g. 15 s tumbling windows read every 10 s): built once /// each on the grid anchored at the epoch, its tumbling windows cannot /// end at every evaluation. - PanesOffInterval, + TumblingWindowsOffInterval, /// Something below it can only run at query time. UpstreamAtQueryTime(String), /// It is not a summary build (e.g. a shared `Scan`): only a summary's @@ -221,7 +224,7 @@ pub(crate) enum IngestionPruneReason { pub(crate) struct SummaryMaterializationEligibility { pub(crate) at_ingestion: Result<(), IngestionPruneReason>, pub(crate) may_keep_across_evaluations: bool, - /// A pane's selected readers must also meet their range-step grids. + /// A tumbling window's selected readers must also meet their range-step grids. pub(crate) range_compatibility: Result<(), PhysicalASAPOptimizationError>, } @@ -229,11 +232,11 @@ pub(crate) struct SummaryMaterializationEligibility { pub(crate) enum SummaryEligibilityError { #[error("selected reader {query} has no workload entry")] UnknownReader { query: usize }, - #[error("materialization eligibility requires a summary build or pane chain")] + #[error("materialization eligibility requires a summary build or tumbling-window chain")] NotSummary, #[error("materialization eligibility requires at least one selected reader")] NoSelectedReaders, - #[error("pane width must be positive")] + #[error("tumbling-window width must be positive")] ZeroPaneWidth, } @@ -301,7 +304,7 @@ fn summary_reader_eligibility( .. }), Some(recurrences), - ) => builds_one_pane_per_evaluation(*tumbling_window_width_ms, recurrences), + ) => builds_one_tumbling_window_per_evaluation(*tumbling_window_width_ms, recurrences), _ => false, }; SummaryMaterializationEligibility { @@ -323,13 +326,13 @@ fn summary_reader_eligibility( /// q2: max_over_time(latency[5m]) every 1m /// ``` /// -/// If Stage 1 chose KLL panes for q1 and exact max panes for q2, Stage 2 +/// If Stage 1 chose KLL tumbling windows for q1 and exact max tumbling windows for q2, Stage 2 /// finds two materialization candidates: /// -/// | materialization candidate | readers | options | -/// |---------------------------|---------|---------------------------------| -/// | Kll ×5 panes (q1) | q1 | NotMaterialized, MatAtIngestion | -/// | Max acc ×5 panes (q2) | q2 | NotMaterialized, MatAtIngestion | +/// | materialization candidate | readers | options | +/// |----------------------------------|---------|---------------------------------| +/// | Kll ×5 tumbling windows (q1) | q1 | NotMaterialized, MatAtIngestion | +/// | Max acc ×5 tumbling windows (q2) | q2 | NotMaterialized, MatAtIngestion | /// /// One read by two or more queries also gets `MatAtQuery` (built once per /// evaluation and reused), and so does the highest node of a shared @@ -349,8 +352,8 @@ pub(crate) struct MaterializationCandidate { /// `Ok` when `MatAtIngestion` is an option. pub(crate) at_ingestion: Result<(), IngestionPruneReason>, /// Whether `MatAtQueryKeptAcrossEvaluations` is an option: the - /// deployment keeps query-time results and it is a tumbling-pane chain - /// that builds one new pane per evaluation. + /// deployment keeps query-time results and it is a tumbling-window chain + /// that builds one new tumbling window per evaluation. pub(crate) may_keep_across_evaluations: bool, } @@ -376,11 +379,11 @@ impl MaterializationCandidate { } } -/// Whether a tumbling-pane chain of `tumbling_window_width_ms`-wide panes read by -/// queries repeating as `readers` builds exactly one new pane per +/// Whether a tumbling-window chain of `tumbling_window_width_ms`-wide tumbling windows read by +/// queries repeating as `readers` builds exactly one new tumbling window per /// evaluation: there is a reader, every reader repeats at a fixed interval -/// equal to the pane width, and the readers whose evaluation phase is known -/// share one phase (modulo the interval). Its other panes are the previous +/// equal to the tumbling-window width, and the readers whose evaluation phase is known +/// share one phase (modulo the interval). Its other tumbling windows are the previous /// evaluations' newest, so a deployment that keeps query-time results /// reuses them (#509 Example 4's B3). /// @@ -388,8 +391,8 @@ impl MaterializationCandidate { /// phase run at the same instants as each other and as the readers with a /// known phase, as plan selection (`asap-plan-selection`) assumes when it counts /// one evaluation per interval. Readers at different known phases would -/// each need their own newest pane, so they do not share one kept chain. -pub(crate) fn builds_one_pane_per_evaluation<'a>( +/// each need their own newest tumbling window, so they do not share one kept chain. +pub(crate) fn builds_one_tumbling_window_per_evaluation<'a>( tumbling_window_width_ms: u64, readers: impl IntoIterator, ) -> bool { @@ -441,7 +444,7 @@ pub fn optimize_physical_asap( let implemented = map_roots(roots, &mut |node| implement(node, &mut cache))?; let mat_candidates = materialization_candidates(&implemented, entries, data, query_time_results_retained); - check_panes_on_range_steps(&mat_candidates, entries)?; + check_tumbling_windows_on_range_steps(&mat_candidates, entries)?; materialization_assignments(&mat_candidates) .map(|assignment| { assigned_candidate(from_logical, &implemented, &mat_candidates, assignment) @@ -471,7 +474,7 @@ pub fn optimize_physical_asap_assigned( let implemented = map_roots(roots, &mut |node| implement(node, &mut cache))?; let mat_candidates = materialization_candidates(&implemented, entries, data, query_time_results_retained); - check_panes_on_range_steps(&mat_candidates, entries)?; + check_tumbling_windows_on_range_steps(&mat_candidates, entries)?; let mut assignment = Vec::with_capacity(mat_candidates.len()); for mat_candidate in &mat_candidates { match choose(&mat_candidate.readers) { @@ -517,7 +520,7 @@ pub(crate) fn optimize_physical_asap_chosen( .collect(); let mat_candidates = materialization_candidates(&implemented, entries, data, query_time_results_retained); - check_panes_on_range_steps(&mat_candidates, entries)?; + check_tumbling_windows_on_range_steps(&mat_candidates, entries)?; let mut assignment = Vec::with_capacity(mat_candidates.len()); for mat_candidate in &mat_candidates { let logical = logical_of @@ -610,15 +613,15 @@ fn physical_candidate_count( let implemented = map_roots(roots, &mut |node| implement(node, &mut cache))?; let mat_candidates = materialization_candidates(&implemented, entries, data, query_time_results_retained); - check_panes_on_range_steps(&mat_candidates, entries)?; + check_tumbling_windows_on_range_steps(&mat_candidates, entries)?; Ok(materialization_assignments(&mat_candidates).count()) } -/// `Err` for the first tumbling-pane chain whose pane width does not +/// `Err` for the first tumbling-window chain whose tumbling-window width does not /// divide the step of a reader's range evaluation -/// ([`PhysicalASAPOptimizationError::PaneWidthOffRangeStep`]): that step -/// would end inside a pane. -fn check_panes_on_range_steps( +/// ([`PhysicalASAPOptimizationError::TumblingWindowWidthOffRangeStep`]): that step +/// would end inside a tumbling window. +fn check_tumbling_windows_on_range_steps( mat_candidates: &[MaterializationCandidate], entries: &[QueryWorkloadEntry], ) -> Result<(), PhysicalASAPOptimizationError> { @@ -649,10 +652,12 @@ fn check_summary_range_steps( continue; }; if range.step_ms % tumbling_window_width_ms.max(&1) != 0 { - return Err(PhysicalASAPOptimizationError::PaneWidthOffRangeStep { - tumbling_window_width_ms: *tumbling_window_width_ms, - step_ms: range.step_ms, - }); + return Err( + PhysicalASAPOptimizationError::TumblingWindowWidthOffRangeStep { + tumbling_window_width_ms: *tumbling_window_width_ms, + step_ms: range.step_ms, + }, + ); } } Ok(()) @@ -798,7 +803,7 @@ fn ingestion_eligibility( if is_filtered_build(node) { return Err(Why::Filtered); } - let (family, chain_pane_width_ms) = match node.asap() { + let (family, chain_tumbling_window_width_ms) = match node.asap() { Some(ASAPOp::SummaryAgg { family, .. }) => (family, None), Some(ASAPOp::SummaryTumblingWindowChain { family, @@ -819,17 +824,17 @@ fn ingestion_eligibility( return Err(Why::CounterNeedsTimeOrder); } } - if let Some(tumbling_window_width_ms) = chain_pane_width_ms { - // A chain's panes are built once each, on the grid anchored at the - // epoch (the plan carries no pane origin): every reader must + if let Some(tumbling_window_width_ms) = chain_tumbling_window_width_ms { + // A chain's tumbling windows are built once each, on the grid anchored at the + // epoch (the plan carries no tumbling-window origin): every reader must // evaluate on its boundaries. A reader at a fixed interval needs an // interval the width divides, whatever its phase; a reader of known // phase also needs that phase on the grid. A reader of unknown phase // is taken to run at multiples of its interval (phase 0), as for a // whole-window build below. - let epoch_panes = PaneLayout { + let epoch_tumbling_windows = TumblingWindowLayout { tumbling_window_width_ms, - pane_origin_ms: Some(0), + tumbling_window_origin_ms: Some(0), }; for d in demands.iter().flatten() { let QueryRecurrence::Repeated(demand) = &d.recurrence else { @@ -839,23 +844,23 @@ fn ingestion_eligibility( .fixed_interval_ms() .is_some_and(|interval_ms| !interval_ms.is_multiple_of(tumbling_window_width_ms)) { - return Err(Why::PanesOffInterval); + return Err(Why::TumblingWindowsOffInterval); } - let reader = plan_pane_phase(demand, tumbling_window_width_ms) - .map_err(|_| Why::PanesOffPhase)?; - let Some(phase_ms) = reader.pane_origin_ms else { + let reader = plan_tumbling_window_phase(demand, tumbling_window_width_ms) + .map_err(|_| Why::TumblingWindowsOffPhase)?; + let Some(phase_ms) = reader.tumbling_window_origin_ms else { if matches!(demand, RepeatedDemand::Scheduled(_)) { - // A schedule whose times are not all on one pane phase. - return Err(Why::PanesOffPhase); + // A schedule whose times are not all on one tumbling-window phase. + return Err(Why::TumblingWindowsOffPhase); } continue; }; - validate_pane_coverage( - &epoch_panes, + validate_tumbling_window_coverage( + &epoch_tumbling_windows, Some(phase_ms), - &WindowEdgeCoverage::PaneAligned, + &WindowEdgeCoverage::TumblingWindowAligned, ) - .map_err(|_| Why::PanesOffPhase)?; + .map_err(|_| Why::TumblingWindowsOffPhase)?; } return Ok(()); } @@ -866,7 +871,7 @@ fn ingestion_eligibility( return Err(Why::RangeEvaluatedWholeWindow); } // A whole-window build: one tumbling window per evaluation, built (like - // a chain's panes) on the grid of its width anchored at the epoch, so + // a chain's tumbling windows) on the grid of its width anchored at the epoch, so // every evaluation must end on that grid. The readers share one fixed // interval and their declared phases agree; a reader with no declared // phase is taken to evaluate at multiples of its interval (phase 0), as @@ -1179,7 +1184,7 @@ fn rebuilt( /// Stage 2 internals that other crates' tests call: building one physical /// candidate for a chosen assignment, implementing one candidate's -/// operators, and the pane-coverage check. +/// operators, and the tumbling-window-coverage check. #[cfg(feature = "test-support")] pub mod test_support { use std::rc::Rc; @@ -1192,7 +1197,8 @@ pub mod test_support { pub use crate::catalog::test_support as catalog; pub use crate::split_by_timing::split_shared_by_timing; pub use crate::summary_window::{ - validate_pane_coverage, PaneCoverageError, PaneLayout, WindowEdgeCoverage, + validate_tumbling_window_coverage, TumblingWindowCoverageError, TumblingWindowLayout, + WindowEdgeCoverage, }; /// See `optimize_physical_asap_chosen` in this crate. @@ -1587,11 +1593,14 @@ mod tests { .remove(0) } - /// `root` with its summary build in 1-min tumbling panes. - fn in_panes(root: QueryRoot) -> QueryRoot { + /// `root` with its summary build in 1-min tumbling windows. + fn in_tumbling_windows(root: QueryRoot) -> QueryRoot { let roots = [root]; let rule_candidates = - asap_logical_asap_optimization::test_support::pane_candidates(&roots, &[Some(MINUTE)]); + asap_logical_asap_optimization::test_support::tumbling_window_candidates( + &roots, + &[Some(MINUTE)], + ); assert_eq!(rule_candidates.len(), 1, "{rule_candidates:?}"); let mut pass2_candidates = asap_logical_asap_optimization::test_support::window_composition_pass2_candidates( @@ -1617,7 +1626,7 @@ mod tests { // An unselected unpredictable query must not restrict an ingestion candidate. #[test] fn selected_reader_eligibility_ignores_optional_unpredictable_consumers() { - let root = in_panes(summary_root("quantile_over_time(0.99, m[5m])")); + let root = in_tumbling_windows(summary_root("quantile_over_time(0.99, m[5m])")); let node = root_operators(&root) .into_iter() .flat_map(OperatorNode::reachable) @@ -1680,7 +1689,7 @@ mod tests { #[test] fn selected_reader_eligibility_checks_phase_and_range_compatibility() { use asap_types::workload::RangeEvaluation; - let root = in_panes(summary_root("quantile_over_time(0.99, m[5m])")); + let root = in_tumbling_windows(summary_root("quantile_over_time(0.99, m[5m])")); let node = root_operators(&root) .into_iter() .flat_map(OperatorNode::reachable) @@ -1727,7 +1736,7 @@ mod tests { assert!(!different_phase.may_keep_across_evaluations); assert_eq!( different_phase.at_ingestion, - Err(IngestionPruneReason::PanesOffPhase) + Err(IngestionPruneReason::TumblingWindowsOffPhase) ); let off_grid = summary_materialization_eligibility( &node, @@ -1739,7 +1748,7 @@ mod tests { .unwrap(); assert!(matches!( off_grid.range_compatibility, - Err(PhysicalASAPOptimizationError::PaneWidthOffRangeStep { .. }) + Err(PhysicalASAPOptimizationError::TumblingWindowWidthOffRangeStep { .. }) )); assert!(matches!( summary_materialization_eligibility( @@ -1786,14 +1795,14 @@ mod tests { nodes } - /// #509 Example 4, B1 and B2: a tumbling-pane chain read by a repeating, + /// #509 Example 4, B1 and B2: a tumbling-window chain read by a repeating, /// predictable query over arriving data is either rebuilt at query time /// or maintained at ingestion time. Maintained, the chain and the scan /// below it run at ingestion time, and the merge and estimate at query /// time. #[test] - fn a_pane_chain_over_arriving_data_may_be_maintained_at_ingestion_time() { - let root = in_panes(summary_root("quantile_over_time(0.99, m[5m])")); + fn a_tumbling_window_chain_over_arriving_data_may_be_maintained_at_ingestion_time() { + let root = in_tumbling_windows(summary_root("quantile_over_time(0.99, m[5m])")); let candidates = optimize_physical_asap("L1", &[root], &[every_minute()], &arriving(), false).unwrap(); assert_eq!(candidates.len(), 2); @@ -1824,15 +1833,15 @@ mod tests { } } - /// A chain of 1-min panes, anchored at the epoch, read by two queries + /// A chain of 1-min tumbling windows, anchored at the epoch, read by two queries /// every minute is maintained at ingestion time when they evaluate on - /// its pane boundaries (phases 0 and 2 min, or one phase unknown), not - /// when one is at phase 30 s: each pane is built once, so it would read - /// panes 30 s off. + /// its tumbling-window boundaries (phases 0 and 2 min, or one phase unknown), not + /// when one is at phase 30 s: each tumbling window is built once, so it would read + /// tumbling windows 30 s off. #[test] - fn a_pane_chain_is_maintained_only_on_its_readers_phases() { + fn a_tumbling_window_chain_is_maintained_only_on_its_readers_phases() { use IngestionPruneReason as Why; - let root = in_panes(summary_root("quantile_over_time(0.99, m[5m])")); + let root = in_tumbling_windows(summary_root("quantile_over_time(0.99, m[5m])")); let at = |phase_ms: u64| QueryWorkloadEntry { recurrence: QueryRecurrence::Repeated(RepeatedDemand::FixedIntervalAt { interval: RepetitionInterval(MINUTE as u32), @@ -1860,10 +1869,13 @@ mod tests { }; assert_eq!(at_ingestion([at(0), at(2 * MINUTE)]), Ok(())); assert_eq!(at_ingestion([at(0), every_minute()]), Ok(())); - assert_eq!(at_ingestion([at(0), at(30_000)]), Err(Why::PanesOffPhase)); + assert_eq!( + at_ingestion([at(0), at(30_000)]), + Err(Why::TumblingWindowsOffPhase) + ); assert_eq!( at_ingestion([at(30_000), every_minute()]), - Err(Why::PanesOffPhase) + Err(Why::TumblingWindowsOffPhase) ); } @@ -1874,7 +1886,7 @@ mod tests { #[test] fn a_tumbling_window_chain_is_maintained_only_on_its_readers_intervals() { use IngestionPruneReason as Why; - let root = in_panes(summary_root("quantile_over_time(0.99, m[5m])")); + let root = in_tumbling_windows(summary_root("quantile_over_time(0.99, m[5m])")); let every = |interval_ms: u64| QueryWorkloadEntry { recurrence: QueryRecurrence::Repeated(RepeatedDemand::FixedInterval( RepetitionInterval(interval_ms as u32), @@ -1909,11 +1921,11 @@ mod tests { assert_eq!(at_ingestion([every_minute(), every(2 * MINUTE)]), Ok(())); assert_eq!( at_ingestion([every_minute(), every(90_000)]), - Err(Why::PanesOffInterval) + Err(Why::TumblingWindowsOffInterval) ); assert_eq!( at_ingestion([every_minute(), every_at_phase_0(90_000)]), - Err(Why::PanesOffInterval) + Err(Why::TumblingWindowsOffInterval) ); } @@ -1923,7 +1935,7 @@ mod tests { #[test] fn ingestion_time_needs_arriving_data_and_repeated_predictable_queries() { use IngestionPruneReason as Why; - let root = in_panes(summary_root("quantile_over_time(0.99, m[5m])")); + let root = in_tumbling_windows(summary_root("quantile_over_time(0.99, m[5m])")); let at_rest = DataWorkload { arrival: DataArrival::AtRest, ..Default::default() @@ -1960,12 +1972,12 @@ mod tests { /// A counter state (`rate`, `increase`) needs each series' samples in /// time order. Continuously ingesting time-series (PromQL) data is taken - /// to arrive in that order, as Prometheus assumes, so its counter panes + /// to arrive in that order, as Prometheus assumes, so its counter tumbling windows /// may be maintained at ingestion time. #[test] - fn counter_panes_over_ingesting_promql_data_run_at_ingestion_time() { + fn counter_tumbling_windows_over_ingesting_promql_data_run_at_ingestion_time() { for query in ["rate(m[5m])", "increase(m[5m])"] { - let roots = [in_panes(summary_root(query))]; + let roots = [in_tumbling_windows(summary_root(query))]; let mut cache = HashMap::new(); let implemented = map_roots(&roots, &mut |n| implement(n, &mut cache)).unwrap(); let mat_candidates = @@ -1982,9 +1994,9 @@ mod tests { /// time order: a counter over a table source, or over mixed data (whose /// at-rest part may be loaded in any order), stays at query time. #[test] - fn counter_panes_over_other_sources_stay_at_query_time() { + fn counter_tumbling_windows_over_other_sources_stay_at_query_time() { use IngestionPruneReason as Why; - let root = in_panes(summary_root("rate(m[5m])")); + let root = in_tumbling_windows(summary_root("rate(m[5m])")); let QueryRoot::Operator(node) = &root else { panic!("{root:?}") }; @@ -2186,13 +2198,13 @@ mod tests { assert_eq!(at_ingestion(at_minute(0)), Ok(())); } - /// A pane chain whose width does not divide a range-evaluated reader's + /// A tumbling-window chain whose width does not divide a range-evaluated reader's /// step is not offered: the candidate fails with the widths, and the - /// caller prunes it. 1-min panes serve 2-min steps. + /// caller prunes it. 1-min tumbling windows serve 2-min steps. #[test] - fn a_pane_chain_off_the_range_step_grid_is_rejected() { + fn a_tumbling_window_chain_off_the_range_step_grid_is_rejected() { use asap_types::workload::RangeEvaluation; - let root = in_panes(summary_root("quantile_over_time(0.99, m[5m])")); + let root = in_tumbling_windows(summary_root("quantile_over_time(0.99, m[5m])")); let stepping = |step_ms: u64| QueryWorkloadEntry { range_evaluation: Some(RangeEvaluation { step_ms, @@ -2213,7 +2225,7 @@ mod tests { assert!( matches!( error, - PhysicalASAPOptimizationError::PaneWidthOffRangeStep { + PhysicalASAPOptimizationError::TumblingWindowWidthOffRangeStep { tumbling_window_width_ms: MINUTE, step_ms: 30_000, } @@ -2222,7 +2234,7 @@ mod tests { ); assert_eq!( error.to_string(), - "pane width 60000 ms does not divide the 30000 ms step of a range \ + "tumbling-window width 60000 ms does not divide the 30000 ms step of a range \ evaluation that reads it" ); assert!(physical_candidate_count( @@ -2280,19 +2292,20 @@ mod tests { ); } - /// Two queries share one scan: one reads it through panes maintained at + /// Two queries share one scan: one reads it through tumbling windows maintained at /// ingestion time, the other at query time. The scan is split, one copy /// per timing; with nothing materialized it stays one node. The shared /// scan may also be built once per evaluation (`MatAtQuery`), but not - /// under ingestion-time panes, where only one query reads it at query + /// under ingestion-time tumbling windows, where only one query reads it at query /// time. #[test] fn a_scan_two_queries_read_at_two_timings_is_split() { - let QueryRoot::Operator(panes) = in_panes(summary_root("quantile_over_time(0.99, m[5m])")) + let QueryRoot::Operator(tumbling_windows) = + in_tumbling_windows(summary_root("quantile_over_time(0.99, m[5m])")) else { unreachable!() }; - let scan = OperatorNode::reachable(&panes) + let scan = OperatorNode::reachable(&tumbling_windows) .into_iter() .find(|n| matches!(n.non_asap(), Some(NonASAPOp::Scan { .. }))) .unwrap(); @@ -2309,7 +2322,10 @@ mod tests { None, ranged, ); - let roots = [QueryRoot::Operator(panes), QueryRoot::Operator(exact)]; + let roots = [ + QueryRoot::Operator(tumbling_windows), + QueryRoot::Operator(exact), + ]; let candidates = optimize_physical_asap( "L1", &roots, @@ -2407,13 +2423,13 @@ mod tests { ); } - /// Readers share one kept pane chain only when they evaluate at the + /// Readers share one kept tumbling-window chain only when they evaluate at the /// same instants: two readers every minute at phases 0 and 2 min do, /// at phases 0 and 30 s they do not (each would need its own newest - /// pane). A reader with no known phase is assumed to run with the + /// tumbling window). A reader with no known phase is assumed to run with the /// others. #[test] - fn readers_at_different_phases_do_not_build_one_pane_per_evaluation() { + fn readers_at_different_phases_do_not_build_one_tumbling_window_per_evaluation() { let at = |phase_ms: u64| { QueryRecurrence::Repeated(RepeatedDemand::FixedIntervalAt { interval: RepetitionInterval(MINUTE as u32), @@ -2421,19 +2437,19 @@ mod tests { }) }; let unknown_phase = every_minute().recurrence; - assert!(builds_one_pane_per_evaluation( + assert!(builds_one_tumbling_window_per_evaluation( MINUTE, [&at(0), &at(2 * MINUTE)] )); - assert!(!builds_one_pane_per_evaluation( + assert!(!builds_one_tumbling_window_per_evaluation( MINUTE, [&at(0), &at(30_000)] )); - assert!(builds_one_pane_per_evaluation( + assert!(builds_one_tumbling_window_per_evaluation( MINUTE, [&at(30_000), &unknown_phase] )); - assert!(!builds_one_pane_per_evaluation( + assert!(!builds_one_tumbling_window_per_evaluation( MINUTE, [&at(0), &unknown_phase, &at(30_000)] )); diff --git a/crates/physical-asap-optimization/src/summary_window.rs b/crates/physical-asap-optimization/src/summary_window.rs index 70c941bd8..64a5aa468 100644 --- a/crates/physical-asap-optimization/src/summary_window.rs +++ b/crates/physical-asap-optimization/src/summary_window.rs @@ -1,6 +1,6 @@ -//! Planner-level summary-window pane primitives. +//! Planner-level tumbling-window layout and window-edge coverage. //! -//! These values describe pane layout and window-edge coverage. They do not +//! These values describe tumbling-window layout and window-edge coverage. They do not //! identify a runtime library, process, placement, shard layout, storage //! backend, or deployment instance; those choices belong to downstream //! physical compilation. @@ -8,51 +8,55 @@ use asap_types::workload::RepeatedDemand; use serde::{Deserialize, Serialize}; -/// Concrete pane phase recorded in a catalog layout or inventory snapshot. +/// Concrete tumbling-window phase recorded in a catalog layout or inventory snapshot. /// Milliseconds are canonical throughout the shared contract. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct PaneLayout { - // Named `pane_width_ms` before #1163. - #[serde(alias = "pane_width_ms")] +pub struct TumblingWindowLayout { + // The deployed wire contract keeps the names from before #1163. + #[serde(rename = "pane_width_ms", alias = "tumbling_window_width_ms")] pub tumbling_window_width_ms: u64, - /// Unix timestamp of a pane edge. `None` means the runtime has not + /// Unix timestamp of a tumbling-window edge. `None` means the runtime has not /// established an origin and therefore cannot claim full coverage. - pub pane_origin_ms: Option, + #[serde(rename = "pane_origin_ms")] + pub tumbling_window_origin_ms: Option, } -/// How a query obtains exact values for partial panes at its two window edges. +/// How a query obtains exact values for partial tumbling windows at its two window edges. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum WindowEdgeCoverage { - PaneAligned, + // Wire name kept from before #1163. + #[serde(rename = "pane_aligned")] + TumblingWindowAligned, #[serde(rename = "exact_boundary_residual")] - ExactWindowEdgeResidual { - executor: String, - source: String, - }, + ExactWindowEdgeResidual { executor: String, source: String }, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum PaneCoverageError { - ZeroPaneWidth, - UnknownPaneOrigin, +// Wire names kept from before #1163. +pub enum TumblingWindowCoverageError { + #[serde(rename = "ZeroPaneWidth")] + ZeroTumblingWindowWidth, + #[serde(rename = "UnknownPaneOrigin")] + UnknownTumblingWindowOrigin, UnknownEvaluationPhase, PhaseMismatch { - pane_phase_ms: u64, + #[serde(rename = "pane_phase_ms")] + tumbling_window_phase_ms: u64, query_phase_ms: u64, }, } -/// Validate that a pane-only evaluation covers a query exactly. A mismatched +/// Validate that a tumbling-window-only evaluation covers a query exactly. A mismatched /// phase is sound only when the physical plan explicitly supplies an exact -/// residual for the partial edge panes. -pub fn validate_pane_coverage( - layout: &PaneLayout, +/// residual for the partial edge tumbling windows. +pub fn validate_tumbling_window_coverage( + layout: &TumblingWindowLayout, evaluation_time_ms: Option, edge_coverage: &WindowEdgeCoverage, -) -> Result<(), PaneCoverageError> { +) -> Result<(), TumblingWindowCoverageError> { if layout.tumbling_window_width_ms == 0 { - return Err(PaneCoverageError::ZeroPaneWidth); + return Err(TumblingWindowCoverageError::ZeroTumblingWindowWidth); } if matches!( edge_coverage, @@ -61,32 +65,33 @@ pub fn validate_pane_coverage( return Ok(()); } let origin = layout - .pane_origin_ms - .ok_or(PaneCoverageError::UnknownPaneOrigin)?; - let evaluation = evaluation_time_ms.ok_or(PaneCoverageError::UnknownEvaluationPhase)?; + .tumbling_window_origin_ms + .ok_or(TumblingWindowCoverageError::UnknownTumblingWindowOrigin)?; + let evaluation = + evaluation_time_ms.ok_or(TumblingWindowCoverageError::UnknownEvaluationPhase)?; let width = layout.tumbling_window_width_ms as i64; - let pane_phase_ms = origin.rem_euclid(width) as u64; + let tumbling_window_phase_ms = origin.rem_euclid(width) as u64; let query_phase_ms = evaluation.rem_euclid(width) as u64; - if pane_phase_ms == query_phase_ms { + if tumbling_window_phase_ms == query_phase_ms { Ok(()) } else { - Err(PaneCoverageError::PhaseMismatch { - pane_phase_ms, + Err(TumblingWindowCoverageError::PhaseMismatch { + tumbling_window_phase_ms, query_phase_ms, }) } } -/// Select a query-aligned pane origin from workload recurrence. A plain fixed +/// Select a query-aligned tumbling-window origin from workload recurrence. A plain fixed /// interval has cadence but no phase, so it remains unknown. Every timestamp -/// in an explicit schedule must have the same phase for one pane layout to +/// in an explicit schedule must have the same phase for one tumbling-window layout to /// serve all occurrences. -pub(crate) fn plan_pane_phase( +pub(crate) fn plan_tumbling_window_phase( demand: &RepeatedDemand, tumbling_window_width_ms: u64, -) -> Result { +) -> Result { if tumbling_window_width_ms == 0 { - return Err(PaneCoverageError::ZeroPaneWidth); + return Err(TumblingWindowCoverageError::ZeroTumblingWindowWidth); } let phase = match demand { RepeatedDemand::FixedIntervalAt { @@ -102,9 +107,9 @@ pub(crate) fn plan_pane_phase( } RepeatedDemand::FixedInterval(_) | RepeatedDemand::EstimatedRate(_) => None, }; - Ok(PaneLayout { + Ok(TumblingWindowLayout { tumbling_window_width_ms, - pane_origin_ms: phase.and_then(|value| i64::try_from(value).ok()), + tumbling_window_origin_ms: phase.and_then(|value| i64::try_from(value).ok()), }) } @@ -113,19 +118,23 @@ mod tests { use super::*; #[test] - fn pane_only_evaluation_rejects_source_and_query_phase_mismatch() { - let layout = PaneLayout { + fn tumbling_window_only_evaluation_rejects_source_and_query_phase_mismatch() { + let layout = TumblingWindowLayout { tumbling_window_width_ms: 60_000, - pane_origin_ms: Some(26_000), + tumbling_window_origin_ms: Some(26_000), }; assert_eq!( - validate_pane_coverage(&layout, Some(56_000), &WindowEdgeCoverage::PaneAligned), - Err(PaneCoverageError::PhaseMismatch { - pane_phase_ms: 26_000, + validate_tumbling_window_coverage( + &layout, + Some(56_000), + &WindowEdgeCoverage::TumblingWindowAligned + ), + Err(TumblingWindowCoverageError::PhaseMismatch { + tumbling_window_phase_ms: 26_000, query_phase_ms: 56_000, }) ); - assert!(validate_pane_coverage( + assert!(validate_tumbling_window_coverage( &layout, Some(56_000), &WindowEdgeCoverage::ExactWindowEdgeResidual { @@ -143,30 +152,61 @@ mod tests { evaluation_phase: asap_types::workload::TimestampMs(56_000), }; assert_eq!( - plan_pane_phase(&demand, 60_000).unwrap(), - PaneLayout { + plan_tumbling_window_phase(&demand, 60_000).unwrap(), + TumblingWindowLayout { tumbling_window_width_ms: 60_000, - pane_origin_ms: Some(56_000), + tumbling_window_origin_ms: Some(56_000), } ); assert_eq!( - plan_pane_phase( + plan_tumbling_window_phase( &RepeatedDemand::FixedInterval(asap_types::workload::RepetitionInterval(60_000)), 60_000, ) .unwrap() - .pane_origin_ms, + .tumbling_window_origin_ms, None ); } - // Renamed pane APIs still read and emit the deployed wire contract. + // Renamed tumbling window APIs still read and emit the deployed wire contract. #[test] fn window_edge_names_preserve_wire_values() { - let layout: PaneLayout = serde_json::from_value(serde_json::json!({ - "tumbling_window_width_ms": 1000, "pane_origin_ms": 0 + let layout: TumblingWindowLayout = serde_json::from_value(serde_json::json!({ + "pane_width_ms": 1000, "pane_origin_ms": 0 })) .unwrap(); + assert_eq!( + serde_json::to_value(&layout).unwrap(), + serde_json::json!({"pane_width_ms": 1000, "pane_origin_ms": 0}) + ); + assert_eq!( + serde_json::to_value(WindowEdgeCoverage::TumblingWindowAligned).unwrap(), + serde_json::json!({"kind": "pane_aligned"}) + ); + for (error, wire) in [ + ( + TumblingWindowCoverageError::ZeroTumblingWindowWidth, + serde_json::json!("ZeroPaneWidth"), + ), + ( + TumblingWindowCoverageError::UnknownTumblingWindowOrigin, + serde_json::json!("UnknownPaneOrigin"), + ), + ( + TumblingWindowCoverageError::PhaseMismatch { + tumbling_window_phase_ms: 1, + query_phase_ms: 2, + }, + serde_json::json!({"PhaseMismatch": {"pane_phase_ms": 1, "query_phase_ms": 2}}), + ), + ] { + assert_eq!(serde_json::to_value(&error).unwrap(), wire); + assert_eq!( + serde_json::from_value::(wire).unwrap(), + error + ); + } let json = serde_json::json!({ "kind": "exact_boundary_residual", "executor": "runtime", "source": "raw" }); @@ -176,6 +216,6 @@ mod tests { WindowEdgeCoverage::ExactWindowEdgeResidual { .. } )); assert_eq!(serde_json::to_value(&coverage).unwrap(), json); - assert!(validate_pane_coverage(&layout, Some(1500), &coverage).is_ok()); + assert!(validate_tumbling_window_coverage(&layout, Some(1500), &coverage).is_ok()); } } diff --git a/crates/plan-selection/src/lib.rs b/crates/plan-selection/src/lib.rs index a4cf7a76f..3a0c858e0 100644 --- a/crates/plan-selection/src/lib.rs +++ b/crates/plan-selection/src/lib.rs @@ -44,11 +44,11 @@ //! the queries whose sub-DAG reaches it and the materialized nodes above //! it, counted without looking through a materialized node: a node below //! one is computed when that node is. The `MatAtIngestion` consumers of -//! one refresh window (`PhysicalASAPDAG::refresh_window`: for a pane -//! chain, its pane width) are refreshed in one ingestion run and count as +//! one refresh window (`PhysicalASAPDAG::refresh_window`: for a tumbling window +//! chain, its tumbling-window width) are refreshed in one ingestion run and count as //! one consumer: a `Scan` under a KLL chain and a DDSketch chain of 10 s -//! panes is computed once per second of arriving data, under chains of -//! 10 s and 60 s panes twice. Stage 2 keeps a shared node one +//! tumbling windows is computed once per second of arriving data, under chains of +//! 10 s and 60 s tumbling windows twice. Stage 2 keeps a shared node one //! node (it copies it only for readers at two timings). A shared //! `NotMaterialized` input costs what reading it separately does (#509 //! Example 4) only when every consumer reads the same window: a query-time `Scan` is priced over the longest window @@ -61,8 +61,8 @@ //! evaluated together, so each distinct interval counts once. //! * A `MatAtIngestion` node runs as data arrives: it is priced over one //! second of arriving rows, once per second, whatever its readers' rates. -//! A tumbling-pane chain maintained this way updates only its newest -//! pane: each arriving row is placed in it and updates it. +//! A tumbling-window chain maintained this way updates only its newest +//! tumbling window: each arriving row is placed in it and updates it. //! * A query repeating every `I` ms runs `1000 / I` times per second, and an //! estimated rate runs at that rate. One-off, scheduled and unknown //! queries are spread over the settings' `non_repeating_query_horizon_seconds` @@ -72,7 +72,7 @@ //! ([`ResourceUsage::average_memory_bytes`](asap_cost::ResourceUsage::average_memory_bytes)), at //! `cost_per_memory_byte_second` per byte per second. A `MatAtIngestion` //! node keeps its state every second: twice for a whole window (the window -//! being built and the completed one queries read) and `N + 1` panes for a +//! being built and the completed one queries read) and `N + 1` tumbling windows for a //! chain of `N` (the `N` the query merges and the one being built). A //! `MatAtQuery` node's state is reused only within one evaluation, so it is //! held only while that evaluation runs: its bytes times the latency of the @@ -83,11 +83,11 @@ //! materialized node reading it (directly or through other //! `NotMaterialized` nodes), its bytes times the latency of the longest //! query reading that node, at the node's rate, as a `MatAtQuery` state is. -//! A `MatAtQueryKeptAcrossEvaluations` tumbling-pane chain (#509 Example +//! A `MatAtQueryKeptAcrossEvaluations` tumbling-window chain (#509 Example //! 4's B3, offered by Stage 2 when the deployment keeps query-time results, //! `DeploymentCapabilities::query_time_results_retained`) is kept across -//! evaluations: each evaluation scans one pane of raw data and builds the -//! newest pane, and the `N − 1` older panes, which complete the readers' +//! evaluations: each evaluation scans one tumbling window of raw data and builds the +//! newest tumbling window, and the `N − 1` older tumbling windows, which complete the readers' //! look-back, are kept every second (in memory, all `N` at the peak). When //! Stage 2 offers both query-time options for a chain, plan selection //! prices each and the cheaper wins. @@ -103,14 +103,14 @@ //! request; a `MatAtIngestion` state is maintained as before, and the //! merge and estimate over it run once per step; //! * a chain kept across evaluations is built once per request: Stage 2 -//! offers it only when the requests repeat at its pane width, so in the -//! steady state each request builds its newest pane and every other pane +//! offers it only when the requests repeat at its tumbling-window width, so in the +//! steady state each request builds its newest tumbling window and every other tumbling window //! any of its steps reads was built by an earlier request or step; -//! * a pane chain kept beyond one evaluation (ingestion time or kept +//! * a tumbling-window chain kept beyond one evaluation (ingestion time or kept //! across evaluations) retains its input's offset plus its window plus //! the longest `span_ms` among its readers (`RealizedPlan::tumbling_window_retention_ms`), -//! so the earliest step of the next request still finds its panes: that -//! many more panes are kept every second. +//! so the earliest step of the next request still finds its tumbling windows: that +//! many more tumbling windows are kept every second. //! //! Latency stays per evaluation (one step). Without a range evaluation //! `S = 1` and the retention is the window, so every price is as above. @@ -125,9 +125,9 @@ //! count; summary build and estimation are priced as rows × sketch depth and //! groups × k. A query-time scan reads the longest window above it (a time //! shift moves a window without widening it); a time shift is free, and a -//! time range is priced by the rows it keeps. A tumbling-pane chain is +//! time range is priced by the rows it keeps. A tumbling-window chain is //! priced as one build over its whole window plus one operation per row to -//! place it in its pane, and keeps one state per group and pane; a merge +//! place it in its tumbling window, and keeps one state per group and tumbling window; a merge //! costs one operation per state it reads. Kept state is sized by its bytes: a sketch by its parameters, a //! fixed-size exact accumulator (sum, count, min, max, rate, ...) as one //! value per group, and exact state that grows with the data (quantile, @@ -322,7 +322,7 @@ mod tests { use asap_accuracy::estimators::hll; use asap_accuracy::propagation::root_guarantee; use asap_cost::execution_demand::evaluation_rate; - use asap_cost::{kept_pane_chains, node_map, plan_cost, NodeMap}; + use asap_cost::{kept_tumbling_window_chains, node_map, plan_cost, NodeMap}; use asap_cost::{ CalibrationFit, ClusterLimits, ClusterObjective, CostInputs, CostModel, NodePriceSource, OperatorKind, PlanCost, ResourceUsage, COST_SOURCE, MEASURED_COST_SOURCE, @@ -377,7 +377,7 @@ mod tests { Ok(at(fit.factor())?.0) } - /// [`self_calibrated_cost`] with `dag`'s node map and kept pane chains. + /// [`self_calibrated_cost`] with `dag`'s node map and kept tumbling-window chains. fn price( dag: &PhysicalASAPDAG, recurrence: &[QueryRecurrence], @@ -387,7 +387,7 @@ mod tests { self_calibrated_cost( dag, &node_map(dag), - &kept_pane_chains(dag), + &kept_tumbling_window_chains(dag), recurrence, &[], data, @@ -3871,7 +3871,7 @@ mod tests { /// read is not checked. #[test] fn invalid_settings_are_rejected() { - let candidates = pane_candidates(10_000.0); + let candidates = tumbling_window_candidates(10_000.0); let select = |settings: &CostInputs| { select_among_plans( &candidates, @@ -3914,7 +3914,7 @@ mod tests { const MINUTE: u32 = 60_000; - /// The p99 target [`pane_candidates`] are sized for. + /// The p99 target [`tumbling_window_candidates`] are sized for. fn p99_target() -> AccuracyTarget { AccuracyTarget::max_error(0.01) .failure_probability(0.001) @@ -3941,19 +3941,20 @@ mod tests { } /// The physical candidates of a per-series p99 over 5 min with its KLL - /// in five 1-min tumbling panes, every minute over data arriving at + /// in five 1-min tumbling windows, every minute over data arriving at /// `rows_per_s`: rebuilt at query time (B2), then maintained at /// ingestion time (B1). - fn pane_candidates(rows_per_s: f64) -> Vec { - let candidates = pane_candidates_for(rows_per_s, &DeploymentCapabilities::default()); + fn tumbling_window_candidates(rows_per_s: f64) -> Vec { + let candidates = + tumbling_window_candidates_for(rows_per_s, &DeploymentCapabilities::default()); assert_eq!(candidates.len(), 2); candidates } - /// [`pane_candidates`] for a deployment with `deployment`'s + /// [`tumbling_window_candidates`] for a deployment with `deployment`'s /// capabilities: B2, B1, then, when it keeps query-time results, B3 /// (built at query time and kept across evaluations). - fn pane_candidates_for( + fn tumbling_window_candidates_for( rows_per_s: f64, deployment: &DeploymentCapabilities, ) -> Vec { @@ -3982,10 +3983,11 @@ mod tests { ) .unwrap(); let roots = sized_as_pass1(&roots, &[p99_target()], &arriving(rows_per_s)); - let rule_candidates = asap_logical_asap_optimization::test_support::pane_candidates( - &roots, - &[Some(u64::from(MINUTE))], - ); + let rule_candidates = + asap_logical_asap_optimization::test_support::tumbling_window_candidates( + &roots, + &[Some(u64::from(MINUTE))], + ); let roots = asap_logical_asap_optimization::test_support::window_composition_pass2_candidates( &roots, @@ -4031,18 +4033,18 @@ mod tests { /// `query_time_results_retained` gates keeping a query-time result across /// evaluations, and only that. Without it, Stage 2 offers the 1-min - /// panes of a query repeating every minute rebuilt (B2) or maintained + /// tumbling windows of a query repeating every minute rebuilt (B2) or maintained /// (B1); with it, also built at query time and kept (B3, /// `MatAtQueryKeptAcrossEvaluations`). B3 is computed once per - /// evaluation, reads one pane (1 min) of raw data, keeps that much raw - /// data on disk, and holds its 4 older panes in memory between + /// evaluation, reads one tumbling window (1 min) of raw data, keeps that much raw + /// data on disk, and holds its 4 older tumbling windows in memory between /// evaluations (on disk, if it were placed there). The same chain /// `MatAtQuery` is reused only within one evaluation: the scan reads the /// 5-min window and nothing is kept between evaluations. #[test] - fn query_time_results_retained_keeps_a_pane_chain_across_evaluations() { - assert_eq!(pane_candidates(10_000.0).len(), 2); - let candidates = pane_candidates_for(10_000.0, &keeping_query_time_results()); + fn query_time_results_retained_keeps_a_tumbling_window_chain_across_evaluations() { + assert_eq!(tumbling_window_candidates(10_000.0).len(), 2); + let candidates = tumbling_window_candidates_for(10_000.0, &keeping_query_time_results()); assert_eq!(candidates.len(), 3); let b2 = &candidates[0].dag; let b3 = &candidates[2].dag; @@ -4076,7 +4078,7 @@ mod tests { else { unreachable!() }; - // Each pane sees its series' 60 samples: the one minute of 10 000 + // Each tumbling window sees its series' 60 samples: the one minute of 10 000 // rows per second over 10 000 series it reads. let samples = StateInputRows { rows_per_state: Some(60), @@ -4090,7 +4092,7 @@ mod tests { assert!(chain_cost .detail .contains(&format!("keeps {between} bytes across evaluations"))); - // On disk, the same N − 1 older panes are kept. + // On disk, the same N − 1 older tumbling windows are kept. let on_disk = with_chain(Materialization::MatAtQueryKeptAcrossEvaluations( Medium::Disk, )); @@ -4117,13 +4119,14 @@ mod tests { ) } - /// #509 Example 4, B1: maintained at ingestion time, a chain of N panes - /// pays the update of the newest pane only, one second of arriving rows - /// once per second, and keeps N + 1 panes (the N the query merges and + /// #509 Example 4, B1: maintained at ingestion time, a chain of N tumbling windows + /// pays the update of the newest tumbling window only, one second of arriving rows + /// once per second, and keeps N + 1 tumbling windows (the N the query merges and /// the one being built) per series. #[test] - fn a_chain_at_ingestion_pays_the_newest_pane_and_keeps_n_plus_one_panes() { - let candidates = pane_candidates(10_000.0); + fn a_chain_at_ingestion_pays_the_newest_tumbling_window_and_keeps_n_plus_one_tumbling_windows() + { + let candidates = tumbling_window_candidates(10_000.0); let dag = &candidates[1].dag; let chain = node_of(dag, is_chain); let Payload::ASAP(ASAPOp::SummaryTumblingWindowChain { @@ -4135,7 +4138,7 @@ mod tests { unreachable!() }; assert_eq!(*tumbling_window_count, 5); - // Each pane sees its series' 60 samples: one minute of 10 000 rows + // Each tumbling window sees its series' 60 samples: one minute of 10 000 rows // per second over 10 000 series. let samples = StateInputRows { rows_per_state: Some(60), @@ -4175,13 +4178,13 @@ mod tests { } /// The latency check (S6, sketch-bench cost model §5): a query's - /// latency is its query-time work on one core. Rebuilding the panes at + /// latency is its query-time work on one core. Rebuilding the tumbling windows at /// query time (B2) takes longer than merging the maintained ones (B1); /// with a bound between the two, B2 is rejected as invalid with its /// latency and B1 is selected. #[test] fn a_candidate_over_a_querys_latency_bound_is_rejected_with_its_latency() { - let candidates = pane_candidates(10_000.0); + let candidates = tumbling_window_candidates(10_000.0); let latency = |c: &PhysicalWorkloadPlan| { let cost = price( &c.dag, @@ -4225,13 +4228,13 @@ mod tests { /// A query evaluated every minute whose evaluation takes longer than a /// minute never keeps up, so even without a latency bound the /// candidate is invalid. Over data fast enough that rebuilding the - /// panes at query time (B2) takes over a minute while merging the + /// tumbling windows at query time (B2) takes over a minute while merging the /// maintained ones (B1) does not, B2 is rejected with its latency and /// the interval, and B1 is selected. #[test] fn a_candidate_slower_than_a_querys_repeat_interval_is_rejected_without_a_bound() { let rows_per_s = 1e9; - let candidates = pane_candidates(rows_per_s); + let candidates = tumbling_window_candidates(rows_per_s); let latency = |c: &PhysicalWorkloadPlan| { price( &c.dag, @@ -4281,7 +4284,7 @@ mod tests { #[test] fn a_one_time_query_has_no_repeat_interval_to_keep_up_with() { let rows_per_s = 1e9; - let candidates = pane_candidates(rows_per_s); + let candidates = tumbling_window_candidates(rows_per_s); let once = QueryWorkloadEntry { recurrence: QueryRecurrence::OneTime { invocations: 1, @@ -4310,7 +4313,7 @@ mod tests { #[test] fn a_broken_latency_bound_is_reported_before_the_repeat_interval() { let rows_per_s = 1e9; - let candidates = pane_candidates(rows_per_s); + let candidates = tumbling_window_candidates(rows_per_s); let mut bounded = QueryWorkloadEntry { recurrence: QueryRecurrence::Repeated(RepeatedDemand::FixedIntervalAt { interval: asap_types::workload::RepetitionInterval(MINUTE), @@ -4349,7 +4352,7 @@ mod tests { #[test] fn ingestion_work_over_one_core_adds_the_newest_windows_compaction() { let latency = |rows_per_s: f64| { - let candidates = pane_candidates(rows_per_s); + let candidates = tumbling_window_candidates(rows_per_s); price( &candidates[1].dag, &[repeating(MINUTE)], @@ -4376,7 +4379,7 @@ mod tests { /// instead of one operation. #[test] fn compaction_prices_merges_from_the_measured_merge() { - let candidates = pane_candidates(1e10); + let candidates = tumbling_window_candidates(1e10); let dag = &candidates[1].dag; let kll = dag .nodes @@ -4460,7 +4463,7 @@ mod tests { |u| u.disk_bytes as f64, ), ]; - for candidate in pane_candidates(10_000.0) { + for candidate in tumbling_window_candidates(10_000.0) { for (set, used) in resources { let cost = price( &candidate.dag, @@ -4487,7 +4490,7 @@ mod tests { /// Cost model 2 prices the candidate's peaks per second: provisioned /// memory, cores and disk; its nodes carry no price. B1 (maintained - /// panes) needs a core for ingestion and one for the query; B2 (rebuilt + /// tumbling windows) needs a core for ingestion and one for the query; B2 (rebuilt /// at query time) one. #[test] fn provisioned_capacity_prices_peak_memory_cores_and_disk() { @@ -4496,7 +4499,7 @@ mod tests { ..ResourceUnitCost::illustrative() }; let settings = under(CostModel::ProvisionedCapacity(prices.clone())); - let candidates = pane_candidates(10_000.0); + let candidates = tumbling_window_candidates(10_000.0); let cores: Vec = candidates .iter() .map(|candidate| { @@ -4530,7 +4533,7 @@ mod tests { cpu_cores, objective: ClusterObjective::LowestLatency, }; - let candidates = pane_candidates(10_000.0); + let candidates = tumbling_window_candidates(10_000.0); let select = |cpu_cores| { let settings = under(CostModel::ClusterCapacity(limits(cpu_cores))); select_among_plans( @@ -4587,7 +4590,7 @@ mod tests { /// less disk than B2's raw data leaves only B1. #[test] fn cluster_capacity_rejects_a_candidate_over_its_disk() { - let candidates = pane_candidates(10_000.0); + let candidates = tumbling_window_candidates(10_000.0); let select = |disk_bytes| { let settings = under(CostModel::ClusterCapacity(ClusterLimits { memory_bytes: u64::MAX, @@ -4679,7 +4682,7 @@ mod tests { /// Under the Pareto front neither dominates the other: both are on it. #[test] fn cluster_objectives_keep_the_earlier_of_tied_candidates() { - let b2 = pane_candidates(10_000.0).remove(0); + let b2 = tumbling_window_candidates(10_000.0).remove(0); let twin = PhysicalWorkloadPlan { id: format!("{}-twin", b2.id), ..b2.clone() @@ -4719,8 +4722,8 @@ mod tests { /// Every Pass 2 window-composition candidate of a per-series p50 over /// 2 min and a p99 over 5 min, both every minute, with both builds in - /// 1-min panes: `shared` picks one chain read by both (the 2-min query - /// merging its newest 2 panes) or a chain each. Its physical candidate + /// 1-min tumbling windows: `shared` picks one chain read by both (the 2-min query + /// merging its newest 2 tumbling windows) or a chain each. Its physical candidate /// with every chain maintained at ingestion time. fn nested_windows_at_ingestion(shared: bool, rows_per_s: f64) -> PhysicalWorkloadPlan { let queries = [ @@ -4765,10 +4768,11 @@ mod tests { }) .collect(); let roots: Vec<_> = pcc::compose_logical_candidate(&inventory, &kll).unwrap(); - let rule_candidates = asap_logical_asap_optimization::test_support::pane_candidates( - &roots, - &queries.map(|(_, interval_ms)| Some(u64::from(interval_ms))), - ); + let rule_candidates = + asap_logical_asap_optimization::test_support::tumbling_window_candidates( + &roots, + &queries.map(|(_, interval_ms)| Some(u64::from(interval_ms))), + ); let roots = asap_logical_asap_optimization::test_support::window_composition_pass2_candidates( &roots, @@ -4776,7 +4780,9 @@ mod tests { ) .unwrap() .into_iter() - .find(|c| c.pane_candidates.len() == 2 && c.shared_chains.is_empty() != shared) + .find(|c| { + c.tumbling_window_candidates.len() == 2 && c.shared_chains.is_empty() != shared + }) .unwrap() .roots .unwrap(); @@ -4805,12 +4811,12 @@ mod tests { .unwrap() } - /// One chain of 5 panes maintained at ingestion time serves a 5-min and - /// a 2-min window: the 2-min query's merge reads its newest 2 panes per + /// One chain of 5 tumbling windows maintained at ingestion time serves a 5-min and + /// a 2-min window: the 2-min query's merge reads its newest 2 tumbling windows per /// series, the 5-min query's all 5, and the plan costs less than a chain - /// per query, since each arriving row updates one pane instead of two. + /// per query, since each arriving row updates one tumbling window instead of two. #[test] - fn a_merge_of_the_newest_panes_is_priced_by_the_panes_it_reads() { + fn a_merge_of_the_newest_tumbling_windows_is_priced_by_the_tumbling_windows_it_reads() { let rows_per_s = 10_000.0; let shared = nested_windows_at_ingestion(true, rows_per_s); let chains: Vec<_> = shared @@ -4854,9 +4860,9 @@ mod tests { ); } - /// A 10-min quantile every minute (10 × 1-min panes) and one every 5 - /// min (2 × 5-min panes), maintained at ingestion time: one chain of - /// 1-min panes serves both, the 5-min query merging all 10 panes once + /// A 10-min quantile every minute (10 × 1-min tumbling windows) and one every 5 + /// min (2 × 5-min tumbling windows), maintained at ingestion time: one chain of + /// 1-min tumbling windows serves both, the 5-min query merging all 10 tumbling windows once /// per 5 min, and costs less than a chain per query. #[test] fn queries_at_different_intervals_share_one_chain_at_ingestion_time() { @@ -4894,7 +4900,7 @@ mod tests { separate_cost.cost_per_second ); assert!(cost.usage.average_memory_bytes < separate_cost.usage.average_memory_bytes); - // Each query merges all 10 panes per series at its own rate: once + // Each query merges all 10 tumbling windows per series at its own rate: once // a minute and once per 5 min. let mut merges: Vec<(u64, String)> = shared .dag @@ -4919,9 +4925,9 @@ mod tests { assert!(merges[1].1.starts_with("0.0167/s"), "{merges:?}"); } - /// A 1-hour quantile every minute (60 × 1-min panes) and every 45 s - /// (80 × 45-s panes) can share only a chain at their gcd, 240 × 15-s - /// panes. Maintained at ingestion time it keeps more states than both + /// A 1-hour quantile every minute (60 × 1-min tumbling windows) and every 45 s + /// (80 × 45-s tumbling windows) can share only a chain at their gcd, 240 × 15-s + /// tumbling windows. Maintained at ingestion time it keeps more states than both /// per-query chains together, so it is priced higher than the /// per-query chains maintained at ingestion time. #[test] @@ -4966,12 +4972,12 @@ mod tests { /// With more than one ingest worker, each worker beyond the first holds /// its own partial copy of a maintained state's newest window: B1's - /// peak memory grows by (k − 1) panes of states, its average memory + /// peak memory grows by (k − 1) tumbling windows of states, its average memory /// (and so cost model 1's price) does not. #[test] fn partial_copies_of_the_newest_window_add_to_peak_memory() { let rows_per_s = 1e9; - let b1 = pane_candidates(rows_per_s).remove(1); + let b1 = tumbling_window_candidates(rows_per_s).remove(1); let chain = node_of(&b1.dag, is_chain); let cost = price( &b1.dag, @@ -4983,7 +4989,7 @@ mod tests { let detail = &cost.per_node[&chain].detail; let workers = cost.usage.peak_cpu_cores - 1; assert!(workers > 1, "{workers}"); - // The chain keeps 6 panes (5 read and the one being built). + // The chain keeps 6 tumbling windows (5 read and the one being built). let kept: u64 = detail .split("; keeps ") .nth(1) @@ -5258,8 +5264,8 @@ mod tests { }) } - /// The exact rebuild of [`quantile_candidates`] and the KLL pane - /// candidates ([`pane_candidates`]: B2 rebuilt at query time, B1 + /// The exact rebuild of [`quantile_candidates`] and the KLL tumbling window + /// candidates ([`tumbling_window_candidates`]: B2 rebuilt at query time, B1 /// maintained at ingestion time), with the KLL's parameters. fn exact_and_kll() -> (Vec, SketchParams) { let mut candidates: Vec<_> = quantile_candidates() @@ -5268,7 +5274,7 @@ mod tests { .collect(); assert_eq!(candidates.len(), 1); candidates[0].id = "EXACT".to_string(); - candidates.extend(pane_candidates(10_000.0)); + candidates.extend(tumbling_window_candidates(10_000.0)); let kll = candidates[2] .dag .nodes @@ -5280,7 +5286,7 @@ mod tests { }) => Some(kind.params().clone()), _ => None, }) - .expect("a KLL pane chain"); + .expect("a KLL tumbling-window chain"); (candidates, kll) } @@ -5319,7 +5325,7 @@ mod tests { plan_cost( &candidate.dag, &node_map(&candidate.dag), - &kept_pane_chains(&candidate.dag), + &kept_tumbling_window_chains(&candidate.dag), &[repeating(MINUTE)], &[], &arriving(10_000.0), @@ -5550,7 +5556,7 @@ mod tests { let cost = self_calibrated_cost( &candidate.dag, &node_map(&candidate.dag), - &kept_pane_chains(&candidate.dag), + &kept_tumbling_window_chains(&candidate.dag), std::slice::from_ref(&recurrence), &[range], &data(), @@ -5605,7 +5611,7 @@ mod tests { /// in place of the bytes the formulas derive from its parameters. #[test] fn measured_state_bytes_size_the_kept_state() { - let candidates = pane_candidates(10_000.0); + let candidates = tumbling_window_candidates(10_000.0); let maintained = &candidates[1]; let kll = maintained .dag @@ -5635,7 +5641,7 @@ mod tests { .average_memory_bytes }; let formula = memory(None); - // The formula's state: each pane sees its series' 60 samples (one + // The formula's state: each tumbling window sees its series' 60 samples (one // minute of 10 000 rows per second over 10 000 series). let samples = StateInputRows { rows_per_state: Some(60), @@ -5646,10 +5652,10 @@ mod tests { } // Oversized state configurations must be rejected as unrepresentable, - // never wrap to a cheap candidate or panic while pricing pane choices. + // never wrap to a cheap candidate or panic while pricing tumbling window choices. #[test] - fn pane_state_byte_overflow_returns_a_cost_error() { - for candidate in pane_candidates(10_000.0) { + fn tumbling_window_state_byte_overflow_returns_a_cost_error() { + for candidate in tumbling_window_candidates(10_000.0) { let kll = candidate .dag .nodes diff --git a/crates/plan-selection/src/report.rs b/crates/plan-selection/src/report.rs index 504cbd443..fbd7668c9 100644 --- a/crates/plan-selection/src/report.rs +++ b/crates/plan-selection/src/report.rs @@ -192,7 +192,7 @@ pub struct WholePlanSelection { /// For each valid candidate, per query, the accuracy Stage 3 accepted. pub accepted: BTreeMap>, pub rejected: Vec, - /// The selected plan's pane retention, by node id in its physical DAG + /// The selected plan's tumbling-window retention, by node id in its physical DAG /// ([`RealizedPlan::tumbling_window_retention_ms`](asap_types::stages::RealizedPlan::tumbling_window_retention_ms)). pub tumbling_window_retention_ms: BTreeMap, /// The physical candidates checked, valid or not. diff --git a/crates/plan-selection/src/search/candidates/configurations.rs b/crates/plan-selection/src/search/candidates/configurations.rs index 0f1fa213c..1525b2bd3 100644 --- a/crates/plan-selection/src/search/candidates/configurations.rs +++ b/crates/plan-selection/src/search/candidates/configurations.rs @@ -174,7 +174,7 @@ mod tests { use asap_logical_asap_optimization::test_support::lower_promql; use asap_logical_asap_optimization::test_support::sharing_rules_and_shared_state_groups; use asap_logical_asap_optimization::test_support::try_populate_pass1; - use asap_logical_asap_optimization::PaneWidthDomain; + use asap_logical_asap_optimization::TumblingWindowWidthDomain; use asap_types::ir::properties::AccuracyTarget; use asap_types::ir::schema::{FieldDataType, SketchAlgorithm}; use asap_types::ir::QueryRoot; @@ -221,7 +221,7 @@ mod tests { .unwrap(); let memo = sharing_rules_and_shared_state_groups( try_populate_pass1(initial, &candidates).unwrap(), - PaneWidthDomain::Complete, + TumblingWindowWidthDomain::Complete, ) .unwrap(); let source = build_candidate_inventory(&memo, &outputs, |_| Ok(7.0)).unwrap(); diff --git a/crates/plan-selection/src/search/candidates/inventory.rs b/crates/plan-selection/src/search/candidates/inventory.rs index b621c2cc0..bb3210929 100644 --- a/crates/plan-selection/src/search/candidates/inventory.rs +++ b/crates/plan-selection/src/search/candidates/inventory.rs @@ -125,7 +125,7 @@ mod tests { use asap_logical_asap_optimization::test_support::lower_promql; use asap_logical_asap_optimization::test_support::sharing_rules_and_shared_state_groups; use asap_logical_asap_optimization::test_support::try_populate_pass1; - use asap_logical_asap_optimization::PaneWidthDomain; + use asap_logical_asap_optimization::TumblingWindowWidthDomain; use asap_types::ir::properties::AccuracyTarget; use asap_types::ir::schema::{FieldDataType, SketchAlgorithm}; use asap_types::ir::{ASAPOp, NonASAPOp, QueryRoot}; @@ -149,7 +149,8 @@ mod tests { .unwrap(); let memo = try_populate_pass1(initial, &candidates).unwrap(); ( - sharing_rules_and_shared_state_groups(memo, PaneWidthDomain::Complete).unwrap(), + sharing_rules_and_shared_state_groups(memo, TumblingWindowWidthDomain::Complete) + .unwrap(), output_groups, ) } @@ -315,11 +316,11 @@ mod tests { assert_eq!(physical.roots.len(), 2); } - // Union-label pane sharing survives typed connection selection and real + // Union-label tumbling window sharing survives typed connection selection and real // Stage 2, while each query receives only its original output columns. #[test] - fn widened_label_readers_materialize_one_shared_pane_chain() { - check_shared_pane_plan( + fn widened_label_readers_materialize_one_shared_tumbling_window_chain() { + check_shared_tumbling_window_plan( [ "avg by (job) (quantile_over_time(0.95, latency[5m]))", "avg by (region) (quantile_over_time(0.99, latency[5m]))", @@ -331,10 +332,10 @@ mod tests { } // Different capacities and windows can share a larger chain while the short - // query reads only its newest panes; Stage 2, accuracy and pricing all validate. + // query reads only its newest tumbling windows; Stage 2, accuracy and pricing all validate. #[test] - fn different_capacity_and_horizon_readers_share_one_validated_pane_chain() { - check_shared_pane_plan( + fn different_capacity_and_horizon_readers_share_one_validated_tumbling_window_chain() { + check_shared_tumbling_window_plan( [ "avg by (job) (quantile_over_time(0.95, latency[5m]))", "avg by (job) (quantile_over_time(0.99, latency[15m]))", @@ -345,7 +346,7 @@ mod tests { ); } - fn check_shared_pane_plan( + fn check_shared_tumbling_window_plan( queries: [&str; 2], errors: [f64; 2], labels: &[&str], @@ -391,7 +392,8 @@ mod tests { asap_logical_asap_optimization::test_support::add_local_window_composition( &mut memo, &entries, ); - let memo = sharing_rules_and_shared_state_groups(memo, PaneWidthDomain::Complete).unwrap(); + let memo = sharing_rules_and_shared_state_groups(memo, TumblingWindowWidthDomain::Complete) + .unwrap(); let source = build_candidate_inventory(&memo, &outputs, |_| Ok(0.0)).unwrap(); let deployment = asap_logical_asap_optimization::DeploymentCapabilities { raw_data_retained: false, @@ -477,7 +479,7 @@ mod tests { else { unreachable!("selected chain checked above") }; - // The complete width domain permits different pane counts. Verify + // The complete width domain permits different tumbling-window counts. Verify // the short reader's coverage, not one traversal-dependent width. assert!(physical.plan.dag.nodes.iter().any(|node| matches!( node.payload, asap_types::ir::Operator::ASAP(ASAPOp::SummaryMerge {newest_tumbling_windows: Some(count), ..}) @@ -488,7 +490,7 @@ mod tests { assert!(selected.node_of(*chains[0]).is_some()); // Validate this exact selected shared graph. Bounded search incumbents // need not choose it, and synthetic activation prices are never bills. - physical.plan.id = "shared-pane-regression".into(); + physical.plan.id = "shared-tumbling-window-regression".into(); let mut evaluator = crate::PlanSelector::new( &entries, &data, @@ -501,7 +503,7 @@ mod tests { evaluator.offer(&physical.plan).unwrap(); let (selection, physical) = evaluator .finish() - .expect("shared pane graph must pass accuracy and cost validation"); + .expect("shared tumbling window graph must pass accuracy and cost validation"); assert!(selection.costs[&selection.selected] .cost_per_second .unwrap() @@ -565,7 +567,7 @@ mod tests { .unwrap(); let memo = sharing_rules_and_shared_state_groups( try_populate_pass1(initial, &generated).unwrap(), - PaneWidthDomain::Complete, + TumblingWindowWidthDomain::Complete, ) .unwrap(); let mut inventory = build_candidate_inventory(&memo, &outputs, |node| { @@ -742,7 +744,8 @@ mod tests { fused.accuracy_budget_parts = 2; memo.candidates[owner].push(fused); let memo = - sharing_rules_and_shared_state_groups(memo, PaneWidthDomain::Complete).unwrap(); + sharing_rules_and_shared_state_groups(memo, TumblingWindowWidthDomain::Complete) + .unwrap(); let mut inventory = build_candidate_inventory(&memo, &outputs, |node| { Ok(if matches!(node.asap(), Some(ASAPOp::SummaryAgg { .. })) { 1000.0 diff --git a/crates/plan-selection/src/search/candidates/physical.rs b/crates/plan-selection/src/search/candidates/physical.rs index 4a5870241..0a4a7c844 100644 --- a/crates/plan-selection/src/search/candidates/physical.rs +++ b/crates/plan-selection/src/search/candidates/physical.rs @@ -23,7 +23,7 @@ mod tests { use asap_logical_asap_optimization::test_support::sharing_rules_and_shared_state_groups; use asap_logical_asap_optimization::test_support::try_populate_pass1; use asap_logical_asap_optimization::DeploymentCapabilities; - use asap_logical_asap_optimization::PaneWidthDomain; + use asap_logical_asap_optimization::TumblingWindowWidthDomain; use asap_types::ir::properties::AccuracyTarget; use asap_types::ir::schema::{FieldDataType, SketchAlgorithm}; use asap_types::ir::{ASAPOp, NonASAPOp}; @@ -64,7 +64,7 @@ mod tests { .unwrap(); let memo = sharing_rules_and_shared_state_groups( try_populate_pass1(initial, &candidates).unwrap(), - PaneWidthDomain::Complete, + TumblingWindowWidthDomain::Complete, ) .unwrap(); let inventory = build_candidate_inventory(&memo, &output_groups, |node| { diff --git a/crates/plan-selection/src/search/candidates/scopes.rs b/crates/plan-selection/src/search/candidates/scopes.rs index ae88d9674..1982758f0 100644 --- a/crates/plan-selection/src/search/candidates/scopes.rs +++ b/crates/plan-selection/src/search/candidates/scopes.rs @@ -366,7 +366,7 @@ mod tests { } // Fix the producer catalog independently of planning budgets and progressive - // pane domains. Root-only readers need one optional binding per output; + // tumbling-window domains. Root-only readers need one optional binding per output; // producers stay constant and producer connections grow exactly linearly. // Output-to-copy assignment choices are intentionally N*N, not claimed linear. #[test] diff --git a/crates/plan-selection/src/search/costing/direct_price_options_tests.rs b/crates/plan-selection/src/search/costing/direct_price_options_tests.rs index 9d88e1957..c8ee50398 100644 --- a/crates/plan-selection/src/search/costing/direct_price_options_tests.rs +++ b/crates/plan-selection/src/search/costing/direct_price_options_tests.rs @@ -378,7 +378,8 @@ fn producer_worker_buckets_remain_joint_choices_and_match_evaluator() { let ingestion = ingestion || (matches!(node.asap(), Some(ASAPOp::SummaryTumblingWindowChain { .. })) && mode == Materialization::MatAtIngestion(Medium::Memory)); - let pane = matches!(node.asap(), Some(ASAPOp::SummaryTumblingWindowChain { .. })); + let tumbling_window = + matches!(node.asap(), Some(ASAPOp::SummaryTumblingWindowChain { .. })); let mut inputs = Vec::new(); node.operator .map_children(|child| inputs.push(append(child.clone(), configured, ingestion, mode))); @@ -390,7 +391,7 @@ fn producer_worker_buckets_remain_joint_choices_and_match_evaluator() { } else { ExecutionTiming::QueryTime }, - if pane { + if tumbling_window { mode } else { Materialization::NotMaterialized @@ -427,11 +428,14 @@ fn producer_worker_buckets_remain_joint_choices_and_match_evaluator() { }) .unwrap(); let roots: Vec<_> = compose_logical_candidate(&generated, &[choice]).unwrap(); - let pane_rules = - asap_logical_asap_optimization::test_support::pane_candidates(&roots, &[Some(60_000)]); + let tumbling_window_rules = + asap_logical_asap_optimization::test_support::tumbling_window_candidates( + &roots, + &[Some(60_000)], + ); let roots = asap_logical_asap_optimization::test_support::window_composition_pass2_candidates( &roots, - &pane_rules, + &tumbling_window_rules, ) .unwrap() .pop() @@ -472,7 +476,7 @@ fn producer_worker_buckets_remain_joint_choices_and_match_evaluator() { Some(NonASAPOp::Scan { .. }) )); // This tests the pricing kernel, not executor installation of a projection - // below a pane. Both producers return exactly the same stream. The identity projection + // below a tumbling window. Both producers return exactly the same stream. The identity projection // adds ingestion CPU without changing rows, values, or source identity. let source = configured.node(raw_producer.0).clone(); let projected = OperatorNode::new_shared(Operator::NonASAP(NonASAPOp::Project { diff --git a/crates/plan-selection/src/search/costing/direct_profile.rs b/crates/plan-selection/src/search/costing/direct_profile.rs index 942ad5324..68c6a72f1 100644 --- a/crates/plan-selection/src/search/costing/direct_profile.rs +++ b/crates/plan-selection/src/search/costing/direct_profile.rs @@ -71,9 +71,9 @@ pub(in crate::search) fn extract( extract_scan_class(configured, context, false) } -/// A second restricted proposal keeps the newest-pane scan class separate from +/// A second restricted proposal keeps the newest-tumbling-window scan class separate from /// ordinary full-window scans. The full candidate graph is never changed. -pub(in crate::search) fn extract_latest_panes( +pub(in crate::search) fn extract_latest_tumbling_windows( configured: &PhysicalCandidatesWithCosts, context: &CandidatePlanningContext<'_>, ) -> Result, DirectProfileError> { @@ -93,7 +93,7 @@ struct PreparedProfile { fn prepare_profile( configured: &PhysicalCandidatesWithCosts, context: &CandidatePlanningContext<'_>, - prefer_latest_pane: bool, + prefer_latest_tumbling_window: bool, ) -> Result, DirectProfileError> { let settings = context.models.cost_inputs; let CostModel::UsageOverTime(_prices) = &settings.cost_model else { @@ -300,7 +300,7 @@ fn prepare_profile( if raw_output { lookbacks[id] = None; } else { - let choices = if prefer_latest_pane && !kept.is_empty() { + let choices = if prefer_latest_tumbling_window && !kept.is_empty() { &kept } else if !ordinary.is_empty() { &ordinary @@ -310,7 +310,7 @@ fn prepare_profile( if choices.is_empty() { nodes[id] = None; } else if choices.contains(&None) - && !(prefer_latest_pane && choices.iter().any(Option::is_some)) + && !(prefer_latest_tumbling_window && choices.iter().any(Option::is_some)) { lookbacks[id] = None; } else { @@ -332,7 +332,7 @@ fn prepare_profile( fn extract_scan_class( configured: &PhysicalCandidatesWithCosts, context: &CandidatePlanningContext<'_>, - prefer_latest_pane: bool, + prefer_latest_tumbling_window: bool, ) -> Result, DirectProfileError> { // The fixed adapter has no selected-reader retention tiers. The joint // option extractor carries those decisions instead. @@ -351,7 +351,8 @@ fn extract_scan_class( { return Ok(None); } - let Some(prepared) = prepare_profile(configured, context, prefer_latest_pane)? else { + let Some(prepared) = prepare_profile(configured, context, prefer_latest_tumbling_window)? + else { return Ok(None); }; let PreparedProfile { @@ -486,8 +487,8 @@ fn profile_for_priced( }), materialized: node.materialization != Materialization::NotMaterialized, raw_retention, - // A chain maintained at ingestion refreshes its newest pane. The - // profile admits no `TimeShift` at ingestion, so that pane ends at + // A chain maintained at ingestion refreshes its newest tumbling window. The + // profile admits no `TimeShift` at ingestion, so that tumbling window ends at // the evaluation time: phase 0. refresh_window: match &node.payload { asap_types::ir::Operator::ASAP(ASAPOp::SummaryTumblingWindowChain { @@ -669,7 +670,7 @@ fn price<'a>( if sketches > 1 && input_override.is_none() { continue; } - let (retained_panes, ingestion_window) = match &node.payload { + let (retained_tumbling_windows, ingestion_window) = match &node.payload { asap_types::ir::Operator::ASAP(ASAPOp::SummaryTumblingWindowChain { tumbling_window_count, tumbling_window_width_ms, @@ -700,7 +701,7 @@ fn price<'a>( &views, (1, 1.0), asap_cost::trailing_window(lookbacks[id]), - retained_panes, + retained_tumbling_windows, ingestion_window, context.data, context.models.cost_inputs, @@ -1016,7 +1017,7 @@ fn state_compaction_seconds( settings.resource_speed.ms_per_cpu_op * context.calibration.factor(), |unit| (unit.ns_per_unit + unit.ns_per_invocation) * 1e-6, ); - let states = priced.output.rows / asap_cost::panes_of(node); + let states = priced.output.rows / asap_cost::tumbling_windows_of(node); (workers - 1) as f64 * states as f64 * merge_ms / 1000.0 } @@ -1415,29 +1416,29 @@ mod tests { } #[test] - fn ingestion_pane_profiles_match_the_real_evaluator() { - check_pane_profile(Materialization::MatAtIngestion(Medium::Memory)); + fn ingestion_tumbling_window_profiles_match_the_real_evaluator() { + check_tumbling_window_profile(Materialization::MatAtIngestion(Medium::Memory)); } #[test] - fn rebuilt_pane_profiles_match_the_real_evaluator() { - check_pane_profile(Materialization::NotMaterialized); - check_pane_profile(Materialization::MatAtQuery(Medium::Memory)); + fn rebuilt_tumbling_window_profiles_match_the_real_evaluator() { + check_tumbling_window_profile(Materialization::NotMaterialized); + check_tumbling_window_profile(Materialization::MatAtQuery(Medium::Memory)); } #[test] - fn kept_pane_profiles_and_scan_classes_match_the_real_evaluator() { - check_pane_profile(Materialization::MatAtQueryKeptAcrossEvaluations( + fn kept_tumbling_window_profiles_and_scan_classes_match_the_real_evaluator() { + check_tumbling_window_profile(Materialization::MatAtQueryKeptAcrossEvaluations( Medium::Memory, )); } - fn check_pane_profile(mode: Materialization) { - check_pane_profile_with_range(mode, None); + fn check_tumbling_window_profile(mode: Materialization) { + check_tumbling_window_profile_with_range(mode, None); } #[test] - fn identical_range_pane_profiles_match_the_real_evaluator() { + fn identical_range_tumbling_window_profiles_match_the_real_evaluator() { let range = Some(asap_types::workload::RangeEvaluation { span_ms: 3_600_000, step_ms: 60_000, @@ -1447,11 +1448,11 @@ mod tests { Materialization::MatAtQuery(Medium::Memory), Materialization::MatAtIngestion(Medium::Memory), ] { - check_pane_profile_with_range(mode, range); + check_tumbling_window_profile_with_range(mode, range); } } - fn check_pane_profile_with_range( + fn check_tumbling_window_profile_with_range( mode: Materialization, range_evaluation: Option, ) { @@ -1470,7 +1471,8 @@ mod tests { let ingestion = ingestion || (matches!(node.asap(), Some(ASAPOp::SummaryTumblingWindowChain { .. })) && mode == Materialization::MatAtIngestion(Medium::Memory)); - let pane = matches!(node.asap(), Some(ASAPOp::SummaryTumblingWindowChain { .. })); + let tumbling_window = + matches!(node.asap(), Some(ASAPOp::SummaryTumblingWindowChain { .. })); let mut inputs = Vec::new(); node.operator.map_children(|child| { inputs.push(append(child.clone(), configured, ingestion, mode)) @@ -1483,7 +1485,7 @@ mod tests { } else { ExecutionTiming::QueryTime }, - if pane { + if tumbling_window { mode } else { Materialization::NotMaterialized @@ -1534,11 +1536,12 @@ mod tests { let choice = generated.targets[0].ways_to_compute.iter().position(|candidate| matches!(candidate, HowToComputeAggregateMeasure::Summary(found) if *found == algorithm)).unwrap(); let roots: Vec<_> = compose_logical_candidate(&generated, &[choice]).unwrap(); - let pane_rules = asap_logical_asap_optimization::test_support::pane_candidates( - &roots, - &[Some(width)], - ); - let roots = asap_logical_asap_optimization::test_support::window_composition_pass2_candidates(&roots, &pane_rules).unwrap().pop().unwrap().roots.unwrap(); + let tumbling_window_rules = + asap_logical_asap_optimization::test_support::tumbling_window_candidates( + &roots, + &[Some(width)], + ); + let roots = asap_logical_asap_optimization::test_support::window_composition_pass2_candidates(&roots, &tumbling_window_rules).unwrap().pop().unwrap().roots.unwrap(); let data: DataWorkload = serde_json::from_value(serde_json::json!({ "arrival":{"ingesting":{"rows_per_second":rows_per_second}}, "sources":{"latency":{"series_count":series_count,"rows_held":rows_per_second * 300,"values_non_negative":true,"max_distinct_per_evaluation":1000}} @@ -1799,7 +1802,7 @@ mod tests { } if mode == Materialization::MatAtQueryKeptAcrossEvaluations(Medium::Memory) { // The same physical scan may feed either a full-window - // rebuild or a newest-pane refresh. Keep these priced + // rebuild or a newest-tumbling-window refresh. Keep these priced // domains distinct, and validate each selected binding. let normal_chain = PhysicalCandidateId(configured.physical.candidates.len()); @@ -1821,7 +1824,7 @@ mod tests { } configured.sync_physical_for_tests(); let normal = extract(&configured, &context).unwrap().unwrap(); - let latest = extract_latest_panes(&configured, &context) + let latest = extract_latest_tumbling_windows(&configured, &context) .unwrap() .unwrap(); assert!(!normal.supported[chain] && normal.supported[normal_chain.0]); @@ -1926,23 +1929,23 @@ mod tests { } /// A KLL chain (p90 over a minute) and a DDSketch chain (p99 over five - /// minutes) kept at ingestion over one `Scan`, each in panes of its - /// query's interval. With equal pane widths, one ingestion run per pane + /// minutes) kept at ingestion over one `Scan`, each in tumbling windows of its + /// query's interval. With equal tumbling-window widths, one ingestion run per tumbling window /// computes the `Scan` for both chains, and the old Stage 3 engine's /// modeled cost equals the real evaluator's with the `Scan` computed /// once, for one schedule (both every 10 s) and for mixed schedules - /// (every 20 s and every 40 s, 20 s panes). With 10 s and 60 s panes the + /// (every 20 s and every 40 s, 20 s tumbling windows). With 10 s and 60 s tumbling windows the /// chains run apart and both count the `Scan` twice (#1151). #[test] fn chains_of_one_refresh_window_share_their_scan_in_the_direct_profile() { - // ((p90 interval, its pane width), (p99 interval, its pane width), Scan computations) + // ((p90 interval, its tumbling-window width), (p99 interval, its tumbling-window width), Scan computations) for (kll, dd, scan_computations) in [ ((10_000, 10_000), (10_000, 10_000), 1), ((20_000, 20_000), (40_000, 20_000), 1), ((10_000, 10_000), (60_000, 60_000), 2), ] { let (modeled, evaluated, multiplier) = two_chains_over_one_scan(kll, dd); - let case = format!("p90 (every, panes) {kll:?} ms, p99 {dd:?} ms"); + let case = format!("p90 (every, tumbling windows) {kll:?} ms, p99 {dd:?} ms"); assert!( (evaluated - modeled).abs() <= 1e-9 * evaluated.max(1e-15), "{case}: direct {modeled}, evaluator {evaluated}" @@ -1952,20 +1955,20 @@ mod tests { } /// [`chains_of_one_refresh_window_share_their_scan_in_the_direct_profile`] - /// with the p90 and the p99 each `(interval in ms, pane width in ms)`: + /// with the p90 and the p99 each `(interval in ms, tumbling-window width in ms)`: /// the direct profile's modeled cost of the selected plan, the /// evaluator's cost of it, and the evaluator's multiplier of the `Scan`. fn two_chains_over_one_scan(kll: (u64, u64), dd: (u64, u64)) -> (f64, f64, u64) { use asap_logical_asap_optimization::test_support::{ - compose_logical_candidate, generate_per_computation_candidates, pane_candidates, - window_composition_pass2_candidates, + compose_logical_candidate, generate_per_computation_candidates, + tumbling_window_candidates, window_composition_pass2_candidates, }; use asap_types::ir::schema::SketchAlgorithm; use asap_types::stages::HowToComputeAggregateMeasure; use std::collections::HashMap; use std::rc::Rc; /// `node` and every node below it as candidates of `configured`, one - /// per node (`seen`, by address), at ingestion below a pane chain. + /// per node (`seen`, by address), at ingestion below a tumbling-window chain. fn append( node: &Rc, configured: &mut PhysicalCandidatesWithCosts, @@ -2007,7 +2010,7 @@ mod tests { let target = AccuracyTarget::max_error(0.05) .failure_probability(0.01) .accuracy_scope(AccuracyScope::PerValue); - // (query, interval in ms, sketch, pane width in ms) + // (query, interval in ms, sketch, tumbling-window width in ms) let queries = [ ( "quantile_over_time(0.9, latency[1m])", @@ -2050,20 +2053,27 @@ mod tests { .iter() .map(|(_, interval, ..)| Some(*interval)) .collect(); - // Each build in its query's pane width. - let panes: Vec<_> = pane_candidates(&roots, &grid) + // Each build in its query's tumbling-window width. + let tumbling_windows: Vec<_> = tumbling_window_candidates(&roots, &grid) .into_iter() - .filter(|pane| { - (pane.window_ms(), pane.tumbling_window_width_ms) == (60_000, kll.1) - || (pane.window_ms(), pane.tumbling_window_width_ms) == (300_000, dd.1) + .filter(|tumbling_window| { + ( + tumbling_window.window_ms(), + tumbling_window.tumbling_window_width_ms, + ) == (60_000, kll.1) + || ( + tumbling_window.window_ms(), + tumbling_window.tumbling_window_width_ms, + ) == (300_000, dd.1) }) .collect(); - assert_eq!(panes.len(), 2, "{panes:?}"); - let roots = window_composition_pass2_candidates(&roots, &panes) + assert_eq!(tumbling_windows.len(), 2, "{tumbling_windows:?}"); + let roots = window_composition_pass2_candidates(&roots, &tumbling_windows) .unwrap() .into_iter() .find(|candidate| { - candidate.pane_candidates.len() == 2 && candidate.shared_chains.is_empty() + candidate.tumbling_window_candidates.len() == 2 + && candidate.shared_chains.is_empty() }) .unwrap() .roots diff --git a/crates/plan-selection/src/search/costing/lower_bounds.rs b/crates/plan-selection/src/search/costing/lower_bounds.rs index 09e54c12e..cb9eada14 100644 --- a/crates/plan-selection/src/search/costing/lower_bounds.rs +++ b/crates/plan-selection/src/search/costing/lower_bounds.rs @@ -19,7 +19,7 @@ pub(in crate::search) struct LowerBoundMetrics { pub priced_candidates: usize, pub rounded_down_candidates: usize, pub ingestion_scans: usize, - pub ingestion_pane_chains: usize, + pub ingestion_tumbling_window_chains: usize, pub query_readers: usize, pub ingestion_transforms: usize, } @@ -252,7 +252,7 @@ pub(in crate::search) fn apply( } asap_types::ir::Operator::ASAP( ASAPOp::SummaryTumblingWindowChain { .. }, - ) => metrics.ingestion_pane_chains += 1, + ) => metrics.ingestion_tumbling_window_chains += 1, _ => metrics.ingestion_transforms += 1, } } @@ -260,7 +260,7 @@ pub(in crate::search) fn apply( } } } - // The solver's coefficient range is narrower than the full pane domain. + // The solver's coefficient range is narrower than the full tumbling-window domain. // Zero is still a certified lower bound for omitted tiny local charges; // the evaluator continues to charge their complete actual cost. metrics.rounded_down_candidates = round_down_small_bounds(&mut costs); @@ -274,8 +274,8 @@ pub(in crate::search) fn apply( /// Each candidate's common reader window: the window every candidate that /// may read it (through time shifts) reads, as the evaluator's -/// `windows_read` counts windows (a time range's range, a pane chain's -/// panes). A query-time scan reads its readers' longest window, or every +/// `windows_read` counts windows (a time range's range, a tumbling-window chain's +/// tumbling windows). A query-time scan reads its readers' longest window, or every /// row held when no reader has one; both are at least the rows of this /// window capped by the rows held. `None` when no reader has a window, or /// when readers' windows differ: which window the scan reads then depends on @@ -311,7 +311,7 @@ fn common_reader_windows(configured: &PhysicalCandidatesWithCosts) -> Vec