DDSketch
Suppose your app runs on many devices, and each one measures how long its requests take. You want to answer: "what's the typical response time?" and "how slow are the worst 1%?" — the median and the 99th percentile. Averages won't do (one slow outlier hides in an average), and collecting every raw measurement on one machine is exactly the kind of central bottleneck kuilt avoids.
DDSketch lets each device keep a small summary — a sketch — of its own measurements. Any two sketches can merge into one, and the merged sketch answers percentile questions about the combined measurements as if one machine had seen them all. You pick the precision up front: a sketch built with 1% relative accuracy answers any percentile within 1% of the true value — whether that value is 2 milliseconds or 2 minutes.
Converges to: the quantile summary of every value recorded on every replica, with each estimate within the configured relative accuracy (α) of the exact quantile.
Merging loses nothing
The key promise: merging is lossless. The sketch groups values into logarithmic buckets (each bucket spans a fixed ratio, so precision is relative, not absolute), and each bucket just counts how many values landed in it. Merging two sketches merges the counts, bucket by bucket — the result is exactly the sketch you would have built from the combined stream. Merge order doesn't matter, merging twice doesn't matter, and no accuracy is lost at the seams.
Each bucket's count is a GCounter (every replica owns its own slot, merged by maximum), which is what makes the merge safe to repeat: a message delivered twice never double-counts. That makes DDSketch a true CRDT — idempotent, commutative, associative — robust to kuilt's drop/duplicate/reorder delivery.
Two sketches merge only if they were built with the same configuration (accuracy and indexable range) — fix those once per deployment, like HyperLogLog's precision.
Code examples
Track latency percentiles on one node:
Two servers merge their sketches losslessly:
How the guarantee works
With relative accuracy α, the bucket boundaries grow by a constant factor γ = (1+α)/(1−α): bucket i covers values in (γ^(i−1), γ^i]. A value v is filed under index ⌈log_γ v⌉, and a quantile query returns the bucket's representative value 2γ^i/(γ+1) — the point whose worst-case relative error over the bucket is exactly α at both edges. Negative values use a mirrored bucket store; zeros are counted exactly in their own slot.
Memory is bounded without breaking the merge: values smaller in magnitude than minIndexedValue count as zeros, and values larger than maxIndexedValue clamp into the top bucket and increment a mergeable overflowCount — so a too-narrow range is observable, never silent. At the defaults (α = 0.01, range 10⁻⁹…10¹⁸) the sketch holds at most ≈3110 buckets per sign, and only buckets that actually receive values exist at all.
This is the algorithm from DDSketch: A Fast and Fully-Mergeable Quantile Sketch with Relative-Error Guarantees (Masson, Rim, Lee — PVLDB 2019), with the bucket counts lifted into GCounters so the merge is idempotent as well as lossless. The state maps one-to-one onto OpenTelemetry's ExponentialHistogramDataPoint (zero count + positive/negative log-bucket arrays), which is what makes it the natural mergeable backing for latency metrics.
When to prefer something else
You know the bucket boundaries you want (fixed SLA thresholds, say) —
Histogramcounts against explicit buckets exactly and maps to OTel's explicit-bucketHistogram.You need exact counts, not a distribution — use
GCounter/PNCounter.You need distinct-counting or frequency, not quantiles — those are
HyperLogLogandCountMinSketch.