Repository navigation
feat(planner): --planner saved for SQL - #866
milindsrivastava1997 merged 2 commits into
Conversation
An e2e query is now written in `promql` or `sql`; --query-language picks which ones are planned. In SQL mode each `records` metric is a table (labels as metadata columns, values as value columns, time column `ts`), and a SQL query is one leaf that may read several RQEs, all of which must be served by one deployment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
milindsrivastava1997
left a comment
There was a problem hiding this comment.
Code review: no definite crash/wrong-output bugs; robustness, test-gap and cleanup notes inline.
| #[derive(Deserialize)] | ||
| struct E2eMetric { | ||
| #[serde(rename = "type")] | ||
| kind: String, |
There was a problem hiding this comment.
E2eMetric.kind (serde type) is now required with no #[serde(default)], so PromQL --planner saved refuses any e2e table whose metrics lack type — previously only labels was needed. Old tables / exporters that omit type now fail with "missing field type". Consider defaulting kind and only treating an explicit records as a SQL table.
There was a problem hiding this comment.
Won't fix. Every sketch-bench e2e table writes type (gauge, counter or records) on every metric. Requiring it fails loudly; defaulting would quietly plan a metric with a missing or misspelled type as PromQL.
| } | ||
|
|
||
| /// The time column of the `records` tables the e2e SQL queries read. | ||
| const SQL_TIME_COLUMN: &str = "ts"; |
There was a problem hiding this comment.
SQL time column is hard-coded to ts, while asap-tools takes it from the dataset's timestamp_col (experiment_utils/config.py:1011). A records dataset with a different timestamp column gets a streaming config naming a nonexistent column, and nothing flags it at plan time.
There was a problem hiding this comment.
Won't fix. The time column is fixed by the queries themselves: sketch-bench's sql strings say ts (WHERE ts BETWEEN ...), so a dataset with another timestamp column wouldn't match them whatever the planner wrote.
| ensure!( | ||
| other == rqe, | ||
| "leaf {:?} every {} ms reads both {other:?} and {rqe:?}", | ||
| other == rqes, |
There was a problem hiding this comment.
Duplicate-leaf check compares RQE lists as ordered Vecs, but for a SQL leaf the list is a set. The same SQL query listed twice at one interval_secs with rqes ["users_1pct","users_5pct"] vs ["users_5pct","users_1pct"] fails with a spurious "reads both ...". Sort/dedupe before comparing.
There was a problem hiding this comment.
Fixed in 7459ec4: a SQL leaf's RQEs are sorted before comparing. Test: reads_a_repeated_sql_query_whose_rqes_are_reordered.
| name: name.clone(), | ||
| time_column: SQL_TIME_COLUMN.to_string(), | ||
| value_columns: m.values.keys().cloned().collect(), | ||
| metadata_columns: m.labels.iter().map(|l| l.name.clone()).collect(), |
There was a problem hiding this comment.
A records metric becomes a SQL table without the non-empty metadata_columns check MilpConfig::hints does (TableWithoutMetadataColumns) — the saved path never calls hints(). labels: [] passes here but --planner milp would refuse it; values: {} gives empty value_columns that only fails later with a confusing "not a value column" error.
There was a problem hiding this comment.
Fixed in 7459ec4: saved_plan_output now calls MilpConfig::hints(), so a records table without labels gets the same TableWithoutMetadataColumns refusal as --planner milp, and empty values is refused by name. Test: refuses_a_records_metric_without_labels_or_values.
| sql = e2e_sql_config(&selected, &e2e.metrics); | ||
| MilpConfig::Sql(&sql) | ||
| } | ||
| _ => { |
There was a problem hiding this comment.
_ => routes every non-sql language to the PromQL arm, relying on validation in main.rs and E2eQuery::text. saved_plan_output is pub, so reordering that validation would silently plan a third language as PromQL. Match QueryLanguage::promql explicitly and fail otherwise.
There was a problem hiding this comment.
Fixed in 7459ec4: saved_plan_output matches promql and sql explicitly and refuses any other language. Test: refuses_a_language_other_than_promql_or_sql.
| "--planner saved supports only --query-language promql" | ||
| matches!( | ||
| args.query_language, | ||
| QueryLanguage::promql | QueryLanguage::sql |
There was a problem hiding this comment.
Language is validated twice: this matches!(promql | sql) guard duplicates the other => bail! in E2eQuery::text (which also reruns per query). Two error strings for one condition; adding a language means editing both.
There was a problem hiding this comment.
Fixed in 7459ec4: the language is checked once, at the top of saved_plan_output. The main.rs guard is gone, and E2eQuery::text only picks the field.
| } | ||
|
|
||
| #[test] | ||
| fn refuses_a_sql_query_whose_rqes_are_on_two_deployments() { |
There was a problem hiding this comment.
Test gap: multi-RQE SQL path is only tested with RQEs on two different deployments. Nothing covers two RQEs on the same deployment with different sketch params / deployment_grouping (should refuse with "different sketches"). If the reads loop regressed to checking only the first RQE per leaf, no test would fail.
There was a problem hiding this comment.
Added in 7459ec4: refuses_a_sql_query_whose_rqes_need_different_sketches (same deployment, different lg_k). Checking only a query's first RQE fails it.
| selected: &[(&E2eQuery, &str)], | ||
| metrics: &BTreeMap<String, E2eMetric>, | ||
| ) -> anyhow::Result<ControllerConfig> { | ||
| let config = json!({ |
There was a problem hiding this comment.
PromQL config is built via json! + deserialize round-trip while the SQL config is built as a struct directly, so validation differs (e.g. PromQL gets QueryGroup's positive repetition_delay_ms check; SQL relies on later check_repeat_interval). Building both the same way would keep validation consistent.
There was a problem hiding this comment.
Won't fix. Both paths refuse a zero interval (PromQL in QueryGroup's deserializer, SQL in check_repeat_interval); only the error message differs.
- A plan's window_secs/slide_secs may be fractional (an instant query's window is one scrape, 0.1 s at 10 samples/s); they are converted to whole ms, and a fraction of a ms is refused. - A SQL query's RQEs are compared as a set. - records metrics go through --planner milp's table checks; empty values are refused. - The query language is checked once, in saved_plan_output. - Test: one SQL query's RQEs needing different sketches is refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes #856.
Summary
--planner saved --query-language sqlwrites a sketch-bench plan for the multigroup/all tables' SQL queries, as #828 does for PromQL. The e2e eval (#862) runs the all workload's multigroup queries this way.e2equery is written inpromqlorsql(exactly one; both or neither is an error).--query-languagepicks which are planned (promql or sql only, checked once insaved_plan_output), and a table with none in that language is an error. This also keeps PromQL--planner savedworking onalltables, whose multigroup entries have nopromql.recordsmetric becomes a table (labels →metadata_columns, values →value_columns, time columnts), with--planner milp's table checks and emptyvaluesrefused. Thesqlstrings, written by sketch-bench, are used as written, one query group perinterval_secs.window_secs/slide_secsmay be fractional. Since sketch-bench#215 an instant query's window is one scrape (0.1 s at 10 samples/s), which the old whole-second parse refused for PromQL too. They are converted to whole ms; a fraction of a ms is refused.Testing
cargo test -p asap_planner: 24saved_plantests, 12 new: SQL over arecordstable with an HLL roll-up and a DDSketch; the PromQL run of a mixed table; a SQL query's RQEs on two deployments, or needing different sketches on one (both refused); reordered RQEs (accepted); a pick from another table, no query in the language, a query in both languages, a records metric without labels or values, an unsupported language (all refused); a 0.1 s window (100 ms) and a 0.5 ms one (refused). Removing the sort, or checking only a query's first RQE, fails the matching test.Real plans: sketch-bench
main(Sketchlib CountMin Support #215, 198 migrate asap h2o benchmark pipeline to asapquery #216) exported at--samples-per-sec 10and re-planned locally (autosketch_vs_asap --runs 1,extract_autosketch_plans.py), then everyasapplan converted with--data-ingestion-interval-ms 100:Before sketch-bench#216, SQL converted 3 of 16: templates 14, 15 and 17 put one query's RQEs on two deployments.
🤖 Generated with Claude Code