Skip to content

Add saturation study: error vs N, N_saturation and Pareto value data - #133

Merged
milindsrivastava1997 merged 1 commit into
mainfrom
saturation-study
Oct 4, 2026
Merged

milindsrivastava1997 merged 1 commit into
mainfrom
saturation-study

Conversation

@milindsrivastava1997

Copy link
Copy Markdown
Contributor

Re-lands #130 on main. #130 was meant for main but was merged into dev-milind-turboprom after #129 had already been squash-merged into main. This is #130's squash commit cherry-picked onto main, applied cleanly with no other changes. Follow-up: #131 (accuracy after merging), now stacked on this branch.

Conclusions (task 3: sketch accuracy, saturation and cost)

Full write-up with the math behind the conclusions: docs/saturation_conclusions.md

  1. No sketch's theoretical error bound depends on N. Error that still moves with N comes from two small-N effects: a noisy ground truth when N is small, and a tail of keys still arriving while N < K. Past N_sat, error is set by the config alone, so a profile measured on a small window extrapolates to larger (merged) windows.

  2. N_sat is mostly 1e3–1e6, with two exceptions:

    • With uniform keys and large K, CMS and CountSketch never saturate: at K = 1e7 the error is still rising at 1e9.
    • With large K, top-k precision keeps improving with N up to 1e9.

    So an N_sat measured only up to 1e7 is a lower bound when K is large.

  3. Error scales with config size as theory predicts, so configs can be interpolated:

    Sketch Error scales as
    CMS 1/w (steeper with skew)
    CountSketch 1/√w (≈ w^(−θ) with skew)
    HLL 1/√m
    KLL 1/k
    DDSketch α_dd
  4. The data distribution decides which sketch to use:

    • Low θ is the worst case for CMS and top-k, so size them from the lower θ bound.
    • At low skew and large K, CountSketch beats CMS at equal memory. For CallGraph, CountSketch needs 48 KB where CMS needs 192 KB.
    • Top-k is unreliable for θ ≤ 0.5.
    • KLL's error does not depend on the distribution.
    • DDSketch's rank error grows with α while its memory shrinks with α, so size rank error from the upper α bound and memory from the lower α bound.
  5. Merging (Measure accuracy after merging m shards #131):

    • CMS, CountSketch, HLL and DDSketch merge exactly: merged error equals the single-sketch error.
    • KLL's error rises more for larger k: about 1.0–1.1× at k = 50/200 and 1.12–1.32× at k = 800 at N = 1e7, and up to 3–4× at N = 1e4–1e5.
    • Top-k is unchanged in the median but loses up to 40% precision at large K.

    So single-sketch curves are optimistic for top-k and KLL in merged windows.

  6. Cost:

    • Per-item insert CPU is flat in N and in the distribution: CMS ≈ 22 ns, CountSketch ≈ 26 ns, HLL ≈ 3.4 ns, KLL ≈ 25 ns, DDSketch ≈ 16 ns. Top-k is the exception, rising from about 100 ns to 1,750 ns per item as θ goes from 0 to 2.
    • Memory is fixed by the config, except DDSketch, which needs more memory for heavier tails.
  7. Sketch vs exact computation (exact = buffer the samples, compute at query time):

    • Memory: sketches save 10× after a few thousand items and 100× after 1e4–1e6 items, depending on sketch size.
    • CPU:
      • CMS and CountSketch are about 13× cheaper, HLL about 40×; neither reaches 100×.
      • Quantile sketches exceed 100× from 1e5–1e6 items, because exact computation has to sort.
      • At high skew, top-k is slower per item than exact; its saving is memory only.
    • Real windows hold millions of items, so sketches save at least one to two orders of magnitude of memory.
  8. Worst-case parameters → configuration is automated (scripts/recommend_config.py). Examples:

    Query Recommendation
    Google by (user) CMS 3×1024 (12 KB)
    CallGraph by (service) CountSketch 3×4096 (48 KB)
    MSRTMCR instant 192 KB
    p99 latency DDSketch α = 0.01 (3–5 KB)

Limits:

  • Some recommendations extrapolate beyond N = 1e7.
  • Merged accuracy covers every top-k and KLL config, but only up to N = 1e7 and 64 shards.
  • The exact baseline buffers the stream; with a streaming hash-map counter, the frequency sketches' memory advantage would be smaller.

Summary

Measures, for each sketch type, configuration and data distribution:

  • Empirical error vs N, and N_saturation, the stream length after which error stops changing.
  • Insert / merge / query CPU and memory, measured serially on an idle machine.
  • Crossover N* against exact computation: the stream length from which exact computation stays at least 10× or 100× more expensive than the sketch.
  • A recommended configuration per query, from the worst-case data parameters fitted in feat(asap-tools): label query sets and skew fits for cluster traces ASAPQuery#746.

Full method and results: docs/saturation_study.md. Figures, each folder with a README: docs/figures/saturation/.

What's added

Piece What it does
--dataset pareto (--pareto-alpha, --pareto-scale, f64 or floored i64) Heavy-tailed value streams for quantile sketches; i64 so the polars exact quantile baseline applies
scripts/study_saturation.py Error-vs-N sweep over sketches × configs × distributions. Phases: --phase accuracy|cost|crossover|all. Supports --resume, --cost-rows 3, --points-from
scripts/recommend_config.py Joins ASAPQuery's worst-case parameters (θ, K, N, α per query and range) with the grid curves. Picks the smallest-memory config per sketch family that meets the query's accuracy target, and marks extrapolated, not-yet-saturated and no-merge-curve results
scripts/plot_saturation_{curves,cost,crossover}.py Error vs N with each sketch's theoretical bound; cost vs θ/α; exact vs sketch memory and CPU

N_sat is the first checkpoint after which every later seed-mean error stays within max(10% of the plateau, 2 standard errors) of the mean of the last 3 points.

N*(f) is the first N from which exact cost stays ≥ f × sketch cost at every larger N. CPU is counted as "ingest N items + answer one query" on both sides.

Grid

Sketch Configs
CMS, CountSketch, CMS-heap top-k cols 256–16384 × rows 3/5
HLL lg_k 12/14/16
KLL k 50/200/800
DDSketch α 0.005–0.05
  • Data: Zipf θ ∈ {0 … 2} × K ∈ {1e3, 1e5, 1e7}, or Pareto α ∈ {1.1, 1.5, 2, 3}.
  • Error runs: N up to 1e7 with 3 seeds (595 points), plus 185 points run to 1e9.
  • Cost runs: serial, on an idle machine, rows=3 only (rows=5 cost ≈ 5/3 of that).

Key results

Every theoretical bound is N-independent. Error that still changes with N comes from small-N effects: a noisy ground truth, or a tail of keys still filling in while N < K.

Plateau error vs sketch size (log-log slope against each sketch's size parameter: width w for CMS/CountSketch/top-k, register count m = 2^lg_k for HLL, k for KLL, α for DDSketch):

Sketch Theory Measured
CMS −1 −1.0 at low skew; steeper with skew
CountSketch −0.5 −0.43 to −0.51 at low skew; ≈ −θ with skew
HLL −0.5 −0.46 to −0.66
KLL ≈ −1 −1.03 to −1.18
DDSketch +1 0.98 to 1.08

Run to 1e9, 67 of the 108 points not saturated by 1e7 saturate:

  • Uniform keys with K = 1e7 never saturate CMS or CountSketch.
  • Top-k at K = 1e7 keeps improving with N.
  • So an N_sat from curves that stop at 1e7 is a lower bound when K is large.

Cost per item at N = 1e7, flat in N and in the distribution unless noted:

Sketch Insert CPU
CMS ≈ 22 ns
CountSketch ≈ 26 ns
HLL ≈ 3.4 ns
KLL ≈ 25 ns
DDSketch ≈ 16 ns
CMS-heap top-k 100 ns at θ = 0, rising to 1,750 ns at θ = 2

DDSketch memory shrinks from 6.3 KB to 2.3 KB as the Pareto α grows from 1.1 to 3.

Crossover vs exact (exact = buffer the samples, compute at query time):

Sketch Memory 10× / 100× from N = CPU 10× / 100× from N =
CMS / CountSketch 3×1024 5.6e3 / 1e5 1e3 / not reached (≈ 13×)
CMS / CountSketch 3×16384 1.8e5 / 3.2e6 1e3 / not reached
HLL lg_k = 12 5.6e3 / 5.6e4 3.2e3 / not reached (≈ 40×)
KLL k = 200 5.6e3 / 1e5 1e3 / 1e6
DDSketch α = 0.01 5.6e3 / 5.6e4 1e3 / 1.8e5
CMS-heap top-k as CMS not reached: slower per item than exact at θ ≥ 1

Recommendations, with the cost data:

Query Recommendation
Google by (user) CMS 3×1024 (12 KB)
CallGraph by (service) CountSketch 3×4096 (48 KB), against CMS 3×16384 (192 KB)
MSRTMCR by (msname) instant needs 192 KB; 5m/1h ranges need 48 KB
p99 latency DDSketch α = 0.01 (3.4–4.6 KB)

Known limits

  • The exact baseline buffers the stream. A streaming hash-map counter would move the frequency memory crossover later.
  • Top-k and KLL error after merging differs from a single sketch (see Measure accuracy after merging m shards #131). Their recommendations are marked when there's no merge curve for that point.
  • Pareto α above 3 (e.g. Google cpu_rate) is clamped to the edge of the grid.

Validation

  • cargo test -p aqpbm-datagen passes, including a Pareto α recovery test.
  • clippy -D warnings is clean on the touched crates.
  • python3 -m unittest scripts/test_study_saturation.py scripts/test_recommend_config.py: 22 + 11 tests pass.

🤖 Generated with Claude Code

🤖 Generated with Claude Code

…130)

* Add Pareto distribution to datagen and --dataset pareto

DataDistribution::Pareto(ParetoParameter { alpha, scale, seed }) samples
rand_distr::Pareto. Like Normal it is unbounded, so it has no domain and
refuses a cardinality or string rendering. The CLI adds --pareto-alpha and
--pareto-scale (default 1.0) and requires --dtype f64. Includes an MLE test
for alpha and the regenerated merged_record schema.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add saturation study script

scripts/study_saturation.py sweeps stream length N per sketch, config and
distribution point, averages the error over seeds, and reports N_sat: the
smallest checkpoint from which the error stays within a relative tolerance
of its tail plateau. A final-N run records insert/merge/query CPU and
memory_bytes. scripts/test_study_saturation.py covers N_sat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Widen the saturation band by seed standard error

A checkpoint is in band when it is within max(10% of the plateau, 2 SE)
of the plateau, so seed noise alone no longer breaks a small plateau.
The curve CSV gains a seed_se column, and child runs read stdin from
/dev/null so a detached study is never stopped for terminal input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Document the saturation study and add its plotting scripts

The doc gives the N_sat definition, the reduced-run results, a
theoretical bound per sketch with how the empirical curves relate to
it, and what is not covered yet (accuracy after merging, config scaling).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Allow --dataset pareto --dtype i64 (floored draws)

The datagen renderer already truncates each f64 draw into an i64 column,
and Pareto draws are >= scale > 0, so truncation is a floor; only the CLI
refused i64. Allowing it lets the i64-only polars exact quantile baseline
score a Pareto stream (e.g. --pareto-scale 1000 keeps three digits).
Adds a datagen test that the i64 column is the floored f64 draws.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Saturation study: config grid, exact-baseline crossover, phase split

- Configs step 4x: CMS/CountSketch/CMS-heap cols 256..16384 x rows 3,5;
  HLL lg_k 12,14,16 (asap HLL has no lg_k 10); KLL k 50,200,800;
  DDSketch alpha 0.005..0.05.
- Quantile points use Pareto(alpha, scale 1000) floored to i64.
- --phase accuracy|cost|all: cost reads saturation_curve.csv from --out
  and runs only the serial cost runs, so CPU can be timed separately.
- The cost phase also runs each family's exact polars baseline at every
  checkpoint and writes crossover.csv: the smallest N where exact
  memory_bytes (and insert+prepare+query CPU) reach 10x/100x the sketch's.
- --points-from CSV selects a subset of the grid (for the 1e9 runs).
- Tests for n_star, read_curve, --points-from and the cost phase against
  a fake approxbench.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Saturation study: --resume and --cost-rows

- --resume (accuracy): keep the points whose curve over the requested sizes
  is already in saturation_curve.csv, write them back first, and run only
  the rest; a point cut off mid-write is dropped and rerun. Raw records are
  appended.
- --cost-rows N (cost): time only the rows=N configs of the Vector2D
  sketches; other rows keep blank cost columns and get no crossover row.
- Tests for both against the fake approxbench.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Saturation plots: draw one config per sketch from grid output

The curve and cost files now hold the whole config grid; each figure keeps
the config its bound is written for (rows=3 cols=1024, lg_k=12, k=200,
alpha=0.01). The caption no longer hard-codes the seed count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Saturation study: config-grid and 1e9 results

Document the 595-point config grid to 1e7 (N_sat per config, plateau
scaling against theory) and the 185-point 1e9 subset: which unsaturated
points saturate by 1e9, which keep growing (uniform data at K=1e7,
top-k miss rate), and how far 1e7 N_sat verdicts move with a longer curve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Saturation study: recommend a sketch config per query from worst-case data parameters

scripts/recommend_config.py reads ASAPQuery's skew_summary.csv (new
worst_* / min_N / max_N / target_* schema, or the older per-window one),
rounds each query's worst case to the measured grid toward the harder side
(theta down, K up, rank alpha up), reads the error at max_N from the grid
(or 1e9) saturation curves, optionally from the merged curve for top-k and
KLL, and writes the smallest config per family that meets the target to
out/recommendations.csv, with n_sat, flags and cost columns once the cost
phase exists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add saturation study figures with reading guides

Error-vs-N figures with theoretical bounds and the serial cost figure for
the reduced run, plus the 1e9-subset figures, each with a README.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Saturation study: grid cost results and a per-query crossover N*

N* is now the size from which exact stays >= f x the sketch, so polars'
fixed start-up cost no longer marks N = 1e3 as a crossover, and both sides
are charged one query instead of their differently sized probe sets.
--phase crossover recomputes crossover.csv from the saved cost JSONL.
Adds the grid cost and exact-vs-sketch figures, the crossover and
recommendation CSVs, and the results to the doc. The recommender's query
column is renamed query_phase_us, since it is the whole query phase.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add the grid crossover and recommendation tables

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Document the saturation study's conclusions and the math behind them

Conclusions across the trace analysis, the sketch study and merging, plus
derivations: why KLL's rank error is distribution-free, why DDSketch's
rank error grows and its memory shrinks with the Pareto index, and why
merging is exact for linear sketches but adds variance for KLL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Update merge conclusions with the full top-k and KLL grids

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Key saved cost records and keep out-of-grid curves on resume

--phase crossover matched saved cost records to points by position, so a
different --cost-rows than the cost phase used gave silently wrong numbers
or a StopIteration. Cost and exact records now carry their key and are
looked up by it; a missing one exits with a clear message.

--resume rewrote saturation_curve.csv with only the current grid's curves,
so a narrower resume deleted finished curves. Rows outside the grid are
now written back unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Cancel queued accuracy runs when one fails

A failed approxbench run (or Ctrl-C) used to surface only after every
queued run finished, and those results were discarded anyway.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Match cardinality as a number in point_key

A saturation CSV filtered with pandas writes Zipf cardinalities as
1000.0 (the Pareto rows' empty cells make the column float), which
never matched the grid's "1000".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Milind Srivastava <milindsrivastava1997@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants