Repository navigation
ddsketch: drop sum/min/max from the state (new ASAPv1 kind 0x05 0x01) - #252
Merged
Merged
Conversation
The DDSketch state is now the two bucket stores and the zero count. No DDSketch readout needs sum/min/max: they only clamped quantiles to [min, max]. A delta names buckets, not values, so a delta-fed receiver could only fold in bucket representatives and ended with different scalars than the producer. Sum, minimum and maximum belong to a separate exact aggregate. - Remove the sum/min/max fields and the sum()/min()/max() accessors. - Quantiles read every rank, the extremes included, from its bucket's representative; q = 0 / 1 are within alpha of the true extremes. - merge_delta and apply_delta update buckets and count only, so deltas rebuild the producer bit for bit. - ASAPv1: encode 0x05 0x01 / 0x05 0x81 (five-field payload); decode the retired 0x05 0x00 / 0x05 0x80 with the scalars dropped. EhBucketSketch nests 0x05 0x01 and accepts 0x05 0x00. - EhBucketSketch "min" / "max" on a DDSketch read quantiles 0 / 1. - Golden fixtures for the new kinds (submodule bump); docs and tests.
…ank fallback) - octo.rs: the octo-runtime test read the removed min()/max(); it now checks quantiles 0 and 1 within alpha of the stream's extremes. - EhBucketSketch: spec rules 1-2, wire comments, API doc and test doc count eleven nested ids (ten encoded plus the retired 0x05 0x00). - docs/tests.md: rows for the delta kind checks, the e2e envelope kind and the octo-runtime test match the tests. - The within-alpha claim is for indexable magnitudes; smaller nonzero magnitudes count as zero and read 0.0. - value_at_rank returns the last occupied bucket instead of panicking if count ever exceeded the buckets. - New tests: the retired decoder refuses an int or float32 scalar and drops NaN ones; a rank past the buckets reads the last occupied one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
zzylol
force-pushed
the
ddsketch/drop-sum-min-max
branch
from
October 28, 2026 23:54
84066e2 to
f961b90
Compare
This was referenced Oct 28, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #250
Why
A DDSketch's readouts are its quantiles, and they come from the bucket stores.
DdSketchalso keptsum,minandmax, and the ASAPv1 payload carried them. Nothing a DDSketch answers needed them: they only clamped quantile answers to[min, max], plus three accessors. They also broke deltas. A delta names buckets, not values, somerge_delta/apply_deltacould only fold in bucket representatives, and a receiver fed by deltas ended with a different sum and different extremes than the producer.The design decision: sum, count, min and max are a separate exact aggregate, not sketch state. This PR is the DDSketch part of the umbrella issue.
What
DdSketch's state is the two bucket stores, the zero count and the admission rate (countstays derived).sum(),min()andmax()are removed.q = 0andq = 1are withinalphaof the true minimum and maximum (for indexable magnitudes: a nonzero magnitude below the indexable range counts as zero and reads0.0).merge_deltaand Octo'sapply_deltatouch buckets and counts only, so a delta-fed receiver holds the producer's buckets exactly.0x05 0x01(state, five-field payload) and0x05 0x81(delta, same payload as before). The retired0x05 0x00/0x05 0x80still decode, with the scalars dropped, and are never encoded.EhBucketSketchnests0x05 0x01and accepts0x05 0x00, so it has eleven decodable nested ids, ten of them encoded.EhBucketSketch's"min"/"max"commands on a DDSketch read quantiles 0 and 1.08440a1).memory_bytes/estimated_memory_bytesneed no edit: both start fromsize_of::<DdSketch>(), which is now 24 bytes smaller.main) already left the sum / min / max out of theDDSketchrow.countonly), Q-DERIVED; the DDSketch, Octo and EhBucketSketch API pages; CHANGELOG.How
ddsketch.rs: fields,add_to_sumand the scalar updates intry_add,merge,apply_deltaandapply_zero_deltaare removed.get_value_at_quantileclamps the nearest rank to1..=nand readsvalue_at_rank, the same bucket walkquantile_interpolateduses, now without the rank-0 / rank-n−1special cases or the clamp. Ifcountever exceeded the buckets (no code path allows it),value_at_rankreturns the last occupied bucket and does not panic.wire.rs:DdPayloadis the five fields.DdPayloadWithScalarsreads the retired eight-field payload and converts by dropping the scalars. The old scalar decode rule is gone.delta.rs: decodes either delta kind, encodes0x05 0x81.octo.rs(octo-runtimetest): checks quantiles 0 and 1 withinalphaof the stream's extremes instead of the removedmin()/max().Key code interfaces added / modified
src/sketches/ddsketch.rssrc/sketches/ddsketch/wire.rssrc/sketches/ddsketch/delta.rssrc/asapv1/kind.rs(crate-private; the wire ids)src/sketch_framework/eh_bucket_sketch.rsWire format (
docs/asapv1_wire_format.md)0x05 0x01[counts, offset, negative_counts, negative_offset, zero_count]0x05 0x81[indices, deltas, negative_indices, negative_deltas, zero_count]0x05 0x00[counts, offset, sum, min, max, negative_counts, negative_offset, zero_count]0x05 0x800x05 0x81Usage
A collector pane sends DDSketch deltas to a receiver. It keeps sum / min / max in its own exact aggregate. This was compiled and run as an integration test at
4234c5c(the same commit before the rebase onto #254, which touches only the capability table):Before this PR, the same receiver's
sum(),min()andmax()were built from bucket representatives and differed from the producer's.Major algorithm changes
100.3and101.4fall in bucket 230, whose representative is100.495((1 + α)·γ^230). The producer hadsum = 201.7,min = 100.3,max = 101.4. A receiver fed the bucket delta hadsum = 200.989andmin = max = 100.495, so the two states differed.[min, max];q <= 0/q >= 1returned the exactmin/max; deltas folded representatives into the scalars.adddoes one add and two compares less. The state is 24 bytes smaller, and each payload three f64 fields smaller.q = 0/q = 1are no longer exact observed values: a consumer that needs the exact extremes keeps a min / max aggregate.Before this PR
DdSketchtrackedsum/min/max, carried them on the wire (0x05 0x00), returned exact extremes atq = 0 / 1, and could not rebuild them from deltas.After this PR
DdSketchis buckets only (0x05 0x01/0x05 0x81). A delta stream rebuilds it exactly, quantiles are within α everywhere (for indexable magnitudes; smaller nonzero values count as zero), and older0x05 0x00/0x05 0x80bytes still decode.Verification
Rebased onto
mainat21b6357(after #249, #251 and #254). Commits:d3a5ddc(the change) andf961b90(review fixes). Only the tests covering the changed code, plus format and lint:cargo test --lib: 1010 passed, 0 failed, 4 ignored.cargo test --features octo-runtime --lib octo: 65 passed.cargo test --release --features octo-runtime --test e2e octo: 80 passed.cargo test --release --features experimental --lib -- ddsketch eh_bucket octo asapv1 admission: 111 passed (on950bb5c).cargo test --test asapv1_golden: 71 passed (default features), 72 passed (experimental).cargo test --test ddsketch_signed: 5 passed.cargo test --test conformance_kit: 13 passed.cargo test --test capability: 3 passed (default andexperimental).cargo test --test heap_memory_bytes: 1 passed on950bb5c(includes the DDSketchestimated_memory_bytesbound).cargo test --test pbt ddsketch: 9 passed;--test pbt octo_delta: 25 passed (onf961b90).cargo test --test pbt -- ddsketch dd_ octo eh wire quantile: 47 passed.cargo test --release --test e2e -- ddsketch dd_ octo eh_ wire: 110 passed. Those two filtered runs were on the pre-feat(capability)!: Planner vocabulary, exhaustive serde enums, rows without exact aggregates #254 base950bb5c; feat(capability)!: Planner vocabulary, exhaustive serde enums, rows without exact aggregates #254 changes only the capability table, docs andlib.rsre-exports.cargo fmt --check: clean.cargo clippy --all-targets,--all-targets --features experimentaland--workspace --all-targets --all-features --locked -- -D warnings: no warnings.deltas_rebuild_the_producer_bit_for_bit: the receiver's non-zero cells and quantiles equal the producer's after each delta batch.ddsketch_decodes_the_retired_kind_with_scalars,ddsketch_retired_fixtures_decode_to_their_bucket_twins: old bytes decode.ddsketch_endpoints_are_within_alpha,ddsketch_returns_the_signed_extremes_within_alpha: q = 0 / 1 are within α of the true min / max.ddsketch_min_and_max_commands_are_within_alpha.ddsketch_retired_scalars_must_be_float64: the retired decoder refuses an intsumor a float32min, and drops NaN scalars.a_rank_past_the_buckets_reads_the_last_occupied_bucket: thevalue_at_rankfallback.pbt/e2esuites, and benchmarks.Limitations and follow-ups
main, the registry gains0x05 0x01/0x05 0x81and keeps0x05 0x00/0x05 0x80as retired kinds with a payload. On top of the Go DDSketch codec PR (feat(ddsketch)!: ASAPv1 codec with signed values; drop the protobuf, msgpack, delta and gob formats sketchlib-go#90), the codec encodes0x05 0x01and decodes both kinds. A Go delta codec for0x05 0x81does not exist yet. Go merge order: the Gomainregistry PR first (after golden RefCell to reuse buffer during insertion when only have Shared Reference (&T) #4), then KLL compactor merge: safe or unsafe #90 rebased onto Gomain(takingmain'skind.go), then the codec branch restacked on it with itskind.gohunk dropped.quantile_interpolated) and ASAPCollector (get_value_at_quantile). Tests there that compare q = 0 / 1 or ranks 0 / n−1 with exact values need an α tolerance.🤖 Generated with Claude Code