Repository navigation
feat(asap-tools): exact materialized-view arm for sketch-bench e2e queries - #845
Conversation
…nerated2 workloads Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e SQL configs Adds a client_options.simulated_start_time so templated SQL can be evaluated against bulk-loaded data with fixed timestamps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… configs
sketch_bench_workloads.py reads a sketch-bench table's workloads[0].e2e block
and writes, per table, the metric tables' init SQL, one SQL file per query
interval and a baseline experiment_run_clickhouse.py config whose query
groups repeat every interval.
Queries keep generate_workloads.py's Prometheus 2.53 semantics (range
vectors read (T - d, T], extrapolatedRate for rate/increase,
quantileExactInclusive) and add the classic template shapes: topk over
sum by, sum/count/quantile by over instant vectors (latest sample per series
in (T - 1s, T]) and rates, and the ratio of two quantile_over_time. Eval
time is {{ end_time }} floored to the interval. Unsupported shapes and
structured (non-PromQL) entries raise.
generate_workloads.extrapolated_rate now builds on rate_from_samples, which
the new module reuses; its SQL output is unchanged.
The test compares every generated statement in clickhouse-local against a
Python implementation of the Prometheus semantics at several eval times,
covering empty windows and every extrapolation branch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
… SQL Experiments run Prometheus 2.53.2 (installation/prometheus/install.sh), whose range selectors and instant lookback are closed, [T - d, T]; left-open windows arrived in 3.0. Aligned eval times put a sample on the left boundary, so the SQL now reads ts >= T - d. The Python reference selects [T - d, T] as well. generate_workloads.py's generated2 SQL is unchanged; only the shared rate_from_samples docstring no longer names a window shape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
Structured e2e entries {table, value, capability, grouping, quantile,
window_secs, interval_secs} become pooled queries over a records table.
Cardinality is uniqExact of the value field per group, and quantile is
quantileExactInclusive over all of a group's samples. Both read the same
closed [T - W, T] window, with T floored to the interval as the PromQL
queries are. init.sql now creates records tables, with one Float64 column
per value field. Each table's structured queries are also written in
ASAPQuery's SQL frontend dialect (COUNT(DISTINCT col), QUANTILE(q, col),
BETWEEN DATEADD(s, -W, NOW()) AND NOW()) to asapquery.sql.
The clickhouse-local test adds a skewed records dataset in which endpoint is
a child of service. It checks template 16's CUBE over (region, service,
endpoint) and several cardinality groupings against a Python reference.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
The sketch-bench ClickHouse configs now run baseline_mv beside baseline: exact mergeable states in materialized views, no sketches. There is one AggregatingMergeTree view per (metric, value, statistic, interval T). Its key is the T-second bucket plus the union of the queries' groupings and their child_of parents (every label for per-series statistics). The states are sumState, countState, quantilesExactInclusiveState over the queries' levels, argMaxState per series for instant vectors, and per-series first/last sample, min/max ts and count for rate/increase. Those feed the same extrapolatedRate SQL. Bucket b holds (b - T, b], and an `edge` flag keeps samples at exactly b apart. A closed window [E - W, E] therefore merges the buckets in (E - W, E] plus the edge rows of bucket E - W. A query whose window is not a multiple of T raises, so instant vectors are only served at T = 1 s. The init SQL creates the views after their tables and before any data arrives. The test runs every MV query next to its raw SQL in clickhouse-local. Results match exactly, except sums, which agree within 1e-9 relative. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
Multigroup (structured) queries involve no Prometheus, so they now read
ASAPQuery's left-open window (E - W, E] instead of the closed [E - W, E]
that classic PromQL keeps to match Prometheus 2.53. ASAPQuery's SQL
frontend and precompute windows are (start, end], so ClickHouse and
ASAPQuery compute over the same samples. Query gets a `left_open` flag that
structured entries set; the raw SQL uses `ts > E - W`.
The ASAPQuery-dialect file ends its window at the floored eval time instead
of NOW(). The frontend cannot floor a literal, so the client's
{{ end_datetime }} is moved back by {{ end_time % T }} inside DATEADD:
ts BETWEEN DATEADD(s, -{{ end_time % T + W }}, '{{ end_datetime }}')
AND DATEADD(s, -{{ end_time % T }}, '{{ end_datetime }}')
which the frontend parses to (start, duration) = (E - W, W).
The Python reference for multigroup now pools records in (t - w, t].
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
Each sketch-bench table now gets one config per arm. The baseline config (init.sql) creates only the tables. The baseline_mv config (init_mv.sql) creates the tables and their views. Both files drop the views first, so none left by an earlier baseline_mv run keeps feeding on the recreated tables. Both configs list both arms' SQL files, so saved results label every query by its baseline text. experiment_run_clickhouse.py monitored ingest under the baseline mode's client config and output dir. A baseline_mv-only run has neither, so ingest now uses baseline when the run has it, and otherwise the run's first mode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
A multigroup query's window is now ASAPQuery's (E - W, E], so its baseline_mv SQL merges just the buckets in (E - W, E] and skips the edge rows that hold a closed window's left boundary. Classic PromQL queries keep the closed window and its edge rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
…se configs
The SQL floors {{ end_time }} to T, but the current query client sleeps T
after each repetition's queries finish, so its eval times drift by the
query latency and the arms answer different windows. Each query group now
sets `align_to_interval: true` and `query_time_offset: 10` (PR #841's
default). With #841's client, repetition k is evaluated at
floor((start - O) / T) * T + k * T and sent O seconds later.
The current client ignores `align_to_interval` (unknown client options
become unused attributes) and shifts its eval time back by the offset. The
module docstring records the drift until #841's client lands.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
…a query convert() planned views for every query, and plan_views raises when a window is not a multiple of the interval (e.g. an instant vector, which reads [T - 1s, T], at T > 1 s). So such a table failed to convert even for the baseline arm, which needs no views. Now a table with such a query gets only the baseline arm: init.sql, the raw SQL and the baseline config, plus a note on stderr naming the queries. Dropping just those queries from the MV arm would shift its query indices against the baseline SQL that labels both arms' results. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
…w SQL ClickHouse 26.10 turns `ts > <integer>` on a DateTime64 key column into `ts in [<integer> + 1, +Inf)` for index pruning, so the left-open multigroup SQL dropped the samples in the window's first second from the result (A6: raw vs MV relative differences of 1e-6 to 2.6e-3 on 49 of 52 queries). The raw SQL now compares ts with toDateTime64(<bound>, 3) on both ends, for classic and multigroup queries. A regression test runs a left-open and a closed query in clickhouse-local over a table with 8-row granules and samples every 7 ms around the window start. Without the cast it reproduces the loss (8435 of 8571 rows). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
…MV SQL The views' `bucket` column is a DateTime (whole seconds), so integer bounds already filter it exactly. They are now toDateTime(<bound>) anyway, to match the column type, as the raw SQL's ts bounds now are. The key-pruning regression test, merged from feat/e2e-sql-from-table, now passes the views to load_script (this branch's signature). It also runs each query's MV SQL over views with 1-row granules and checks raw and MV against the expected counts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
Brings in #837 (this branch's base, squash-merged) and #828. The one conflict, generate_workloads.py (add/add), keeps this branch's version: main's copy is #837's original commit, and this branch later moved extrapolatedRate into rate_from_samples and closed its windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
sketch-bench's exporter now writes each structured e2e entry's ASAPQuery
SQL (`sql`, e.g. `... WHERE ts BETWEEN DATEADD(s, -300, NOW()) AND NOW()
...`), so it is the single ASAPQuery SQL renderer. asapquery.sql now takes
that SQL as written and only replaces NOW() with the floored eval time; this
module's own dialect renderer is gone. The ClickHouse raw SQL still comes
from the structured fields.
The frontend's DATEADD takes only NOW(), a literal or a CAST as its base, so
NOW() becomes a datetime literal floored in the client's Jinja template:
{{ end_datetime }} (UTC) with its seconds floored for T dividing 60, or its
minutes for 60 | T | 3600. Other intervals raise.
All 936 distinct structured queries in sketch-bench cd51be9's tables
(T = 60 s and 300 s), rendered through the client's QueryTemplate at three
end times, parse in ASAPQuery's SQL frontend to (start, duration) =
(floor(E) - W, W) and pass its matcher.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
Brings in origin/main (#837, #828) and the switch to the table's ASAPQuery SQL. Conflicts resolved by keeping both sides: the module docstring keeps this branch's per-arm outputs with the new asapquery.sql line; the tests keep PlanViewsTest and FlooredEndDatetimeTest; experiment_run_clickhouse and its test import both _ingest_mode and #828's prebuilt-output helpers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae
Code reviewStacks on #844. Core MV logic checks out (closed and left-open windows,
Tests: missing a regression test that baseline |
# Conflicts: # asap-tools/experiments/datasets/fake_metric/sketch_bench_workloads.py # asap-tools/experiments/tests/test_sketch_bench_workloads.py
- Keep every view in one database that each init drops, so views another workload left on the metric tables stop firing on inserts. - Clear the table's generated files before writing, so a skipped baseline_mv arm or a removed interval leaves no stale SQL or config. - Render views with no label keys (empty grouping) as valid DDL. - Raise ValueError when child_of names an unknown label. - Share one mv_servable() check between convert and plan_views. - Docstring: E for the eval time, T for the interval. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Addressed in 18a35eb. The branch now has
Also, from #844's review: a view whose queries all have empty grouping (no label keys) rendered Each fix was checked by reverting it and seeing its test fail. Results: 22 tests pass, plus 🤖 Generated with Claude Code |
A re-run that failed validation or rendering had already deleted the earlier SQL files, leaving its config pointing at missing files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stacked on the sketch-bench SQL PR (base branch
feat/e2e-sql-from-table).Summary
Adds the
baseline_mvarm: ClickHouse with materialized views and no sketches, only exact, mergeable states. Each view's bucket width is the query's repeat interval T.One view per (table, value column, statistic, T), an
AggregatingMergeTreekeyed by bucket and the union of the groupings that read it, plus theirchild_ofparents. Per-series and instant queries keep every label. Coarser groupingsGROUP BYa subset and merge (roll-up). For example, template 16's whole CUBE reads one(region, service, endpoint)view.States:
sumState,countStatequantilesExactInclusiveStateover the view's levelsuniqExactStateargMaxState(value, ts)extrapolatedRateORDER BY … LIMIT kWindows: bucket b holds
(b−T, b], and anedgeflag keeps samples at exactly b apart. Classic's closed[E−W, E]merges the buckets in(E−W, E]plus bucketE−W's edge rows. Multigroup's left-open(E−W, E]merges just the buckets. A window that isn't a multiple of T raises. Instant queries need T = 1 s.Per-arm init SQL, so the baseline arm never pays for the views:
init.sql(tables only) andinit_mv.sql(tables, then views; the views must exist before data arrives). Both drop old views first.experiment_run_clickhouse.pynow uses the run's first mode for ingest monitoring whenbaselineisn't in the run, so abaseline_mv-only run works.Testing
toStartOfIntervalbuckets, makes 84–132 subtests fail.init_mv.sqlexecute for classic (4 views), multigroup (3 views:http_user_id_cardinality_60s,http_latency_quantile_60s,flows_src_ip_cardinality_60s) and all-metrics8 (56 views).tests/test_experiment_run_clickhouse.py. black/flake8 clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01AnJ7LocWxguh3q4QWkCYae