Skip to content

docs(design): holistic edge→backend compression (cold chunk + warm sketch) - #331

Merged
zzylol merged 12 commits into
mainfrom
docs/holistic-compression-design
May 25, 2026
Merged

zzylol merged 12 commits into
mainfrom
docs/holistic-compression-design

Conversation

@zzylol

@zzylol zzylol commented May 25, 2026

Copy link
Copy Markdown
Contributor

Design doc for review (no code) — docs/design/holistic-edge-backend-compression.md.

What it specs

  • Cold raw chunk format (§1): part/chunk byte layout; lossless best-of-N codec {Gorilla-XOR, INT_FOR_DELTA, INT_FOR_DOD} with a decimal-exactness guard; write path stores to S3 without decode; a decode-on-read Thanos StoreAPI decodes chunks to XOR AggrChunks at query time, so S3 holds the compact custom form while a stock PromQL engine queries it.
  • Warm sketch encoding (§2): the offset/common-bits idea per family — SUM/KLL use the offset (shift-equivariant), DDSketch FOR+delta on bucket indices, HLL sparse, CMS narrow-counter — plus offset×delta-transmission with the offset bound to the Full-snapshot epoch (so deltas stay mergeable).
  • Offset drift → Full re-base (§3): unifies warm (re-emit Full) and cold (cut chunk, new base); per-series adaptive Full cadence + a heartbeat Full; counters use delta (no drift).
  • Shared per-series offset from parse-once (§4).

Evidence

Offline benchmark on the Chimp/Serf real datasets (/mydata/compress-bench): INT FOR+delta beats Gorilla ~4.8× (up to ~10×) on fixed-decimal series (the majority of real metrics); Gorilla wins on true high-precision float → hence the best-of-N.

Decided out of scope

No zstd-wrapped variants, no lossy/Serf — cold stays purely lossless.

Open decisions (§5)

  1. Drift / heartbeat-Full thresholds (per-shape defaults).
  2. Which warm sketch families get offset/FOR first — audit of sketchlib-go / asap-sketchlib in progress to size the real gain vs what's already done.

zzylol and others added 12 commits May 25, 2026 09:46
…etch)

Design (for review, not code) for a custom lossless cold chunk format and a
unified offset/frame-of-reference scheme that also shrinks warm sketch state.

- Cold chunk: best-of-N {Gorilla-XOR, INT_FOR_DELTA, INT_FOR_DOD}, all lossless,
  with a decimal-exactness guard; write path stores to S3 WITHOUT decode; a
  decode-on-read Thanos StoreAPI decodes to XOR at query time so a stock PromQL
  engine queries the compact S3 form.
- Warm sketch: per-family offset/FOR/delta/sparse (SUM/KLL use the offset;
  DDSketch index-FOR; HLL sparse; CMS narrow-counter), plus offset×delta-
  transmission with the offset bound to the Full-snapshot epoch.
- offset-drift → Full re-base, unifying warm (re-emit Full) and cold (cut chunk,
  new base); per-series adaptive Full cadence + heartbeat; counters use delta.

