mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-10-25 13:22:30 +00:00
* perf(promql): reuse sliding min and max candidates Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(promql): simplify extrema benchmark parameters Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test(promql): record baseline sliding extrema SQL results Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * perf(promql): rescan windows that barely overlap Reusing candidates loses to a plain scan when consecutive windows overlap little: the deque bookkeeping then costs more than the rescan it replaces. A local Criterion run on 4096 samples at width 240 / step 240 measured 10.79 -> 22.14 us for min and 12.83 -> 19.85 us for max. Pick the evaluator once per batch from the first two windows. RangeManipulate emits one window length and one step per batch, so that sample decides for all of them, and both evaluators return identical bits, so a wrong pick costs time only. Batches that do not qualify fold each window on its own. Move the incremental state into SlidingExtrema so tests can drive it directly: the exhaustive four-sample differential test cannot reach it through a UDF call, because such a batch never qualifies for reuse. Signed-off-by: Dennis Zhuang <xzhuang@greptime.com> * fix(promql): select the extrema evaluator from batch averages Reading the window shape off the first two windows misreads the batch. RangeManipulate starts a series at max(query start, first aligned sample), so a series that begins inside the query range gets a first window covering roughly one step, and a window covering no sample at all is emitted as (0, 0). Either one closed the gate for the whole batch, including the one-hour window at a 15s step that candidate reuse was written for. Compare the batch averages instead: at least 32 samples per window, and a step advancing at most a quarter of that. Uniform batches select exactly as before, so the thresholds keep the meaning they were measured with. The 32-sample rule had also moved most of the benchmark and query-regression shapes onto the rescan, including the case built to measure reset and rebuild. Widen those windows to 40 samples, add a step at the selection boundary, and add an end-to-end case with 40-sample windows advancing 5. Signed-off-by: Dennis Zhuang <xzhuang@greptime.com> * fix(promql): ignore empty windows when measuring batch advance The advance was read from the first and last window offsets, but a window covering no sample is emitted as (0, 0). A query whose last evaluation lands exactly one window past the last sample ends on such a window, and its zero offset made a batch of disjoint windows look like one that never moved, which selected the evaluator built for overlap. Results stayed correct; the cost was deque bookkeeping on the shape the scan fallback exists for. Take the offset span over the windows that cover a sample. Empty windows stay in the window count, where they only make both conditions stricter. Signed-off-by: Dennis Zhuang <xzhuang@greptime.com> --------- Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> Signed-off-by: Dennis Zhuang <xzhuang@greptime.com> Co-authored-by: Dennis Zhuang <xzhuang@greptime.com>
273 lines
8.0 KiB
TOML
273 lines
8.0 KiB
TOML
# 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. Min/max cover sliding extrema, 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
|
||
|
||
# Sliding extrema cover small, representative, and wide overlapping windows. At this
|
||
# cadence the 1m and 5m windows hold about 5 and 21 samples, below the 32-sample
|
||
# average the UDF requires before it reuses candidates, so they measure the rescan.
|
||
[[scenario.queries]]
|
||
name = "min_over_time_1m"
|
||
kind = "tql"
|
||
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') min_over_time(promql_range_boundary{host=~'host.*'}[1m])"
|
||
warmup = 3
|
||
iterations = 9
|
||
|
||
[scenario.queries.thresholds]
|
||
max_candidate_latency_regression_pct = 20
|
||
|
||
[[scenario.queries]]
|
||
name = "max_over_time_1m"
|
||
kind = "tql"
|
||
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') max_over_time(promql_range_boundary{host=~'host.*'}[1m])"
|
||
warmup = 3
|
||
iterations = 9
|
||
|
||
[scenario.queries.thresholds]
|
||
max_candidate_latency_regression_pct = 20
|
||
|
||
[[scenario.queries]]
|
||
name = "min_over_time_5m"
|
||
kind = "tql"
|
||
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') min_over_time(promql_range_boundary{host=~'host.*'}[5m])"
|
||
warmup = 3
|
||
iterations = 9
|
||
|
||
[scenario.queries.thresholds]
|
||
max_candidate_latency_regression_pct = 20
|
||
|
||
[[scenario.queries]]
|
||
name = "max_over_time_5m"
|
||
kind = "tql"
|
||
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') max_over_time(promql_range_boundary{host=~'host.*'}[5m])"
|
||
warmup = 3
|
||
iterations = 9
|
||
|
||
[scenario.queries.thresholds]
|
||
max_candidate_latency_regression_pct = 20
|
||
|
||
[[scenario.queries]]
|
||
name = "min_over_time_1h"
|
||
kind = "tql"
|
||
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') min_over_time(promql_range_boundary{host=~'host.*'}[1h])"
|
||
warmup = 3
|
||
iterations = 9
|
||
|
||
[scenario.queries.thresholds]
|
||
max_candidate_latency_regression_pct = 20
|
||
|
||
[[scenario.queries]]
|
||
name = "max_over_time_1h"
|
||
kind = "tql"
|
||
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '15s') max_over_time(promql_range_boundary{host=~'host.*'}[1h])"
|
||
warmup = 3
|
||
iterations = 9
|
||
|
||
[scenario.queries.thresholds]
|
||
max_candidate_latency_regression_pct = 20
|
||
|
||
# A ten-minute step keeps the one-hour window on the reuse path without the
|
||
# single-sample step the two cases above use.
|
||
[[scenario.queries]]
|
||
name = "min_over_time_1h_step10m"
|
||
kind = "tql"
|
||
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '10m') min_over_time(promql_range_boundary{host=~'host.*'}[1h])"
|
||
warmup = 3
|
||
iterations = 9
|
||
|
||
[scenario.queries.thresholds]
|
||
max_candidate_latency_regression_pct = 20
|
||
|
||
[[scenario.queries]]
|
||
name = "max_over_time_1h_step10m"
|
||
kind = "tql"
|
||
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '10m') max_over_time(promql_range_boundary{host=~'host.*'}[1h])"
|
||
warmup = 3
|
||
iterations = 9
|
||
|
||
[scenario.queries.thresholds]
|
||
max_candidate_latency_regression_pct = 20
|
||
|
||
# A bounded control with a one-hour evaluation step avoids overlapping windows.
|
||
[[scenario.queries]]
|
||
name = "min_over_time_1h_nonoverlap_control"
|
||
kind = "tql"
|
||
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '1h') min_over_time(promql_range_boundary{host=~'host.*'}[1h])"
|
||
warmup = 3
|
||
iterations = 9
|
||
|
||
[scenario.queries.thresholds]
|
||
max_candidate_latency_regression_pct = 20
|
||
|
||
[[scenario.queries]]
|
||
name = "max_over_time_1h_nonoverlap_control"
|
||
kind = "tql"
|
||
query = "TQL ANALYZE VERBOSE (1704070800, 1704078000, '1h') max_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
|