Files
discord9 a7590f8174 perf(promql): avoid repeated scans in sliding range evaluation (#8646)
* perf(promql): use two pointers for sliding range boundaries

Replace the stale cursor heuristic in RangeManipulateStream::calculate_range
with monotonic left/right cursors. The old path rescanned each evaluation
window (O(E x samples-per-window)) and could lose valid samples after sparse
gaps or trailing empty windows. The two pointers keep strict monotonic
progress, reducing boundary generation to O(N + E) while preserving
(curr-range, curr] semantics, start/end shortening, and empty-window output.

Controlled release benchmarks (fixed CPU, ABBA):
- Public RangeManipulate wall time: ~28% faster at 1m/15s, ~66% at 5m/15s,
  ~96% at 1h/15s.
- Warmed distributed TQL ANALYZE 1h queries: ~17-21% faster end to end;
  shorter windows stayed within run-order noise.

Signed-off-by: discord9 <discord9@163.com>

* perf(promql): specialize changes/resets with adaptive edge counting

The generic range_fn macro slices, downcasts, and rescans every overlapping
window for changes() and resets(). Replace the macro path for these two
functions with hand-written UDF wrappers backed by a shared private
edge-count kernel: direct raw-offset scans when requested edges are few,
otherwise one global u64 edge prefix so each window is answered by a prefix
difference.

Behavior is preserved bit-for-bit, including raw null-buffer values, NaN
semantics, signed zero, infinities, empty/singleton windows, independent
timestamp/value offsets, arbitrary window layouts, and exact DataFusion
error messages. The shared proc macro, planner, serializer, and other range
functions are untouched.

Controlled release benchmarks (fixed CPU, ABBA):
- Dense sliding windows (k=4/20/240): 91.7-95.6% less public UDF wall time.
- Low-coverage fallback (N=4096, 8 windows): 73.9-74.4% faster.
- Warmed distributed TQL ANALYZE 5m/1h changes/resets: 12.1-19.7% client
  and 12.0-20.9% server latency improvement; controls stayed within drift.

Signed-off-by: discord9 <discord9@163.com>

* ci(query-regression): include PromQL range boundary case in defaults

An audit of historical query-regression runs found zero range-query
coverage: all 208 PromQL ANALYZE samples were bare selectors, so range
evaluation could regress without CI noticing. Wire the
promql_range_boundary case (introduced in #8646) into DEFAULT_CASES so
label-triggered runs measure the range path. The case is cheap: a ~0.3s
synthetic fixture and about a minute of query execution per base/candidate
pass.

Signed-off-by: discord9 <discord9@163.com>

* chore(promql): address sliding range review nits

Move test-only imports into their test modules and remove the unused
pre-specialization changes and resets helpers.

Signed-off-by: discord9 <discord9@163.com>

* style(promql): apply pinned rustfmt

Signed-off-by: discord9 <discord9@163.com>

* test(promql): cover sparse range results

Share the changes and resets test scaffolding while keeping their behavior
oracles independent. Add an end-to-end sqlness regression for sparse samples,
empty intermediate windows, and a valid trailing sample.

Signed-off-by: discord9 <discord9@163.com>

---------

Signed-off-by: discord9 <discord9@163.com>
2026-08-03 06:53:21 +00:00

167 lines
4.7 KiB
TOML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PromQL overlapping range-boundary benchmark.
#
# 128 series × 780 timestamps at 15s cadence = 99,840 rows. Data starts one
# hour before evaluation; 481 evaluations span two hours. `count_over_time` is
# boundary-sensitive, while `sum_over_time` intentionally includes kernel scan
# work and the plain selector controls unrelated path variance.
[case]
name = "promql_range_boundary"
description = "PromQL overlapping range-boundary generation at 15-second scrape and evaluation cadence"
[scenario]
kind = "direct_readable_sst"
seed = 8623
[[scenario.tables]]
database = "public"
name = "promql_range_boundary"
engine = "mito"
append_mode = true
sst_format = "flat"
primary_key = ["host", "instance"]
time_index = "ts"
[[scenario.tables.columns]]
name = "host"
type = "STRING"
semantic = "tag"
distribution = { kind = "cardinality", values = 16, prefix = "host" }
[[scenario.tables.columns]]
name = "instance"
type = "STRING"
semantic = "tag"
distribution = { kind = "cardinality", values = 128, prefix = "instance" }
[[scenario.tables.columns]]
name = "value"
type = "DOUBLE"
semantic = "field"
distribution = { kind = "deterministic_wave", min = 0.0, max = 1000.0 }
[[scenario.tables.columns]]
name = "ts"
type = "TIMESTAMP(9)"
semantic = "timestamp"
[scenario.layout]
regions = 1
sst_count = 13
rows_per_sst = 7680
row_group_size = 1920
series_count = 128
start_unix_nanos = 1704067200000000000
step_nanos = 15000000000
time_range_layout = "non_overlapping_per_sst"
series_layout = "timestamp_major"
[[scenario.queries]]
name = "plain_selector_control_2h"
kind = "tql"
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') promql_range_boundary{host=~'host.*'}"
warmup = 3
iterations = 9
[scenario.queries.thresholds]
max_candidate_latency_regression_pct = 20
# Four samples per full window: fixed-overhead/small-window control.
[[scenario.queries]]
name = "count_over_time_1m"
kind = "tql"
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') count_over_time(promql_range_boundary{host=~'host.*'}[1m])"
warmup = 3
iterations = 9
[scenario.queries.thresholds]
max_candidate_latency_regression_pct = 20
# Twenty samples per full window: representative dense overlap.
[[scenario.queries]]
name = "count_over_time_5m"
kind = "tql"
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') count_over_time(promql_range_boundary{host=~'host.*'}[5m])"
warmup = 3
iterations = 9
[scenario.queries.thresholds]
max_candidate_latency_regression_pct = 20
# 240 samples per full window: primary boundary-sensitive stress case.
[[scenario.queries]]
name = "count_over_time_1h"
kind = "tql"
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') count_over_time(promql_range_boundary{host=~'host.*'}[1h])"
warmup = 3
iterations = 9
[scenario.queries.thresholds]
max_candidate_latency_regression_pct = 20
# Edge-scan kernel candidates: the Phase 3 implementation replaces repeated
# per-window scans with exact edge-prefix counts.
[[scenario.queries]]
name = "changes_5m"
kind = "tql"
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') changes(promql_range_boundary{host=~'host.*'}[5m])"
warmup = 3
iterations = 9
[scenario.queries.thresholds]
max_candidate_latency_regression_pct = 20
[[scenario.queries]]
name = "changes_1h"
kind = "tql"
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') changes(promql_range_boundary{host=~'host.*'}[1h])"
warmup = 3
iterations = 9
[scenario.queries.thresholds]
max_candidate_latency_regression_pct = 20
[[scenario.queries]]
name = "resets_5m"
kind = "tql"
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') resets(promql_range_boundary{host=~'host.*'}[5m])"
warmup = 3
iterations = 9
[scenario.queries.thresholds]
max_candidate_latency_regression_pct = 20
[[scenario.queries]]
name = "resets_1h"
kind = "tql"
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') resets(promql_range_boundary{host=~'host.*'}[1h])"
warmup = 3
iterations = 9
[scenario.queries.thresholds]
max_candidate_latency_regression_pct = 20
# The deterministic wave is not a true monotonic counter. This query exercises
# the regular rate path but is not a numeric counter-rate oracle.
[[scenario.queries]]
name = "rate_wave_5m"
kind = "tql"
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') rate(promql_range_boundary{host=~'host.*'}[5m])"
warmup = 3
iterations = 9
[scenario.queries.thresholds]
max_candidate_latency_regression_pct = 20
# Same wide boundaries as count, but the sum kernel scans all window values and
# intentionally dilutes the boundary-only gain.
[[scenario.queries]]
name = "sum_over_time_1h_dilution"
kind = "tql"
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') sum_over_time(promql_range_boundary{host=~'host.*'}[1h])"
warmup = 3
iterations = 9
[scenario.queries.thresholds]
max_candidate_latency_regression_pct = 20