Informed by an offline benchmark on the Chimp/Serf real datasets: INT FOR+delta
beats Gorilla ~4.8x on fixed-decimal series (the majority); Gorilla wins on true
high-precision floats — hence best-of-N. Decided out of scope: no zstd, no lossy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backend stores the wire bytes opaquely (no re-serialize / no part recompression),
so wire = sketch_db storage = disk parts — one serialize change wins on all three.
Measured (N=5000): HLL dense 16,532 B flat → sparse 5–50× (P1, also cuts warm
memory since stored uncompressed); KLL raw-f64 2,157 B → (v−offset) fixed-point
~2× (P2). DDSketch (556 B, already FOR+varint), CMS/CountSketch (~12.5 KB, already
zigzag-varint), SUM (OTLP-framing-bound) already well-encoded — skip. Supersedes
the earlier DDSketch-index-FOR / narrow-CMS rows. §5.2 resolved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Measured (/mydata/xseries-bench, real cluster groups + synthetic): shared
timestamp column is the cross-series win — ts is 36-52% of per-series bytes
(values FOR-compress well), so sharing it across a k-series group saves -43%
on the real aggregate, correlation-independent (the Heracles result). The
cross-series VALUE base is net-NEGATIVE (-3.5%): predictor is noise/signal
ratio not correlation, per-series delta already took the common bits, plus a
float-domain trap. Adopt shared-ts grouped layout; cross-series value base is
opt-in only for near-replica groups.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v1 is zero-tuning: drift = hard/overflow only (re-base when a residual exceeds
the frame's chosen integer width — a correctness bound, not a knob); heartbeat
= fixed N windows (= existing Full cadence; cold's chunk bound already serves).
Counters use delta + heartbeat. The soft/efficiency-K drift (the only tunable)
is deferred to a later optimization, added only if long-lived frames degrade
without overflowing. §5.1 marked decided.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sampling-enhanced sketches compose with the compression scheme on orthogonal,
complementary axes: compression cuts bytes, sampling cuts ingest CPU/update-rate
(the axis FOR/offset can't touch — it's what reduces hashing/compaction cost).
Warm-only (cold raw stays lossless). Composition rule: store the RAW SAMPLED
integer state + a global p per frame, rescale ×1/p at QUERY (not store), so
varint/FOR/sparse keep working on small integers. Per-family: HLL hash-threshold
sampling synergizes strongly with sparse encoding (thins registers, extends the
sparse regime ~1/p×); DDSketch/CMS store smaller sampled counts; KLL weighted
insert is orthogonal. Error budgets add (per-family bounds, KLL pN≳k²); p is a
per-metric control-plane knob alongside tier/sketch-type.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…et constant

The cold (VM/INT_FOR) and warm (KLL/SUM) paths share the parse-once computation
— the per-series decimal SCALE (must be identical so both land in the same
integer domain) + value stats — but NOT a single offset constant: cold re-bases
per block (tightest per-block width) while warm re-bases per Full-epoch (stable
base so Deltas stay mergeable); those cadences conflict. Scope is the raw-value-
offset families only (cold-INT / KLL / SUM); DDSketch (index-FOR), HLL (hash),
CMS (counters) have no shared value offset. Kind matches per shape (gauge→FOR,
counter→delta), not as a number. Also note sampling (§6) is what closes the
sketch-build CPU gap the offset can't.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Formal backing for §6's cited bounds: the four sampling-enhanced sketches with
their algorithms + unbiasedness/variance/concentration proofs and final boxed
bounds — DDSketch (inverse-prob bucket; x_q∈(1±α)x_{q±ε_s}), KLL (weighted
insert; ε_k+ε_s, pN≳k²), HLL (hash-threshold, not additive; RSE≈√((1-p)/(pn)+
1.04²/m); why per-occurrence is biased), Nitro/CMS/CountSketch (additive; var
(1-p)/p·Σa²; CM loses no-underestimate). Design rules by update type. §6.5 (the
Go benchmark proving these empirically vs sketchlib-go) lands separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…g benchmark

Measured vs real sketchlib-go (harness /mydata/sampling-bench): all §7 bounds
held. p=0.1 → ~10× fewer updates + ~6× wall-clock per family. KLL pN≳k² confirmed
as a hard boundary; HLL hash-threshold is unbiased to p=0.01 (n≥1e5) while
per-occurrence blows up (0.66→3.8) — and the threshold hash MUST be independent
of HLL's register hash (else 15–84% bias). Sampled serialization never bloats
(composition rule holds). Caveats: HLL m is compile-time const; sparser-HLL win
only below register saturation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Geometric skip-ahead is a statistically-identical, cheaper implementation of
per-update Bernoulli(p) (gaps ~Geometric(p) ⟺ iid keep w.p. p): same §7 bounds /
composition rule, amortizes sampling RNG to ~O(p)/item. Applies to the per-update
families (DDSketch/KLL/CMS/CountSketch/Nitro), NOT HLL (hash-threshold per key,
already RNG-free). Orthogonal to shared-ts and the offset — it touches neither
timestamps nor the value base.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 phases, ordered by isolation+ROI+dependency+risk. Parallel start set PR1
(HLL-sparse) ∥ PR2 (KLL-offset) ∥ PR5 (cold INT codec lib); critical path
PR5→PR6(StoreAPI)→PR7(shared-ts). Sampling (PR3 gated no-op + PR4 controller p)
in phase 2; offset unification + warm drift→Full in phase 4. Format-changing PRs
use additive proto fields + dual-read for staggered rollout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… measured)

The 4.8× came from VictoriaMetrics lib/encoding WITH zstd-wrapping; since we
decided no-zstd (§5), the realized win is the no-zstd FOR+delta codec measured
in the intchunk PR: ~2.2-3.6× on fixed-decimal. A varint-residual refinement is
being added to push toward ~3×+ without zstd. §0 + §8 updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…#434)

Varint-residual sub-codecs landed in the intchunk best-of-N; on the Chimp/Serf
fixed-decimal class the aggregate moved 2.22x -> 2.33x vs Gorilla (up to ~3.6x
per series). Bottom line: no-zstd cold compression caps ~2.3x; the 4.8x needs
zstd (excluded). Gorilla-XOR stays the lossless fallback for true floats.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zzylol
zzylol merged commit b535972 into main May 25, 2026
@zzylol
zzylol deleted the docs/holistic-compression-design branch July 17, 2026 20:05
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.

1 participant