Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,21 @@ pub enum InferOperation {
/// Extract maximum value from MinMax/HydraMinMax accumulator
ExtractMax,

/// Extract increase value from Increase/HydraIncrease accumulator
ExtractIncrease,

/// Extract rate (increase / time_range) from Increase accumulator
ExtractRate,
/// Extract increase value from Increase/HydraIncrease accumulator.
///
/// The optional payload carries the range-vector boundaries
/// `(range_start_ms, range_end_ms)` of the instant query being evaluated.
/// When present, the accumulator reproduces Prometheus `extrapolatedRate`
/// (counter-reset correction + extrapolation); when `None` (callers without a
/// range vector, e.g. SQL/Elastic), it falls back to the plain reset-corrected
/// increase. Embedded here so it survives the logical→physical boundary, the
/// same way `Quantile`/`TopK` carry their parameters.
ExtractIncrease(Option<(i64, i64)>),

/// Extract rate (increase / time_range) from Increase accumulator. Carries the
/// same optional `(range_start_ms, range_end_ms)` payload as
/// [`InferOperation::ExtractIncrease`].
ExtractRate(Option<(i64, i64)>),

// ========================================================================
// Sketch operations
Expand Down Expand Up @@ -262,8 +272,8 @@ impl fmt::Display for InferOperation {
InferOperation::ExtractCount => write!(f, "EXTRACT_COUNT"),
InferOperation::ExtractMin => write!(f, "EXTRACT_MIN"),
InferOperation::ExtractMax => write!(f, "EXTRACT_MAX"),
InferOperation::ExtractIncrease => write!(f, "EXTRACT_INCREASE"),
InferOperation::ExtractRate => write!(f, "EXTRACT_RATE"),
InferOperation::ExtractIncrease(_) => write!(f, "EXTRACT_INCREASE"),
InferOperation::ExtractRate(_) => write!(f, "EXTRACT_RATE"),
// Sketch operations
InferOperation::CountDistinct => write!(f, "COUNT_DISTINCT"),
InferOperation::Quantile(p) => write!(f, "QUANTILE({:.4})", *p as f64 / 10000.0),
Expand Down Expand Up @@ -1112,8 +1122,8 @@ impl SummaryInfer {
InferOperation::ExtractCount => DataType::Float64,
InferOperation::ExtractMin => DataType::Float64,
InferOperation::ExtractMax => DataType::Float64,
InferOperation::ExtractIncrease => DataType::Float64,
InferOperation::ExtractRate => DataType::Float64,
InferOperation::ExtractIncrease(_) => DataType::Float64,
InferOperation::ExtractRate(_) => DataType::Float64,
// Sketch operations
InferOperation::CountDistinct => DataType::UInt64,
InferOperation::Quantile(_) | InferOperation::Median => DataType::Float64,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ use std::fmt;
use std::str::FromStr;
use tracing::debug;

/// `query_kwargs` key carrying the range-vector start boundary (epoch ms) for a
/// `rate()`/`increase()` evaluation. Used to drive Prometheus-style extrapolation
/// in `IncreaseAccumulator::query`. When absent, the accumulator falls back to a
/// non-extrapolated, reset-corrected result.
pub const RANGE_START_MS_KWARG: &str = "range_start_ms";

/// `query_kwargs` key carrying the range-vector end boundary (epoch ms) for a
/// `rate()`/`increase()` evaluation. See [`RANGE_START_MS_KWARG`].
pub const RANGE_END_MS_KWARG: &str = "range_end_ms";

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum QueryTreatmentType {
Exact,
Expand Down
32 changes: 29 additions & 3 deletions asap-query-engine/src/engines/logical/plan_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ use datafusion_summary_library::{
InferOperation, PrecomputedSummaryRead, SketchType, SummaryInfer, SummaryMergeMultiple,
};
use promql_parser::parser::token::{self, T_ADD, T_DIV, T_MOD, T_MUL, T_POW, T_SUB};
use promql_utilities::query_logics::enums::{AggregationType, Statistic};
use promql_utilities::query_logics::enums::{
AggregationType, Statistic, RANGE_END_MS_KWARG, RANGE_START_MS_KWARG,
};
use std::sync::Arc;

use crate::engines::simple_engine::{QueryExecutionContext, StoreQueryParams};
Expand Down Expand Up @@ -189,8 +191,8 @@ impl QueryExecutionContext {
Statistic::Min => Ok(InferOperation::ExtractMin),
Statistic::Max => Ok(InferOperation::ExtractMax),
Statistic::Count => Ok(InferOperation::ExtractCount),
Statistic::Increase => Ok(InferOperation::ExtractIncrease),
Statistic::Rate => Ok(InferOperation::ExtractRate),
Statistic::Increase => Ok(InferOperation::ExtractIncrease(self.range_bounds_ms())),
Statistic::Rate => Ok(InferOperation::ExtractRate(self.range_bounds_ms())),
Statistic::Quantile => {
// Extract quantile parameter from query_kwargs
let q = self
Expand All @@ -215,6 +217,30 @@ impl QueryExecutionContext {
}
}

/// Parse the range-vector boundaries `(range_start_ms, range_end_ms)` from the
/// query kwargs.
///
/// The PromQL layer populates these for rate()/increase() instant queries via
/// `build_query_kwargs_promql`; embedding them in the `InferOperation` carries
/// them across the logical→physical boundary so `IncreaseAccumulator` can
/// extrapolate. Returns `None` when absent (non-range callers), leaving the
/// accumulator on its reset-corrected fallback.
fn range_bounds_ms(&self) -> Option<(i64, i64)> {
let start = self
.metadata
.query_kwargs
.get(RANGE_START_MS_KWARG)?
.parse::<i64>()
.ok()?;
let end = self
.metadata
.query_kwargs
.get(RANGE_END_MS_KWARG)?
.parse::<i64>()
.ok()?;
Some((start, end))
}

/// Map aggregation type to SketchType (SummaryType) for the value accumulator
fn map_aggregation_type_to_summary_type(&self) -> Result<SketchType, DataFusionError> {
Self::agg_type_to_sketch_type(self.agg_info.aggregation_type_for_value)
Expand Down
25 changes: 17 additions & 8 deletions asap-query-engine/src/engines/physical/accumulator_serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use datafusion_summary_library::SketchType;
use crate::data_model::{MultipleSubpopulationAggregate, SingleSubpopulationAggregate};
use crate::precompute_operators::{
CountMinSketchAccumulator, DatasketchesKLLAccumulator, DeltaSetAggregatorAccumulator,
HllAccumulator, HydraKllSketchAccumulator, MultipleIncreaseAccumulator, MultipleSumAccumulator,
SetAggregatorAccumulator, SumAccumulator,
HllAccumulator, HydraKllSketchAccumulator, IncreaseAccumulator, MultipleIncreaseAccumulator,
MultipleSumAccumulator, SetAggregatorAccumulator, SumAccumulator,
};
use crate::AggregateCore;

Expand All @@ -40,9 +40,12 @@ pub fn deserialize_accumulator(
})?;
Ok(Box::new(acc))
}
SketchType::Increase => Err(DataFusionError::NotImplemented(
"Increase Arroyo deserialization not implemented".to_string(),
)),
SketchType::Increase => {
let acc = IncreaseAccumulator::deserialize_from_bytes_arroyo(bytes).map_err(|e| {
DataFusionError::Internal(format!("Failed to deserialize Increase: {}", e))
})?;
Ok(Box::new(acc))
}
SketchType::MinMax => Err(DataFusionError::NotImplemented(
"MinMax Arroyo deserialization not implemented".to_string(),
)),
Expand Down Expand Up @@ -151,6 +154,9 @@ pub fn serialize_accumulator_arroyo(acc: &dyn AggregateCore) -> Vec<u8> {
if let Some(set_acc) = acc.as_any().downcast_ref::<SetAggregatorAccumulator>() {
return set_acc.serialize_to_bytes_arroyo();
}
if let Some(inc_acc) = acc.as_any().downcast_ref::<IncreaseAccumulator>() {
return inc_acc.serialize_to_bytes_arroyo();
}
if let Some(inc_acc) = acc.as_any().downcast_ref::<MultipleIncreaseAccumulator>() {
return inc_acc.serialize_to_bytes_arroyo();
}
Expand Down Expand Up @@ -178,9 +184,12 @@ pub fn deserialize_single_subpopulation(
})?;
Ok(Box::new(acc))
}
SketchType::Increase => Err(DataFusionError::NotImplemented(
"Increase Arroyo deserialization not implemented".to_string(),
)),
SketchType::Increase => {
let acc = IncreaseAccumulator::deserialize_from_bytes_arroyo(bytes).map_err(|e| {
DataFusionError::Internal(format!("Failed to deserialize Increase: {}", e))
})?;
Ok(Box::new(acc))
}
SketchType::MinMax => Err(DataFusionError::NotImplemented(
"MinMax Arroyo deserialization not implemented".to_string(),
)),
Expand Down
19 changes: 16 additions & 3 deletions asap-query-engine/src/engines/physical/summary_infer_exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use datafusion::physical_plan::{
};
use datafusion_summary_library::{InferOperation, SketchType, SummaryInfer};
use futures::stream;
use promql_utilities::query_logics::enums::Statistic;
use promql_utilities::query_logics::enums::{Statistic, RANGE_END_MS_KWARG, RANGE_START_MS_KWARG};
use std::any::Any;
use std::collections::HashMap;
use std::fmt;
Expand Down Expand Up @@ -150,8 +150,8 @@ impl SummaryInferExec {
InferOperation::ExtractCount => Statistic::Count,
InferOperation::ExtractMin => Statistic::Min,
InferOperation::ExtractMax => Statistic::Max,
InferOperation::ExtractIncrease => Statistic::Increase,
InferOperation::ExtractRate => Statistic::Rate,
InferOperation::ExtractIncrease(_) => Statistic::Increase,
InferOperation::ExtractRate(_) => Statistic::Rate,
InferOperation::CountDistinct => Statistic::Cardinality,
InferOperation::Quantile(_) | InferOperation::Median => Statistic::Quantile,
InferOperation::TopK(_) => Statistic::Topk,
Expand All @@ -164,6 +164,12 @@ impl SummaryInferExec {
///
/// Some operations embed parameters (e.g. Quantile embeds the quantile value,
/// TopK embeds k) that accumulators need via the kwargs HashMap.
///
/// For `ExtractRate`/`ExtractIncrease` the embedded `(range_start_ms,
/// range_end_ms)` boundaries are re-materialized into
/// [`RANGE_START_MS_KWARG`]/[`RANGE_END_MS_KWARG`] so `IncreaseAccumulator`
/// applies Prometheus extrapolation instead of the reset-corrected fallback.
/// Without a payload (`None`), no kwargs are produced and the fallback stands.
fn infer_op_to_kwargs(op: &InferOperation) -> Option<HashMap<String, String>> {
match op {
InferOperation::Quantile(q_u16) => {
Expand All @@ -182,6 +188,13 @@ impl SummaryInferExec {
kwargs.insert("k".to_string(), k.to_string());
Some(kwargs)
}
InferOperation::ExtractIncrease(Some((range_start_ms, range_end_ms)))
| InferOperation::ExtractRate(Some((range_start_ms, range_end_ms))) => {
let mut kwargs = HashMap::new();
kwargs.insert(RANGE_START_MS_KWARG.to_string(), range_start_ms.to_string());
kwargs.insert(RANGE_END_MS_KWARG.to_string(), range_end_ms.to_string());
Some(kwargs)
}
_ => None,
}
}
Expand Down
12 changes: 10 additions & 2 deletions asap-query-engine/src/engines/simple_engine/mod.rs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This updates only the range query execution path, but not the instant query execution path. Note that Prometheus "instant" queries also operate on a range of data. Range queries are simply a way to express multiple instant queries executing at different timesteps.

When you send a query request to Prometheus, it can be an instant query, evaluated at one point in time, or a range query at equally-spaced steps between a start and an end time. PromQL works exactly the same in each case; the range query is just like an instant query run multiple times at different timestamps.

https://prometheus.io/docs/prometheus/latest/querying/basics/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are actually three execution paths for rate()/increase(), not two:

  1. Single-metric instant (handle_query_promql → execute_context → execute_query_pipeline) — gets RANGE_START_MS_KWARG/RANGE_END_MS_KWARG via build_query_kwargs_promql's new Statistic::Rate | Statistic::Increase arm (promql.rs:144-101).
  2. Range query, single or binary (handle_range_query_promql / handle_binary_expr_range_promql → execute_range_query_pipeline) — gets the new step_kwargs with per-step RANGE_START_MS_KWARG/RANGE_END_MS_KWARG (mod.rs:1511-1550). ✅ Fixed.
  3. Binary-arithmetic instant (handle_binary_expr_promql → build_arm_logical_plan → DataFusion physical plan → SummaryInferExec) — this is a completely separate code path, and SummaryInferExec::infer_op_to_kwargs (engines/physical/summary_infer_exec.rs:167-187) only
    builds kwargs for Quantile/Median/TopK; everything else, including InferOperation::ExtractIncrease/ExtractRate, falls through to _ => None. That None propagates to IncreaseAccumulator::query(statistic, None), which hits the fallback branch — reset-corrected but not
    extrapolated.

@zaoxing zaoxing Jul 3, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using my phone, will double check. But I don’t think counter-reset problem happens on a single-metric instant query as all points are in one timestamp? Oh, with some lookback windows.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zaoxing This is confusing about Prometheus but instant query != instant vector.
Instant vector - all data points at 1 timestamp
Range vector - data points across multiple timestamp
Instant query - a query evaluated at 1 timestamp, but may span data points across multiple timestamps. For e.g. avg_over_time(data[5m]) @ t=10:00:00 is an instant query evaluated at 10am that consider data from 9:55am to 10am
Range query - an instant query that is evaluated multiple times, at multiple timestamps.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, will revision.

Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ use promql_utilities::ast_matching::{PromQLPattern, PromQLPatternBuilder};
use promql_utilities::data_model::KeyByLabelNames;
use promql_utilities::get_is_collapsable;
use promql_utilities::query_logics::enums::{
AggregationOperator, AggregationType, PromQLFunction, Statistic,
AggregationOperator, AggregationType, PromQLFunction, Statistic, RANGE_END_MS_KWARG,
RANGE_START_MS_KWARG,
};
use serde_json::Value;

Expand Down Expand Up @@ -1501,12 +1502,19 @@ impl SimpleEngine {
key, start_ms, end_ms, step_ms, lookback_ms, tumbling_window_ms
);

// Per-step query kwargs: clone the base map once, then refresh the
// range-vector boundaries each step (rate/increase extrapolation needs
// [window_start, current_time) per emitted point).
let mut step_kwargs = context.base.metadata.query_kwargs.clone();

// Iterate by OUTPUT timestamp, not by bucket index
let mut current_time = start_ms;
while current_time <= end_ms {
// Window covers [current_time - lookback_ms, current_time)
// This means we look at buckets that START within this range
let window_start = current_time.saturating_sub(lookback_ms);
step_kwargs.insert(RANGE_START_MS_KWARG.to_string(), window_start.to_string());
step_kwargs.insert(RANGE_END_MS_KWARG.to_string(), current_time.to_string());

// Collect all AVAILABLE buckets in this window (skip missing ones)
let mut window_buckets: Vec<Box<dyn AggregateCore>> = Vec::new();
Expand All @@ -1532,7 +1540,7 @@ impl SimpleEngine {
merged.as_ref(),
&context.base.metadata.statistic_to_compute,
&Some(key.clone()),
&context.base.metadata.query_kwargs,
&step_kwargs,
) {
Ok(value) => {
debug!(
Expand Down
18 changes: 16 additions & 2 deletions asap-query-engine/src/engines/simple_engine/promql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use asap_types::query_requirements::build_query_requirements_promql;
use asap_types::PromQLSchema;
use promql_utilities::ast_matching::PromQLMatchResult;
use promql_utilities::data_model::KeyByLabelNames;
use promql_utilities::query_logics::enums::Statistic;
use promql_utilities::query_logics::enums::{Statistic, RANGE_END_MS_KWARG, RANGE_START_MS_KWARG};
use promql_utilities::query_logics::parsing::get_metric_and_spatial_filter;
use std::collections::HashMap;
use std::time::Instant;
Expand Down Expand Up @@ -129,6 +129,7 @@ impl SimpleEngine {
&self,
statistic: &Statistic,
match_result: &PromQLMatchResult,
timestamps: &QueryTimestamps,
) -> Result<HashMap<String, String>, String> {
let mut query_kwargs = HashMap::new();

Expand All @@ -145,6 +146,19 @@ impl SimpleEngine {
debug!("Extracted k value: {:?}", k);
query_kwargs.insert("k".to_string(), k);
}
Statistic::Rate | Statistic::Increase => {
// Pass the range-vector boundaries so IncreaseAccumulator can apply
// Prometheus extrapolation. For an instant `rate(x[5m])` evaluated
// at T, `timestamps` spans [T - 5m, T].
query_kwargs.insert(
RANGE_START_MS_KWARG.to_string(),
timestamps.start_timestamp.to_string(),
);
query_kwargs.insert(
RANGE_END_MS_KWARG.to_string(),
timestamps.end_timestamp.to_string(),
);
}
_ => {}
}

Expand Down Expand Up @@ -286,7 +300,7 @@ impl SimpleEngine {
}

let query_kwargs = self
.build_query_kwargs_promql(&statistic_to_compute, match_result)
.build_query_kwargs_promql(&statistic_to_compute, match_result, &timestamps)
.map_err(|e| {
warn!("{}", e);
e
Expand Down
Loading
Loading