User need and MVP
Different levels of tests with targeted queries:
- the queries can be compiled by ASAPQuery-backend and ASAPPlanner, the outputs are the correct physical plans
- The queries/physical plans can be executed by the ASAPQuery-backend data plane, and all the mapped physical operators in ASAP are implemented.
- The execution of such queries and physical plans by ASAPQuery-backend data plane shows lower resource usage and lower query latency compared to exact DB, i.e., Prometheus, VictoriaMetrics, ClickHouse.
Authoritative design
Adding e2e tests for that 3 levels.
Inputs, outputs, and invariants
Evaluation queries: PromQL and ClickHouse SQL
Use a 100 ms scrape interval. Vary label_0 cardinality across 10, 100, 1,000, 10,000, 100,000, and 1,000,000, and vary T across 1m, 10m, 1h, 6h, and 24h. Evaluate instant queries every 1 second and temporal queries every 1 minute. For quantiles, use q = 0.50, 0.75, 0.90, 0.95, and 0.99. Exercise these queries at all three test levels above.
Use a ClickHouse samples table with series_id UInt64, label_0 String, ts_ms Int64 (Unix milliseconds), and value Float64; retain the complete label set in a series dictionary keyed by series_id. Bind {t_ms:Int64} to each evaluation timestamp, {window_ms:Int64} to T in milliseconds, and {q:Float64} to the requested quantile. Each SQL statement starts with the relevant CTEs below. The examples assume unique timestamps per series, finite float samples, no stale markers, and no native histograms. Those additional PromQL semantics need dedicated fixtures and ClickHouse handling before claiming full parity. Keep the benchmark input and evaluation timestamps identical across engines.
Shared CTEs for instant and range-vector input (use the relevant one before each query):
WITH
{t_ms:Int64} AS t_ms,
{window_ms:Int64} AS window_ms,
instant_samples AS (
SELECT series_id, label_0, argMax(value, ts_ms) AS value
FROM samples
WHERE ts_ms <= t_ms AND ts_ms >= t_ms - 300000
GROUP BY series_id, label_0
),
window_samples AS (
SELECT series_id, label_0, ts_ms, value
FROM samples
WHERE ts_ms > t_ms - window_ms AND ts_ms <= t_ms
)
SELECT ...;
The 300000 ms instant lookback must match the configured Prometheus lookback. If the fixture includes stale markers, argMax alone is insufficient: inspect the latest sample's stale flag before admitting a series. Apply label predicates within these CTEs. Preserve the series dictionary when comparing output labels.
For queries 6, 7, and 9, append these CTEs after window_samples and use the appropriate final SELECT. They implement Prometheus 3.5.0's float-counter reset correction and extrapolation in ClickHouse syntax; increase_value = rate_value * window_ms / 1000 except for normal floating-point rounding.
, ordered AS (
SELECT series_id, label_0, ts_ms, value,
row_number() OVER (PARTITION BY series_id ORDER BY ts_ms) AS sample_index,
lag(value, 1, 0.) OVER (PARTITION BY series_id ORDER BY ts_ms) AS previous_value
FROM window_samples
), corrected AS (
SELECT series_id, label_0, count() AS sample_count,
min(ts_ms) AS first_ms, max(ts_ms) AS last_ms,
argMin(value, ts_ms) AS first_value,
argMax(value, ts_ms) AS last_value,
sum(if(sample_index > 1 AND value < previous_value,
previous_value, 0.)) AS reset_correction
FROM ordered
GROUP BY series_id, label_0
HAVING sample_count >= 2
), durations AS (
SELECT *,
last_value - first_value + reset_correction AS corrected_delta,
(last_ms - first_ms) / 1000. AS sampled_seconds,
(first_ms - (t_ms - window_ms)) / 1000. AS gap_start_seconds,
(t_ms - last_ms) / 1000. AS gap_end_seconds
FROM corrected
), thresholds AS (
SELECT *, sampled_seconds / (sample_count - 1) AS average_gap_seconds
FROM durations
), adjusted AS (
SELECT *,
if(gap_start_seconds >= 1.1 * average_gap_seconds,
average_gap_seconds / 2, gap_start_seconds) AS start_seconds,
if(gap_end_seconds >= 1.1 * average_gap_seconds,
average_gap_seconds / 2, gap_end_seconds) AS end_seconds
FROM thresholds
), extrapolated AS (
SELECT *,
if(corrected_delta > 0 AND first_value >= 0,
least(start_seconds, sampled_seconds * first_value / corrected_delta),
start_seconds) AS zero_adjusted_start_seconds
FROM adjusted
), per_series_counter AS (
SELECT series_id, label_0,
corrected_delta *
(sampled_seconds + zero_adjusted_start_seconds + end_seconds)
/ sampled_seconds AS increase_value,
corrected_delta *
(sampled_seconds + zero_adjusted_start_seconds + end_seconds)
/ sampled_seconds / (window_ms / 1000.) AS rate_value
FROM extrapolated
)
SELECT ...;
Replace SELECT ... with the matching final query below. In #2 and #9, ClickHouse ORDER BY label_0, value DESC ... LIMIT 3 BY label_0 ranks rows and keeps up to three original series in each label_0 group. This is the ClickHouse equivalent of PromQL topk by (label_0), which retains the selected series labels. GROUP BY label_0 would collapse the input to one aggregate row per group and would therefore be incorrect here. Ties require a declared deterministic comparator policy.
| # |
PromQL |
ClickHouse final SELECT |
| 1 |
sum by (label_0) (data) |
SELECT label_0, sum(value) AS value FROM instant_samples GROUP BY label_0 |
| 2 |
topk by (label_0) (3, data) |
SELECT series_id, label_0, value FROM instant_samples ORDER BY label_0, value DESC, series_id LIMIT 3 BY label_0 |
| 3 |
quantile by (label_0) (q, data) |
SELECT label_0, quantileExactInclusive({q:Float64})(value) AS value FROM instant_samples GROUP BY label_0 |
| 4 |
sum_over_time(data[T]) |
SELECT series_id, sum(value) AS value FROM window_samples GROUP BY series_id |
| 5 |
quantile_over_time(q, data[T]) |
SELECT series_id, quantileExactInclusive({q:Float64})(value) AS value FROM window_samples GROUP BY series_id |
| 6 |
rate(data[T]) |
SELECT series_id, rate_value AS value FROM per_series_counter |
| 7 |
sum by (label_0) (rate(data[T])) |
SELECT label_0, sum(rate_value) AS value FROM per_series_counter GROUP BY label_0 |
| 8 |
sum by (label_0) (sum_over_time(data[T])) |
SELECT label_0, sum(series_sum) AS value FROM (SELECT series_id, label_0, sum(value) AS series_sum FROM window_samples GROUP BY series_id, label_0) GROUP BY label_0 |
| 9 |
topk by (label_0) (3, rate(data[T])) |
SELECT series_id, label_0, rate_value AS value FROM per_series_counter ORDER BY label_0, rate_value DESC, series_id LIMIT 3 BY label_0 |
| 10 |
quantile_over_time(0.9, data[T]) / quantile_over_time(0.5, data[T]) |
SELECT series_id, quantileExactInclusive(0.9)(value) / quantileExactInclusive(0.5)(value) AS value FROM window_samples GROUP BY series_id |
For supported count_over_time, use SELECT series_id, count() AS value FROM window_samples GROUP BY series_id; for increase, use SELECT series_id, increase_value AS value FROM per_series_counter. Do not use ClickHouse's approximate quantile or approximate topK as the exact SQL baseline. Compare nonfinite values and missing series separately; the finite-only SQL above does not establish those cases.
Supported query forms and compositions
Test each form with and without label filters. Apply equivalent predicates inside the ClickHouse input CTEs. Keep the full series_id grouping for temporal operations and retain original series labels for topk.
| Form |
PromQL |
ClickHouse final SELECT |
| Spatial count |
count by (label_0) (data) |
SELECT label_0, count() AS value FROM instant_samples GROUP BY label_0 |
| Spatial sum |
sum by (label_0) (data) |
SELECT label_0, sum(value) AS value FROM instant_samples GROUP BY label_0 |
| Spatial quantile |
quantile by (label_0) (q, data) |
SELECT label_0, quantileExactInclusive({q:Float64})(value) AS value FROM instant_samples GROUP BY label_0 |
| Spatial top-k |
topk by (label_0) (k, data) |
SELECT series_id, label_0, value FROM instant_samples ORDER BY label_0, value DESC, series_id LIMIT {k:UInt64} BY label_0 |
| Temporal count |
count_over_time(data[T]) |
SELECT series_id, count() AS value FROM window_samples GROUP BY series_id |
| Temporal sum |
sum_over_time(data[T]) |
SELECT series_id, sum(value) AS value FROM window_samples GROUP BY series_id |
| Temporal quantile |
quantile_over_time(q, data[T]) |
SELECT series_id, quantileExactInclusive({q:Float64})(value) AS value FROM window_samples GROUP BY series_id |
| Counter increase |
increase(data[T]) |
SELECT series_id, increase_value AS value FROM per_series_counter |
| Counter rate |
rate(data[T]) |
SELECT series_id, rate_value AS value FROM per_series_counter |
Also test multiple spatial aggregations, a temporal aggregation followed by spatial aggregations, and binary operations between aggregation expressions. For ClickHouse SQL, evaluate both operands at the same timestamp, preserve the PromQL grouping and label-matching rules, then join on the correct label set. Include cases for missing series, ties, and nonfinite values before claiming full composition support.
ClickHouse references: exact inclusive quantile, LIMIT BY, window functions, and argMax. The counter formula follows Prometheus 3.5.0 extrapolatedRate.
Before and after behavior
After should have these tests in MVP CI of the ASAPQuery-backend repo.
Unit and end-to-end acceptance tests
Explicit non-goals
User need and MVP
Different levels of tests with targeted queries:
Authoritative design
Adding e2e tests for that 3 levels.
Inputs, outputs, and invariants
Evaluation queries: PromQL and ClickHouse SQL
Use a 100 ms scrape interval. Vary
label_0cardinality across 10, 100, 1,000, 10,000, 100,000, and 1,000,000, and varyTacross1m,10m,1h,6h, and24h. Evaluate instant queries every 1 second and temporal queries every 1 minute. For quantiles, useq= 0.50, 0.75, 0.90, 0.95, and 0.99. Exercise these queries at all three test levels above.Use a ClickHouse
samplestable withseries_id UInt64,label_0 String,ts_ms Int64(Unix milliseconds), andvalue Float64; retain the complete label set in a series dictionary keyed byseries_id. Bind{t_ms:Int64}to each evaluation timestamp,{window_ms:Int64}toTin milliseconds, and{q:Float64}to the requested quantile. Each SQL statement starts with the relevant CTEs below. The examples assume unique timestamps per series, finite float samples, no stale markers, and no native histograms. Those additional PromQL semantics need dedicated fixtures and ClickHouse handling before claiming full parity. Keep the benchmark input and evaluation timestamps identical across engines.Shared CTEs for instant and range-vector input (use the relevant one before each query):
WITH {t_ms:Int64} AS t_ms, {window_ms:Int64} AS window_ms, instant_samples AS ( SELECT series_id, label_0, argMax(value, ts_ms) AS value FROM samples WHERE ts_ms <= t_ms AND ts_ms >= t_ms - 300000 GROUP BY series_id, label_0 ), window_samples AS ( SELECT series_id, label_0, ts_ms, value FROM samples WHERE ts_ms > t_ms - window_ms AND ts_ms <= t_ms ) SELECT ...;The
300000ms instant lookback must match the configured Prometheus lookback. If the fixture includes stale markers,argMaxalone is insufficient: inspect the latest sample's stale flag before admitting a series. Apply label predicates within these CTEs. Preserve the series dictionary when comparing output labels.For queries 6, 7, and 9, append these CTEs after
window_samplesand use the appropriate finalSELECT. They implement Prometheus 3.5.0's float-counter reset correction and extrapolation in ClickHouse syntax;increase_value = rate_value * window_ms / 1000except for normal floating-point rounding.Replace
SELECT ...with the matching final query below. In #2 and #9, ClickHouseORDER BY label_0, value DESC ... LIMIT 3 BY label_0ranks rows and keeps up to three original series in eachlabel_0group. This is the ClickHouse equivalent of PromQLtopk by (label_0), which retains the selected series labels.GROUP BY label_0would collapse the input to one aggregate row per group and would therefore be incorrect here. Ties require a declared deterministic comparator policy.SELECTsum by (label_0) (data)SELECT label_0, sum(value) AS value FROM instant_samples GROUP BY label_0topk by (label_0) (3, data)SELECT series_id, label_0, value FROM instant_samples ORDER BY label_0, value DESC, series_id LIMIT 3 BY label_0quantile by (label_0) (q, data)SELECT label_0, quantileExactInclusive({q:Float64})(value) AS value FROM instant_samples GROUP BY label_0sum_over_time(data[T])SELECT series_id, sum(value) AS value FROM window_samples GROUP BY series_idquantile_over_time(q, data[T])SELECT series_id, quantileExactInclusive({q:Float64})(value) AS value FROM window_samples GROUP BY series_idrate(data[T])SELECT series_id, rate_value AS value FROM per_series_countersum by (label_0) (rate(data[T]))SELECT label_0, sum(rate_value) AS value FROM per_series_counter GROUP BY label_0sum by (label_0) (sum_over_time(data[T]))SELECT label_0, sum(series_sum) AS value FROM (SELECT series_id, label_0, sum(value) AS series_sum FROM window_samples GROUP BY series_id, label_0) GROUP BY label_0topk by (label_0) (3, rate(data[T]))SELECT series_id, label_0, rate_value AS value FROM per_series_counter ORDER BY label_0, rate_value DESC, series_id LIMIT 3 BY label_0quantile_over_time(0.9, data[T]) / quantile_over_time(0.5, data[T])SELECT series_id, quantileExactInclusive(0.9)(value) / quantileExactInclusive(0.5)(value) AS value FROM window_samples GROUP BY series_idFor supported
count_over_time, useSELECT series_id, count() AS value FROM window_samples GROUP BY series_id; forincrease, useSELECT series_id, increase_value AS value FROM per_series_counter. Do not use ClickHouse's approximatequantileor approximatetopKas the exact SQL baseline. Compare nonfinite values and missing series separately; the finite-only SQL above does not establish those cases.Supported query forms and compositions
Test each form with and without label filters. Apply equivalent predicates inside the ClickHouse input CTEs. Keep the full
series_idgrouping for temporal operations and retain original series labels fortopk.SELECTcount by (label_0) (data)SELECT label_0, count() AS value FROM instant_samples GROUP BY label_0sum by (label_0) (data)SELECT label_0, sum(value) AS value FROM instant_samples GROUP BY label_0quantile by (label_0) (q, data)SELECT label_0, quantileExactInclusive({q:Float64})(value) AS value FROM instant_samples GROUP BY label_0topk by (label_0) (k, data)SELECT series_id, label_0, value FROM instant_samples ORDER BY label_0, value DESC, series_id LIMIT {k:UInt64} BY label_0count_over_time(data[T])SELECT series_id, count() AS value FROM window_samples GROUP BY series_idsum_over_time(data[T])SELECT series_id, sum(value) AS value FROM window_samples GROUP BY series_idquantile_over_time(q, data[T])SELECT series_id, quantileExactInclusive({q:Float64})(value) AS value FROM window_samples GROUP BY series_idincrease(data[T])SELECT series_id, increase_value AS value FROM per_series_counterrate(data[T])SELECT series_id, rate_value AS value FROM per_series_counterAlso test multiple spatial aggregations, a temporal aggregation followed by spatial aggregations, and binary operations between aggregation expressions. For ClickHouse SQL, evaluate both operands at the same timestamp, preserve the PromQL grouping and label-matching rules, then join on the correct label set. Include cases for missing series, ties, and nonfinite values before claiming full composition support.
ClickHouse references: exact inclusive quantile, LIMIT BY, window functions, and argMax. The counter formula follows Prometheus 3.5.0
extrapolatedRate.Before and after behavior
After should have these tests in MVP CI of the ASAPQuery-backend repo.
Unit and end-to-end acceptance tests
Explicit non-goals