Repository navigation
rqe-optimizer: let mergeable finer-group deployments serve coarser RAQEs - #190
Conversation
4bc0aa3 to
c20748e
Compare
c20748e to
f5297d3
Compare
| | Ingest, per active `D` | `lambda × a_D × c_ins` | `card(G) × m × a_D` (open windows) | | ||
| | Merge, per RAQE | `card(G) × (n_i − 1) × c_mrg / T_i` | `card(G) × m` (one accumulator per group); 0 when `n_i = 1` | | ||
| | Query, per RAQE | `card(G) × c_qry / T_i` | `card(G) ×` output bytes | | ||
| | Storage, per active `D` | 0 | `card(G) × m × ((max_i S_i − x) / y + 1)` (closed windows) | |
There was a problem hiding this comment.
@zzylol These are part of the old cost model. We should check these against the new cost model and merge tables?
There was a problem hiding this comment.
Yes we should update and merge
There was a problem hiding this comment.
Merged in 332ed6a. Cost by use §3 is now the one cost table: ingest, compaction, query job (merge, then estimate) and storage, each with CPU per event, mean vCPUs, memory, and how long the memory is held. It is written in terms of I_d (instances per window, card(G_d) or 1 for a shared sketch) and I_r (states a query ends with, card(G_r) or 1), summed over parts (sketch + key tracker).
The old snapshot table is gone. The snapshot-AUC section (milp::minimize, still supported) now just lists how it accounts §3's table differently: k_D = 1 so no compaction; query memory held all the time; per-RAQE serial latency against latency_sla_ms instead of batch latency. The DD m definition stayed there and now distinguishes card(G_d) (window) from card(G_r) (merge accumulator).
| Each row of the cost table (from | ||
| [`scripts/study_saturation.py --phase optimizer-cost`](../scripts/study_saturation.py)) | ||
| is measured on one instance, at whatever key count the benchmark fed it | ||
| (`measured_keys`). A key is one distinct entry an instance stores exactly, | ||
| such as one group's running sum in an exact accumulator. Turning a row into a | ||
| deployment's cost needs two properties of the family: |
There was a problem hiding this comment.
@zzylol Did you ever review this section? This was present earlier. I believe this should help with Hydra's cost model
There was a problem hiding this comment.
Reviewed. The shape/law part is right, and it is in fact the Hydra skeleton: Shared + Fixed is I_d = 1. Changes in 332ed6a:
- the section uses
I_d/card(G_d)(andcard(G_r)for probes), matching §3; - the side-by-side table no longer repeats every phase (its latency row was the old serial
G·(c_qry + (n−1)·c_mrg)with no compaction, and its merge rows ignored roll-ups). It now gives what each family puts into §3's table:m/c_mrgbasis,I_d,I_r, probes, key tracker, whether it rolls up; - the HydraKLL column is marked as in the code but not in the evaluation, pointing to the Hydra section, since the
subpopulationsceiling here conflicts with the Hydra section saying one ceiling is insufficient.
One thing I didn't change: KLL is still treated as Fixed at the 1e6-values size. Worth confirming that still matches the #174 study.
There was a problem hiding this comment.
Checked the KLL point myself (3060337). The old text was wrong in both directions:
- The cost table's KLL
mis not "the size measured at 1e6 values". It is sketch-bench's nominal footprintkll_footprint = 4·k·sizeof(T): 1600 B atk = 50, 6400 B atk = 200. approxbench reports the same bytes at 1e3, 1e6 and 1e8 items (run on clnode109 with the final030 binary). So "Fixed" is exactly what the model gets, and nothing grows withn. - It is also not what
asap_sketchlib::KLLallocates: the lib preallocates max capacity over 61 levels (aqpbm-planeval'skll_max_capacityreplica), which is 4640 B atk = 50(2.9×), 8032 B atk = 200(1.25×) and 22240 B atk = 800(0.87×).
The doc now says this. Whether the cost table should switch KLL's footprint to the allocated capacity is a sketch-bench change outside this PR; it would raise small-k KLL memory about 3×.
| | Phase | CPU | Memory | | ||
| | --- | --- | --- | | ||
| | Ingest | `lambda * a * c_ins,h` | `a * m_h` | | ||
| | Merge a RAQE | `(L/x - 1) * c_mrg,h / T` | `m_h` when `L/x > 1` | | ||
| | Query a RAQE | `C_r * c_probe,h(G_r) / T` | `C_r * output_bytes` | | ||
| | Storage | `0` | `ceil((max L - x)/y + 1) * m_h` | | ||
|
|
There was a problem hiding this comment.
@milindsrivastava1997 : This should be merged with the above tables. We should just have one cost model table with all phases and CPU and memory costs both.
There was a problem hiding this comment.
Merged in 332ed6a: the proposed Hydra model is now §3's table with I_d = I_r = 1 and c_qry → c_probe,h(G_r), shown as an instance rather than a separate formula. Differences from the old Hydra table:
- added compaction (
k_D > 1workers producek_Dpartial grids:(k_D − 1)·c_mrg,h, holdingk_D·m_h) andk_Dcopies of open windows at ingest; - symbols follow the doc: lookback
S_i(notL, which is the SLA),card(G_r)/card(G_d)(notC_r/C_d), noceilon storage; - the key tracker is a part, so its cost adds in every phase, as the text already required.
The anchor#proposed-hydra-cost-modelis unchanged, so the link from the candidates doc still works.
|
Also fixed in 332ed6a: the cost-by-use §3/§4 formulas were out of sync with this PR's roll-ups. The code already prices them (
Ingest, compaction and storage stay at 🤖 Generated with Claude Code |
|
Two review rounds (code, docs, Hydra), fixes pushed in 81c46b7 (code) and 00fd9a9 (docs). Code
Docs
Open design questions (not changed in code)
🤖 Generated with Claude Code |
|
Decision on open question 3 (from @zzylol): the AutoSketch-vs-ASAP eval keeps roll-ups on. ASAP may serve an ungrouped RQE from a grouped deployment on the same stream; that sharing is part of what the planner offers. The results on main (#138) were planned before roll-ups, so they will be rerun once this PR lands. 🤖 Generated with Claude Code |
Keep #190 to roll-ups. docs/rqe_optimizer_hydra.md and its links move to a separate PR stacked on this one; the docs here only say Hydra is future work and doesn't roll up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis
A deployment grouped by G_d now serves a RAQE grouped by any subset G_r when its family merges across groups (exact sum/min/max, HLL, UnivMon cardinality, KLL, DD). The read is priced at G_r: card(G_d)·L/x − card(G_r) merges, with card(G_r) accumulators, probes and output. Accuracy reads G_r's shape and items, and KLL's merge curve at the average fan-out times L/x. Fine candidates take windows from coarser RAQEs. validate_facts rejects a label set with more groups than a superset. Closes #187 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge the cost-by-use CPU and memory tables into one table per phase (ingest, compaction, query job, storage) in terms of I_d and I_r, so roll-ups (G_r ⊂ G_d) are priced as the code prices them: ℓ = card(G_r)·c_qry + (I_d·n − I_r)·c_mrg, accumulators at G_r. The snapshot-AUC section now lists only how it differs (k = 1, query memory always held, per-RAQE serial latency) instead of repeating the table; the shape/size-law side-by-side gives each family's I_d, I_r and tracker; the proposed Hydra cost model is §3's table with I_d = I_r = 1, adding compaction and using the doc's symbols. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis
…at 1e6 The cost table's KLL m is kll_footprint = 4·k·sizeof(T), independent of the item count (1600 B at k = 50 for 1e3, 1e6 and 1e8 items), so it is not "the size measured at 1e6 values" and does not grow with n. Say so, and how it compares with asap_sketchlib's preallocated capacity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis
Dominance pruning compared ingest, query latency, merge memory and storage. Cost by use also bills compaction (per window in every chain, per slide in CPU and byte-seconds) and query output memory, which differs between top-k configs answering different k. A direct RAQE never merges at query time, so a candidate with cheaper ingest but costlier merges could prune one that is cheaper by use. Compare those too; the new test fails without it. Also: workload_scenarios no longer says groupings never share; the exact-increase exclusion states its real (conservative) reason; merged_instance_count notes roll-ups; a circular doc comment; stale §N citations now name the docs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis
- candidates: eligibility allows subset groupings for families that merge across groups; generation shows the roll-up RAQE sets; accuracy is read at G_r with KLL's fan-out merge count, and past the measured merge counts the guarantee applies, floored by the last measurement; pruning lists every compared cost; the MILP lives only in the cost doc; one roll-up section. - cost model: top-k output and query CPU scale with k; k_D sums parts; mergeable_across_groups listed; KLL footprint points at #191/#192; Hydra moved out. - v1 and TODO: phases include compaction and the query job; TODOs match cost by use. - rqe_optimizer_hydra.md (new): Hydra in sketch-bench, accuracy bounds per combination (CMS, CountSketch, HLL, UnivMon, KLL) checked against the paper's Theorem 2 and the library (correlated inner seeds, fixed column hash, few rows), cost model, candidates and MILP edges, sketch-bench requirements and open decisions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis
…ason UnivMon's counters add exactly on merge, but each level's heavy-hitter heap is rebuilt from the union of the two heaps, so a key heavy only in the union is lost: a merged (or rolled-up) answer now reads merge curves, as KLL's does. exact-increase's accumulator merge joins two pieces of one counter in time, so merging two groups' counters is wrong; say so instead of "unconfirmed". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis
validate_facts compared every pair of label sets in a metric's cardinality map, so an inconsistent entry no RAQE groups by rejected the whole workload. The roll-up merge count only reads RAQE groupings and the full label set, so check X ⊂ Y among those. Docs: why rate/increase, top-k and Hydra don't roll up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis
Every Hydra measurement predates the admission requirements, so they are all rerun; hydra-kll goes in that run with the other four variants. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis
Keep #190 to roll-ups. docs/rqe_optimizer_hydra.md and its links move to a separate PR stacked on this one; the docs here only say Hydra is future work and doesn't roll up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis
Tests that fail without their rule: - a DDSketch roll-up sizes its accumulator at the coarse grouping; - validate_facts rejects a RAQE grouping with more groups than series; - pruning drops a heap whose larger answered k only adds output memory. Docs: the KLL footprint paragraph now that #192 is in; univmon-cardinality reads merge curves like KLL (and has no guarantee past them); the TODO's done list covers roll-ups and the pruning costs; "temporal pre-merging"; enumerate.rs names the MILP variables z and u as the docs do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis
ad069f1 to
6b1637a
Compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis
…n (§6.3) (#203) * polars: tag Hydra baseline subset keys by column (#74) The polars exact baselines keyed a label subset by its `;`-joined values, asap_sketchlib 0.2.2's subkey. 0.3.0's Hydra tags each value with its column (`label0:a;label1:b`), so on label columns that share a value domain the baseline merged a key1 group with the same key2 group and scored nonzero against itself. subset_key now builds the 0.3.0 key over hydra_shared's `label{i}` schema (escaping `\`, `:` and `;`), empty labels keep their column, and every lookup goes through it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * eval: multi-grouping anomaly-detection templates (http, flows) The generator gets a metric-level ordered label schema (labels with cardinality, child_of fan-out and skew; per-value data shape) and per-RQE `grouping` and `covers_share`. The 10 templates are now the "classic" set, byte-for-byte the committed tables; "all" adds templates 11, 12, 14, 15, 16, 17 on `http` and `flows`, whose streams each carry several groupings so roll-ups (and later Hydra) can share. The plan runs both sets over the grid. The runner builds each schema stream's MetricFacts from the schema (labels = schema + x; cardinality per used grouping and the full set), takes Raqe.grouping_labels from the RQE's grouping, maps the `cardinality` capability to HLL, and reports covers_share and roll-up deployment groupings in each choice. Classic output is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * polars: write subset key tags without a temporary String; fix test comment Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * hydra: interleaved merge split; merge exactness and bound tests `--merge-split contiguous|interleaved` (default contiguous, unchanged) picks how merge runs split the stream into shards. Interleaved reorders the stream so the existing contiguous `partition` deals it round-robin by record, at the contiguous split's shard sizes, so every row's folds take it unchanged. Tests: hydra-cms, hydra-cs and hydra-hll grids folded from 4 and 7 shards, under both splits, answer every probed subpopulation exactly as the single stream; and every scored group on a fixed 20k-record stream lands inside its hydra doc §2.2 bound (CMS also never under). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * datagen: child_of columns for hierarchical labels A `child_of: <index>` column with `fan_out: f` nests under an earlier string column: its value is the parent row's value, a `.`, and a child index in [0, f) drawn from the column's own distribution. Bad indices, a fan_out that disagrees with the domain, and fields that would not mean what they say on a child are refused before drawing. Adds configs/datagen/hydra_hier.yaml (region 4 -> service 25 -> endpoint 25, status 4, user_id value) for the hydra rows, docs, and tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * hydra: score any target grouping; per-group error output Every hydra-* row (lib and polars, all five variants and hydra-univmon's four statistics) takes `--group-columns J,K,...` (default `0`, today's behavior) in place of the hardcoded SCORED_LABEL_COLUMN. A subpopulation probe is now positioned (`Vec<Option<String>>`, Some at the grouped columns), so `hydra_shared::query` asks any non-empty subset of the key columns and the polars baselines look up `subset_key` with that subset's mask (`group_key`, generalizing `prefix_key`). Each grouped comparator reports its per-group error in its own metric (relative error; mean rank error for hydra-kll) and summarises it as err_mean, err_p50, err_p90, err_max, groups_scored, plus schema_width, records and fanned_mass. `--per-group-out PATH` writes the rows as `group_key,n_q,error`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * datagen: whole uniform bounds for child_of; 2- and 3-label hierarchy cuts A uniform draw is continuous, so `uniform{0.0, 2.9}` with fan_out 2 drew child indices 0, 1 and 2; child_of now requires whole uniform bounds. hydra_hier_d2.yaml and hydra_hier_d3.yaml cut hydra_hier.yaml to its first 2 and 3 labels for the Hydra study's schema-width sweep. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * eval: skewed http labels, per-RQE group coverage, fresh-checkout test Review of #196. http's labels are now Zipf-skewed (region 0.5, service 1.1, endpoint 1.1 within its service, status 2.0) and the flows DDoS burst targets a tail subnet, so covers_share selects a few groups. The generator computes each group's share (product of its labels' shares) and, per RQE, the covered groups (share >= tau, plus always the largest) and the smallest covered group's share and items per window. The runner reads a covered RQE's accuracy (ASAP and AutoSketch) at that group's items instead of the mean group's; covers_share = null is unchanged. The schema test reads an inline HLL curve fixture instead of the gitignored study curves. The synthetic plot keys runs by template set too, and both plot scripts title classic "mixed" and all "mixed + multi-grouping". README: the coverage rule, the N the runner uses, and which schema fields are descriptive. label_set.keys_per_window is commented as the universe K. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * eval: read null-coverage RQEs at their smallest group; template 17 at {service, endpoint} - covers_share = null covers every group, so the runner reads its accuracy at the smallest group (min_covered_share), like a tau RQE's smallest covered group. Classic RQEs (no covers_share) are unchanged. - Template 17 (negative control) groups by {service, endpoint}: its smallest group holds 4.5e4 items in 5m, on the curves. - The generator asserts every schema RQE's covered_min_N >= 1e3 (drops the vacuous `assert covered`); a test checks the old full-label-set template fails it. - Covered facts go through validate_facts. - README: null semantics, template 17, and the numbers' provenance (fresh `all` table, committed saturation inputs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * datagen: the hydra_hier usage example passes --dtype Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * hydra: list every group in --per-group-out; escape keys; refuse no accuracy - Group keys escape `\`, `:` and `;` as polars_shared::subset_key does. - Groups that cannot be scored (zero true statistic, e.g. zero entropy) are written with an empty `error`, so n_q sums to the stream's records. - --per-group-out with no accuracy measurement is refused instead of writing an empty file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * hydra: KLL CDF and UnivMon sum rows; KLL cell merge buffer - hydra-kll-cdf (lib, polars): asks the KLL grid HydraQuery::Cdf(x) at each group's own values at phi = 0.01..0.99, scored by absolute CDF error (SubpopCdfErrorGT, comparator subpop-cdf-error). The polars baseline is the exact share at or below x over the same sorted groups. - hydra-univmon-sum (lib, polars): each record inserted with count = value, so the UnivMon grid's L1 is the group's sum, scored by relative error against exact per-group sums (SubpopSumGT, comparator subpop-sum). Integer value dtypes only; f64/str and values outside [0, i32::MAX] are refused by name. - memory_hydra_kll counts each cell as kll_lib_bytes does, merge buffer of cell_k items included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * hydra-kll: the footprint's merge buffer is the steady state; tie tests The grid clones its cells from one template, and a cloned Vec keeps no capacity, so a cell's merge buffer is empty at build and grows to about cell_k items at its first compaction. Say the footprint counts it at that bound. The CDF test now asks a group of ties (at or below) and a mixed-tie group on a grid wide enough that it shares no cell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * hydra: record merge_split; review fixes - `merge_split` (contiguous|interleaved) is a record field next to `merge_shards` (BenchSection and MergeMetrics; schema regenerated). Merge records stamp it; a record without it split contiguously, so existing results parse, and flatten refuses two different splits in one row as it does two shard counts. - `MergeSplit` moves to aqpbm-core (it is a record type) and is parsed by clap directly instead of matching strings. - The interleaved copy of the stream is built only when a merge runs. - README states the round-robin exactly (contiguous shard sizes, so a short last shard shifts the pattern); MergeMetrics docs name the split. - hydra-cs bound test: note its margin (passes from beta ~4.8). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * study: --phase hydra on the eval's datasets; Hydra cost rows configs/datagen/hydra_http.yaml, hydra_http_latency.yaml and hydra_flows.yaml follow the §6.3 eval's http and flows schemas (labels in schema order, cardinalities, child_of, skews; user_id Zipf 0.8, latency Pareto a = 2, src_ip uniform). The flows DDoS burst is not generated: datagen draws each column independently. --phase hydra runs HYDRA_SWEEPS: the eval's variants on those datasets (every grouping, W 1024/4096/16384, N 1e5..1e7, 1/4/16 interleaved shards, 3 seeds) and every statistic on hydra_hier{_d2,_d3,}.yaml, one run per grouping with --per-group-out, into hydra_saturation.csv with the coverage columns err_max_cov_0.01/0.05 (share >= tau plus the largest group). --jobs under a memory budget, --resume, and --hydra-shard i/n to split it across nodes. --phase optimizer-cost adds Hydra rows (eval variants x W x dataset); MeasuredAt gains dataset and schema_width, the reducer reading the width off the row's scores. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * hydra-univmon-sum: the ground truth refuses negative values too The lib row inserts each value as a UnivMon count and refuses negatives; the truth (and so the polars baseline) accepted them, so a spec with negatives ran on polars and failed on lib. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * study: Hydra cost rows at 3 runs; their test; datagen path from the script Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * docs: Hydra cost rows' run count Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * study_saturation: atomic hydra table rewrite, stricter resume, schema checks --phase hydra --resume now writes the kept rows to hydra_saturation.csv.tmp and os.replace()s it over the table, and keeps a row only when it ends in a line end and every column parses (integers, numbers or "" for the error columns, non-empty text). The CSV format is unchanged, so tables written by 003eb31 resume as before. The eval-spec test also checks each label's cardinality, skew and child_of against PR #196's SCHEMAS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * study_saturation: --hydra-variants/--hydra-ws narrow the Hydra cost rows too A hydra-univmon-cardinality grid at W=16384 merged from 16 shards outgrows a 251 GB node, and the cost phase can't resume; these flags let the cost phase leave a variant or width out, as they do the hydra phase. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * eval: hold covered RQEs to the worse of their smallest and largest group Per sketch-bench#189, KLL/DD/HLL error does not fall as N grows, so the largest covered group can be harder than the smallest. The generator also emits max_covered_share / covered_max_N (the largest group, always covered); the runner reads each covered RQE's accuracy at both the smallest and the largest covered group and keeps the worse (direction-aware), unknown if either read is. Classic RQEs and Hydra's lookup are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * aqpbm-datagen: scale_by/scale_range; hydra_http_latency scales latency per service A f64 column with `scale_by: <label column>` and `scale_range: [lo, hi]` multiplies each row by a factor fixed per label value, log-uniform in [lo, hi] from a hash of the value and the column's seed. Validation: earlier string label column, f64 only, 0 < lo <= hi. Absent fields keep every existing spec's output byte-identical. hydra_http_latency.yaml scales latency by service in [1, 10], so groups differ in latency scale and a Hydra-KLL mixture is no longer the queried group's own CDF. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * eval: template 12 groups by (dst_subnet, proto), not (dst_subnet, dst_port) (dst_subnet, dst_port) has 1e6 groups; answering each every minute takes about 49 s on one core, which set every plan's batch latency above the largest SLA (10 s) and collapsed the multi-grouping frontier. (dst_subnet, proto) has 3e3 groups and keeps three groupings over one stream. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * rqe-optimizer: admit Hydra (HLL, UnivMon cardinality, KLL) as candidates, with measured accuracy by coverage - FamilyProperties for hydra-hll, hydra-univmon-cardinality and hydra-kll: one fixed-size grid, DeltaSet key tracker, no roll-up, and the new answers_any_subgrouping. Added to Capability::families, not to DEPLOYABLE_FAMILIES. - Candidates: on a metric with a hydra_dataset, one grid per schema Λ (each RAQE grouping and the union, where card(Λ) is known); eligible for any RAQE with ∅ ≠ G_r ⊆ Λ. The subpopulations ceiling is gone. - Accuracy: hydra_saturation.csv under the saturation dir (optional), keyed by variant, config, dataset and grouping; the column the RAQE's accuracy_covers_share selects; worst over records and seeds; lossy merges bracketed by merge_shards, unknown past the largest. - Runner: ASAP and PerQuery plan with undeployable families; RQEs carry covers_share; http/flows streams name their Hydra dataset; results report hydra_rqes for workloads Hydra may serve. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * rqe-optimizer: Hydra grids at the full schema from their dataset's rows Round-1 review of #201: - One Hydra grid per (stream, variant, config, window, slide), over the metric's full schema only (MetricFacts::hydra_dataset now carries the dataset and the schema), the width the study measures. - MeasuredAt gains optional `dataset` and `schema_width`; a Hydra row is a candidate on a metric only if measured on its dataset. - Runner: card(full schema) = product of label fan-outs, capped at the stream's series; AutoSketch also allows undeployable families (it skips Hydra itself, #159). - read_csv checks required columns and short rows; config params must be numbers. - Docs: Hydra held to the worst case, built only at the full schema. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * rqe-optimizer: validate the Hydra schema, skip empty Hydra cells, reject long CSV rows validate_facts reports a Hydra schema outside the metric's labels or without a nonzero cardinality, instead of a later panic. The Hydra accuracy fold skips empty cells (no covered group scored in that run) and is unknown only when every row left the column empty. read_csv rejects rows longer than the header too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * eval: test fixtures carry max_covered_share and label cardinalities After rebasing onto #196's two-ended coverage read: #201's runner test RQEs need max_covered_share, and #196's coverage test schema needs the label cardinality the full-schema Hydra grid reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * eval: roll-up ablation, ASAP without roll-ups The runner adds the method `asap-norollup` ("ASAP (no roll-ups)"): ASAP's MILP, candidates, frontier sweep and SLA grid, with each RQE allowed only deployments at its own grouping or Hydra grids. Every result counts `rolled_up_rqes` (RQEs served by a finer non-Hydra deployment), and sanity checks asap <= asap-norollup <= perquery unbounded and at each SLA. The plot scripts draw it as a dashed violet line, add fig_rollup_ablation.png (unbounded cost with and without roll-ups, absolute, CPU and Fargate panels) and a roll-up section in both summaries; results without the method still plot. The run script passes --no-chosen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * eval: AutoSketch vs. ASAP with Hydra candidates and a roll-up ablation Results of the §6.3 synthetic eval (ProjectASAP/ASAPQuery#777) rerun with roll-ups (#190) and their ablation, Hydra candidates measured on the eval's own data (#195–#202), the multi-grouping template set (#196), #192's KLL footprint and a re-measured cost table. 8 workloads, 0 RQEs dropped, 0 sanity violations. Roll-ups serve 40% of the multi-grouping RQEs from a finer deployment and halve ASAP's deployments, but save only 3.3–3.6% (CPU) / 4.4–4.8% (Fargate). Hydra is eligible for many RQEs but never chosen: its insert fans out to every label subset (1.9–5.1 µs per record vs. 3.85 ns for a per-group HLL). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * eval: cube template and a multi-grouping-only workload Template 16 is now p99 latency GROUP BY CUBE(region, service, endpoint), all 7 groupings, replacing {service}, {region, service}, {service, status}. A new `multigroup` template set runs the multi-grouping templates alone, so the roll-up ablation is not diluted by the classic set. Roll-ups now save 60% on multi-grouping (0.551 -> 0.221 vCPU, 26 -> 5 deployments) and 8% on mixed + multi-grouping. Classic is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis * eval: each workload's chosen plans, gzipped Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhcWJExZcmVqS6r6rEtjis --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Closes #187.
A deployment grouped by
G_dcan now serve a RAQE grouped by a subsetG_rwhen its family merges across groups. The read merges eachG_rgroup's fine states into one (a roll-up), and the planner chooses it only when it is SLA-valid and cheapest. ASAPQuery can't run roll-ups yet; see Interface change.Design decisions (please approve)
These are the decisions from the design discussion on #187 (Q&A, Q1 change). Please approve each one or push back. Example used throughout: KLL by
(service, endpoint)(50 groups) serving p99 by(service)(5 groups),L = 1h,x = 1m.Scope
MultipleIncreaseper series, so there is no engine bug there.is_eligible.Q1. Which families roll up? New
FamilyProperties::mergeable_across_groups.is_eligible: the groupings are equal, or the family ismergeable_across_groupsandr.grouping ⊂ d.grouping. A coarse deployment never serves a finer RAQE.Q2. Candidate windows. For families that roll up, a grouping's windows, slides and heaps come from every RAQE with the same capability, metric and filter whose grouping is a subset of it. That way a fine
xcan divide a coarse RAQE'sL. Other families keep their exact-grouping inputs, so top-k candidates don't grow. No new groupings are invented.Q3. Cost of a roll-up read. Each merge folds two states into one. A query reads
card(G_d)·L/xstates and ends withcard(G_r):card(G)·(L/x − 1)·c_mrg / Tmerges · c_mrg / Tcard(G)·m(L)card(G_r)·m(L)card(G)·…card(G_r)·…card(G)·c_qry + card(G)·(L/x−1)·c_mrgcard(G_r)·c_qry + merges·c_mrgG_dG_r = G_d, each row reduces to the old formula.L == x) roll-up still mergescard(G_d) − card(G_r)states.c_mrg.Q4. Accuracy of a roll-up. It reads
data_shape[G_r]and N= λ·L / card(G_r). KLL and univmon-cardinality merge lossily (#131; UnivMon rebuilds its heavy-hitter heaps from the union on merge), so they read their merge curves at⌈card(G_d)/card(G_r)⌉ · L/xmerged instances. That's the average fan-out; the worst group's is in #189. Past the measured merge counts, KLL takes its guarantee. univmon-cardinality has no guarantee, so it has no accuracy there; it isn't in the study orDEPLOYABLE_FAMILIEStoday, so nothing changes in practice.Q5. Plan output. No new field. A RAQE rolls up exactly when its grouping differs from its deployment's.
Q6. Fact validation.
validate_factsrequirescard(X) ≤ card(Y)for every pairX ⊂ Yamong the label sets a plan can use: every RAQE grouping on the metric, plus its full label set. Label sets no RAQE groups by are not checked. This replaces the "more groups than series" check. Otherwise bad facts would make the merge count negative, and the MILP would treat a roll-up as a discount.Interface change (ASAPQuery)
grouping_labelsdiffers from its deployment's. Until ASAPQuery can merge fine groups into coarse ones at read time, it should error on such a plan when it bumps the pinned rev.validate_factsrejects more inputs: a RAQE grouping with more groups than another RAQE grouping, or than the full label set, that is a superset of it.Tests
tests/mixed_grouping.rs(new, end to end):(service, endpoint)and(service)share one fine KLL deployment;candidates.rs:analytical_cost_model.rs: roll-up merge count, accumulators and latency, including a direct roll-up.saturation.rs: KLL roll-up reads the coarse shape, coarse N and the fan-out × windows merge curve.lib.rs: subset-cardinality validation.candidates.rs(pruning):retains_candidate_with_cheaper_compactionandprunes_the_candidate_with_larger_query_output.analytical_cost_model.rs:a_dd_roll_up_sizes_its_accumulator_at_the_coarse_grouping.saturation.rs:univmon_cardinality_merges_lossily_hll_and_dd_do_not.lib.rs:rejects_a_used_label_set_with_more_groups_than_a_used_superset,rejects_a_grouping_with_more_groups_than_seriesandignores_label_sets_no_raqe_uses. These replace the oldrejects_more_groups_than_series.cargo test(104 + 2),clippy --workspace -D warningsandfmtall pass, rebased on main (#192).examples/workload_scenarios.rsagainst main: the only change is the mixed-grouping sweep's "both" case (p99 by(service)plus p99 by(service, endpoint)), which now runs on 1 DDSketch deployment instead of 2:Every other sweep is byte-identical.
Also in this PR (found in review)
validate_factschecks group counts only among the label sets a plan can use: every RAQE grouping on the metric, plus its full label set.rqe_optimizer_candidates.mdandrqe_optimizer_cost_model.md, with one cost table covering every phase, roll-ups included. They now say why rate/increase, top-k and Hydra don't roll up.Open questions
Raqedoesn't record the counted key yet.examples/small_problem.rsaddslatency_p99_1h_by_service. With--milp --saturation-dir study_final030k, it rolls up onto the per-endpoint DD deployment:🤖 Generated with Claude Code