All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[0.10.3] - 2026-08-17
Adds ExDataSketch.SampleData (with a Rust-accelerated fast path) so
every tutorial livebook's "Sample data" section is a single function
call, gives all 16 tutorials a richer front-matter section (mechanism
explainer, diagram, use-for/don't-use-for, measured benefits/numbers),
fixes two doc-site issues (Hex.pm package-page links resolving to raw
markdown; hexdocs.pm defaulting to the Modules tab instead of Pages),
and fixes two flaky tests found via repeated full-suite runs.
Added
ExDataSketch.SampleData-- one sample-data generator per tutorial livebook underlivebooks/sketches/, replacing the ~20-line cache-path/generate/cache-write block each livebook used to duplicate in its own "Sample data" cell with a single function call (e.g.ExDataSketch.SampleData.hll_events()). Randomized generators (the ones producing 1-2 million items) use a newExDataSketch.Backend.RustNIF when available for speed, added asnative/ex_data_sketch_nif/src/sample_data.rs(newrandcrate dependency); deterministic range-based generators are plain Elixir, since there's nothing for a NIF to accelerate in a linear string-formatting pass. Every generator accepts real size overrides (:count,:pool_size,:half_count, or a couple of Theta/IBLT-specific keys) on top of its tutorial-matching default -- caching only applies to the default (no-override) call, so a small overridden call (as used by this module's own test suite) never reads stale-shaped data from, or overwrites, the real livebook cache. Covered bytest/ex_data_sketch_sample_data_test.exs(Elixir, using small overridden counts) and#[cfg(test)]unit tests insample_data.rs(Rust, now run in CI'stest-rustjob via a newcargo teststep). Not part of the sketch API -- this exists purely to make the tutorials shorter and easier to read. Note: livebooks pinned to{:ex_data_sketch, "~> 0.10"}from Hex will not have this module until a release including it ships; running the updated tutorials against the current published package will raiseUndefinedFunctionErroruntil then.
Changed
- Every tutorial livebook under
livebooks/sketches/gained a richer "Introduction" section: a short mechanism explainer (with a Mermaid diagram for most families), explicit "Use it for" / "Don't use it for" guidance, and a benefits/numbers table comparing the sketch against the exact/naive alternative it replaces (memory, error rate, or the specific capability trade being made). Numbers are measured against this library's own implementation, not taken from external sources. mix.exs's ExDoc config:mainnow points at the Quick Start guide instead of theExDataSketchmodule page, so the hexdocs.pm homepage lands on the "Pages" sidebar tab instead of "Modules." "Core Concepts" (aggregation_wall.md,distributed_merge_semantics.md,hash_strategies.md) moved to the first position ingroups_for_extras, ahead of "Getting Started."README.md's links to the Quick Start and Livebooks guides now point at their hexdocs.pm URLs instead of repo-relativeguides/*.mdpaths -- the relative form rendered correctly on GitHub but resolved to a raw-markdown preview link (not the generated HTML page) on the Hex.pm package page, since Hex.pm rendersREADME.mdindependently of the ExDoc-generated site.
Fixed
- Two flaky tests in the test suite, both caused by tests leaving a
GenServer with an active fast recurring timer running after the test
itself returned.
test/ex_data_sketch_broadway_test.exs's "the automatic timer-driven flush does emit periodic_flush" test built aBroadway.PeriodicAggregatorwith a 20ms flush interval and never stopped it; under load its message backlog could keep draining (and keep firing[:ex_data_sketch, :pipeline, :periodic_flush], a process-wide:telemetryevent with no per-instance identifier in its metadata) after the test returned, landing a stray message in whatever other test was next listening on that same event -- observed astest/ex_data_sketch_broadway_test.exs's "manual flush/1 does not emit periodic_flush telemetry" test intermittently failing with "Unexpectedly received message :periodic_flush_fired". Fixed by stopping the aggregator synchronously at the end of its own test, and by movingExDataSketch.BroadwayTesttoasync: false-- this module fundamentally tests a global, unscoped side-channel that any otherasync: truemodule could in principle also emit on, so it no longer races the rest of the suite at all. Two similar leaks intest/ex_data_sketch_server_test.exs(both using a 50ms snapshot interval) fixed the same way. Also widenedtest/ex_data_sketch_server_test.exs's "a backend that raises during snapshot..." test'sassert_receivebound from 200ms to 2000ms -- a genuinely tight bound for a busy machine, independent of the leaks above.
[0.10.2] - 2026-08-14
Started as post-release fixes found via the same manual livebook-
verification process used for v0.10.1 (Cuckoo eviction cycling,
Quotient/CQF full-table decode on every call, CQF silent
overflow, REQ's KLL-shared compaction bug); grew to include
Application-config-driven per-family defaults and a FilterChain
batching fix.
Added
ExDataSketch.Config-- per-family default option overrides via a single flat Application config key, mirroring the existingconfig :ex_data_sketch, backend: ...pattern:config :ex_data_sketch, defaults: [ hll: [p: 16], cqf: [q: 20, r: 10], bloom: [capacity: 50_000] ]Every family's
new/1(orbuild/2forExDataSketch.XorFilter) now merges its configured defaults underneath whatever options are explicitly passed -- explicit options always win.FilterChain.new/0takes no options at all, so it has no corresponding:filter_chainentry. See the "Configuring Per-Family Defaults" section ofguides/usage_guide.mdandExDataSketch.Config's moduledoc.ExDataSketch.Quotient.member_many?/2-- tests membership for multiple items in a single pass, decoding the filter's header once instead of once per item (mirrors the existingput_many/2batch shape). Prefer this overEnum.map(items, &member?(filter, &1))when checking many items against the same filter, e.g. measuring false-positive rate over a large novel set.ExDataSketch.CQF.put!/2-- inserts a single item, raisingExDataSketch.Errors.FilterFullErroron overflow instead of returning an error tuple. Added for chaining convenience (new() |> put!(...) |> put!(...)), mirroringExDataSketch.Cuckoo.ExDataSketch.FilterChain.put_many/2-- batches an insert through each stage's ownput_many/2(Rust-accelerated where that stage's backend supports it) instead of looping single-itemput/2calls. Returns{:ok, chain}or{:error, :full, partial_chain}(stages before the failure fully applied).update_many/2now delegates to it (see Changed below). Added:put_manytocapabilities/0.
Changed
BREAKING:
ExDataSketch.FilterChain.delete/2now returns a baret()instead of{:ok, t()}. It never had a real error case to report -- a per-stage "item not found" is already absorbed as a no-op bydelete_stage/2, and a stage that doesn't support deletion raisesUnsupportedOperationErrorupfront rather than returning an error tuple. Existing callers doing{:ok, chain} = FilterChain.delete(...)need to drop the pattern match:chain = FilterChain.delete(...).ExDataSketch.FilterChain.update_many/2now delegates to the newput_many/2instead of reducing over single-itemupdate/2calls. The old implementation forced every stage onto the Pure backend (no family has a per-item Rust NIF) and reconstructed each stage's entire state binary per item, making a largeupdate_many/2call orders of magnitude slower than necessary regardless of the configured:backend; behavior (raisesFilterFullErroron overflow) is unchanged, only the cost.BREAKING:
ExDataSketch.CQF.put/2andput_many/2now return{:ok, cqf}/{:ok, cqf} | {:error, :full, partial_cqf}instead of a barecqf, mirroringExDataSketch.Cuckoo's existing contract. This is the other half of the fix described below -- detecting overflow requires a way to report it.update/2andupdate_many/2(the genericExDataSketch.Sketchbehaviour callbacks, which must return a bare sketch) now raiseExDataSketch.Errors.FilterFullErroron overflow instead, matching Cuckoo'supdate/2/update_many/2.from_enumerable/2likewise now returns{:ok, cqf} | {:error, :full, partial_cqf}. Existing callers using|>chains withput/2orput_many/2need to switch toput!/2or explicit{:ok, cqf} = ...pattern matching.
Fixed
ExDataSketch.CQFsilently dropped inserts once its table filled up, in both backends, discovered while investigating why a livebook'sput_many/2call over a 1,000,000-event dataset was taking far longer than expected (see the sizing fix below for that side of the investigation). CQF shares its slot layout and shift-right insertion machinery withExDataSketch.Quotient; that machinery's cascade loop was bounded (toslot_countsteps, so it always terminates rather than looping forever on a full table) but had no way to report back that it hadn't found room -- it just stopped, silently leaving the requested item uninserted. UnlikeExDataSketch.Cuckoo, which already had{:error, :full}for exactly this situation, CQF (and Quotient) had no such signal at all. Fixed for CQF by threading a success/failure result through the insertion call chain in both the Pure backend (immutable, so a failed attempt's partial mutations are simply never bound and thus discarded automatically) and the Rust NIF (mutatesVec<Slot>in place, so each insertion path now checks whether its shift-right cascade would fit before applying any mutation -- including preparatory metadata-bit changes -- rather than mutating first and discovering failure partway through, which would have left the table in a structurally inconsistent state for other entries, not just failed the one insert cleanly). Verified: Pure and Rust report:fullat the identical point and produce byte-identical partial state for the same over-capacity input; every item inserted before the failure point remains a correctly-retrievable member; round-tripping the partial state through serialize/deserialize preserves it exactly.ExDataSketch.Quotient's equivalent internal fix is present too (same shared machinery), but surfaces as a safe no-op (a full table simply stops growing) rather than a public{:error, :full}, since Quotient'sput/2/put_many/2API wasn't part of this change -- previously, Quotient's Pure backend could genuinely hang forever inserting into an exactly-100%-full table (no bound existed on that loop at all); it now terminates safely. SeeExDataSketch.CQF's andExDataSketch.Quotient's moduledocs for details.ExDataSketch.CQF.put_many/2(and the tutorial's other full-dataset cells) could take minutes to hours because the demo'sq: 18sized the table for 50,000 distinct keys when it needed to be sized for 1,000,000 total occurrences -- CQF spends one physical slot per occurrence of an item, not per distinct item (seeExDataSketch.CQF's "Counter Encoding" and "Sizing:q" moduledoc sections, the latter also corrected here: it previously described a compact "bracketing" counter scheme that was never actually implemented in either backend -- the real representation is, and always was, one physical slot per duplicate occurrence). Confirmed:q: 18(262,144 slots, ~25% of the 1,000,000 occurrences the demo dataset needs) ran for over 60 minutes without finishingput_many/2;q: 21(2,097,152 slots, ~2x headroom) finishes the identical call in under a second. Fixed the livebook'sqvalues and added a "Sizing: q must budget for total occurrences, not distinct keys" section explaining why, with a worked example.ExDataSketch.REQhad the same compaction weight-preservation bug asExDataSketch.KLL's v0.10.1 fix, independently discovered via the same manual livebook-verification process.req_compact_level's biased compaction promotes half of whichever portion (upper for LRA, lower for HRA) is being compacted, at double the weight; that only preserves total weight when the portion being halved has an even length. Portion length isdiv(n, 2), frequently odd, so -- exactly as with KLL -- every odd-length compaction silently gained or lost one item's worth of weight, corrupting thesum(retained_weight) == ninvariant thatquantile/2depends on. Confirmed via the same weight-invariant check used for the KLL fix (drift growing withn, both HRA and LRA affected identically since the bug is in the shared halving step, not the HRA/LRA bias itself). Symptom: HRA and LRA modes could produce visibly wrong relative behavior (e.g. HRA -- which is supposed to bias accuracy toward high ranks -- performing worse than LRA at p99.9). Fixed with the same technique as KLL: hold back one item (unweighted, for a future compaction) whenever the portion being compacted has an odd length. Verified the weight invariant now holds exactly, and that HRA is measurably more accurate than LRA at high ranks once again (previously, at smallk, the bug made the two modes nearly indistinguishable). No binary format change -- old serialized sketches still decode and work. SeeExDataSketch.REQ's moduledoc for why, similarly to KLL, value error at a specific query can still be large near a sharp change in data density even with this fixed -- that part is inherent to rank-approximate sketches, not a bug.ExDataSketch.Cuckoocould spuriously return{:error, :full, ...}well below its designed ~95.5% load factor, in both the Pure and Rust backends, discovered via the same manual livebook-verification process. The kick-eviction loop chose which slot within a bucket to evict with a plainrem(fingerprint + kick_count, bucket_size)-- a linear function of both inputs. With only2^fingerprint_sizepossible fingerprint values (256 for the default 8-bit size) shared across a much larger item count, two colliding fingerprints that happened to differ by a multiple ofbucket_sizecould synchronize the kick sequence into a short, exactly-repeating cycle among a handful of buckets, burning through every remaining kick without ever finding an empty slot. Confirmed via direct source-level tracing: a real dataset (500,000 sequential"session_N"keys intoCuckoo.new(capacity: 500_000)) hit a 4-step cycle between 3 buckets that exhausted:max_kicksevery time, well below the table's intended capacity. Fixed by routing the evicted fingerprint and kick count through the same hash already used for fingerprint mixing before reducing modbucket_size, which keeps slot choice fully deterministic (same input sequence still always produces the same sketch state) while eliminating the arithmetic periodicity that caused the cycling. Ported identically to the Rust NIF backend; confirmed the two backends still produce byte-identical serialized state for identical input. Because the eviction-slot formula changed, any Cuckoo filter build that exercises kick-eviction now produces a different (but now non-cyclic) final state than before -- this is a behavior change, not a binary format change; old serialized sketches still decode and work. SeeExDataSketch.Cuckoo's moduledoc for details.ExDataSketch.Quotient.member?/2(anddelete/2's lookup phase) could take minutes for what should be a near-instant check, discovered via the same manual livebook-verification process. Both always decoded the entire slot table (2^qslots) into a tuple before reading the one run they actually needed, regardless of which:backendwas configured -- there is no per-item Rust NIF for these (onlyput_many/2has one), so every call paid this cost. Confirmed: 300,000 sequentialmember?/2calls against aq: 19(524,288-slot) filter took roughly 40 minutes. Fixed by adding a lazy decode path that parses only the 32-byte header and reads individual slots directly from the binary at their byte offset on demand, so a lookup now costs O(run length) -- typically a handful of slots -- instead of O(slot_count).delete/2additionally now skips the encode round-trip entirely when the item isn't present. Confirmed: the same 300,000 calls now take ~0.13s total. No format or behavior change -- same results, just fast. SeeExDataSketch.Quotient's moduledoc for details.ExDataSketch.CQF.member?/2,estimate_count/2, anddelete/2had the identical bug, found while investigating the same class of issue forExDataSketch.Quotientabove (CQF shares Quotient's slot layout and decode/encode machinery). Confirmed: ~190ms per singlemember?/2call against aq: 18(262,144-slot) filter, purely from the full-table decode. Fixed the same way. SeeExDataSketch.CQF's moduledoc for details.livebooks/sketches/.verify_extract.exs, the harness that automatically re-runs every tutorial livebook's cells to catch regressions, extracted every cell except theMix.installcell itself -- so a livebook's ownconfig:block (e.g.backend: ExDataSketch.Backend.Rust) was silently never applied during verification, and every livebook ran on the Pure backend regardless of what it configured. This is how theFilterChain.put_many/2slowness above went unnoticed by the harness. Fixed by parsing theMix.installcell'sconfig:option viaCode.string_to_quoted/1and applying it viaApplication.put_env/3before the remaining cells run.ExDataSketch.REQ's moduledoc now documents that it has no NIF acceleration (ExDataSketch.Backend.Rust'sreq_*functions are a thin pass-through toExDataSketch.Backend.Pure), matching the noteExDataSketch.MisraGriesalready carried -- previously undocumented, so:backendsilently had no effect on REQ's performance either way.ExDataSketch.Bloom.put_many/2's Pure backend was asymptotically slower than looping single-itemput/2, not faster, for any batch small relative to the filter's bit-array size -- caught by the newFilterChain.put_many/2performance-regression test flaking on CI (passed locally, intermittently exceeded its bound on GitHub's slower runners) rather than by a functional test, since the output was always correct. The implementation converted the bitset to an Erlang tuple with the stated intent of getting O(1) destructive updates viaput_elem/3in a reduce loop -- but that optimization requires the tuple to have a single owner at the point of update, a conditionEnum.reduce/3's closure-based iteration does not reliably preserve. Eachput_elem/3call was silently a full O(bit_array_size) tuple copy, making a batch ofnitems withhash_counthashes each cost O(n hash_count bit_array_size) instead of the intended O(n hash_count). Confirmed: 1,000 items into a 500,000-capacity Bloom (~300,000-byte bitset) took 1.8-2.6s -- slower than the 1,000 single-itemput/2calls it was meant to beat (~0.25s). Fixed by collecting the scattered bit positions into abyte_index => or_maskmap first, then applying it in a single linear pass over the bitset, which is genuinely O(n hash_count + bit_array_size): the same 1,000-item batch now takes ~80ms, both well under the single-item loop and, unlike the tuple approach, actually scales as intended. Verified byte-identical output to the pre-fix implementation and to an equivalent single-itemput/2loop. This bug predates v0.10.2 (it shipped with the original Bloom filter in v0.4.0); it was only surfaced now becauseFilterChain.put_many/2(new in this release) is the first caller to exerciseBloom.put_many/2at a capacity large enough, with a batch small enough relative to it, to make the quadratic-ish cost visible in a timed test.
[0.10.1] - 2026-08-11
Started as post-release fixes from a full code review of the v0.10.0 diff
(baoulo/reviews/0.10.0_code_review.md); grew to include two significant
correctness fixes found via manual livebook verification (ULL, KLL),
a precision-range widening (HLL), and 16 new per-family tutorial
Livebooks plus a runnable Phoenix demo app.
Added
livebooks/sketches/-- one tutorial livebook per sketch family (16 total:hll,ull,cms,theta,kll,ddsketch,req,frequent_items,misra_gries,bloom,cuckoo,quotient,cqf,xor_filter,iblt,filter_chain). Each generates its own sample data and caches it underSystem.tmp_dir!(), regenerating only if the cache file is missing, so re-running a livebook (or all 16 in sequence) after the first pass is fast.phoenix_demo/-- a minimal, real, runnable Phoenix app demonstratingExDataSketch.LiveDashboard.PageandExDataSketch.Telemetry.Metrics.all/1wired into an actualrouter.ex/telemetry.ex, plus a live homepage backed by two supervisedExDataSketch.Serverinstances and a background traffic simulator. Seephoenix_demo/README.md.guides/telemetry.md: a "Window Events" and a "Server Events" section ([:ex_data_sketch, :window, :roll]and the five:serverevents), missing since those categories shipped -- the guide previously only documented:sketch/:persistence/:stream/:pipeline. Also adds a:window, :rollhandler example (migrated from the now-removedrolling_telemetry.livemd).
Fixed
ExDataSketch.KLL's compaction violated its own weight-preservation invariant, corrupting quantile/rank accuracy for any sketch that ever compacted an odd-length level.kll_compact_level(both the Pure Elixir and Rust NIF backends) cleared an entire level and promoted only half its items regardless of parity; for an odd-length level, that either promotesceil(len/2)orfloor(len/2)items, and neither equalslen/2exactly -- so the "half the items at double the weight" step that's supposed to exactly preserve total weight instead silently gains or loses one item's worth of weight (2^level) every time. Level capacities (max(2, floor(k * (2/3)^depth) + 1)) are frequently odd, so this was the common case, not an edge case: the sum of retained sample weights (whichquantile/2/rank/2divide by to compute a target rank) drifted further from the true item countnwith every such compaction -- confirmed to reach >10% relative drift by 1,000,000 inserts in one measurement. The result was wildly incorrect, and non-monotonic ink, on any distribution with a sharp density change near the queried rank (uniform data was largely unaffected; a synthetic 99%-base/1%-tail mixture queried at p99 showed errors from tens to hundreds of percent, inconsistently acrossk). Fixed by holding back one item (leaving it at the current level, unweighted, for a future compaction) whenever a level has an odd length, so the actually-compacted subset always has even length and total weight is preserved exactly -- verified by checkingsum(retained_weight) == nholds exactly acrossnfrom 1,000 to 1,000,000 (previously off by up to +104,235 atn=1,000,000), and by confirming Pure and Rust backends now produce identical quantile estimates. No binary format change -- old serialized sketches still decode and work, they just carry forward whatever inaccuracy was baked in at serialization time; newly-built sketches are correct going forward. SeeExDataSketch.KLL's moduledoc for why value error (as opposed to the documented rank error bound) can still be large when querying exactly at a distribution's density cliff -- that part is inherent to rank-approximate sketches generally, not this bug.ExDataSketch.ULLimplemented the wrong algorithm. Both backends' register update/merge/estimate were an HLL-derived approximation (a single sub-bucket bit plus Ertl 2017's HLL sigma/tau estimator), not Ertl 2023's actual UltraLogLog. It matched real UltraLogLog closely at low cardinalities (where every prior test and property check exercised it, up ton=10,000atp=14) but diverged sharply once every register had been touched at least once (zeros == 0, the FGRA branch) -- overestimating by orders of magnitude at moderate-to-largenrelative tom, at every precision. Both backends are now a direct, numerically verified port of hash4j'sUltraLogLog.java(the reference Ertl 2023 implementation): the real pack/unpack register encoding (a compressed 3-bit window per byte: geometric rank plus a 2-bit sub-bucket refinement) and the realOptimalFGRAEstimator(closed-form small-range/large-range correction terms plus a 236-entry per-register contribution table, combined viasum^(-1/tau) * factor[p]). Verified byte-for-byte (register state) and to within1e-6relative error (estimate) against a compiled Java reference acrosspin{10,12,14,16}andnup to 2,000,000, plus insertion-order independence and merge-vs-single-sketch equivalence checks. Measured accuracy is now~0.70/sqrt(m)RSE (previously documented as~0.835/sqrt(m), a number derived from the wrong estimator) -- about 30% better than HLL's~1.04/sqrt(m), not the previously claimed ~20%. TheULL1state binary format version is bumped 1 -> 2; version-1 binaries are rejected on decode with a clear error (see Migration).ExDataSketch.Telemetry.Metrics.all/1's twocountermetrics (sketch.ingest.count,stream.reduce.count) never actually fired in any realTelemetry.Metricsreporter (Phoenix LiveDashboard included).event_counter/4relied onTelemetry.Metrics.counter/2's implicit:measurementdefault (the metric name's own last segment,:count), but noExDataSketchevent carries a:countkey in its measurements (sketch.ingest's are:duration/:size_bytes;stream.reducehas none at all) -- percounter/2's own docs, "the measurement must still be available in the event, otherwise the event is not accounted for." Both counters now use an explicit constant:measurementfunction so they actually count every occurrence, regardless of that event's real measurement keys.Critical:
ExDataSketch.Servernow traps exits, so a supervisor- initiated shutdown (not just an explicitGenServer.stop/2) snapshots before terminating, per its documented graceful-shutdown guarantee.Critical: the
opencodeGitHub Actions workflow no longer runs untrusted PR-comment triggers with base-repo secrets against an unpinned third-party action; trigger is now restricted to owner/member/collaborator comments and the action is pinned to a commit SHA.Storage.merge/3(ETS, DETS, CubDB, Mnesia, Ecto) returns{:error, _}instead of crashing when the stored binary is corrupted or from an incompatible sketch version.ExDataSketch.Serverno longer crashes if a snapshot backend raises; the failure is caught and reported via the new[:ex_data_sketch, :server, :snapshot_failed]telemetry event instead.ExDataSketch.CMS.capabilities/0no longer falsely claims:estimate(CMS only supports point queries viaCMS.estimate/2, not the generic single-valueestimate/1).GenStage integration modules (
SketchConsumer,SketchProducer,SketchStage) now honorconfig :ex_data_sketch, :integrations, gen_stage: falseinstead of silently ignoring it.:hash_strategywas silently dropped bynew/1/build/2across all six membership-filter families (Bloom,Cuckoo,Quotient,CQF,IBLT,XorFilter), making the option a no-op: hashing always fell back to the NIF-availability default regardless of what was requested, and the:v1-format "requires:phash2" guard always passed trivially as a result.:hash_strategy(including:murmur3, used for Apache DataSketches interop) is now resolved viaExDataSketch.Hash.resolve_strategy/1at build time and actually honored, matchingExDataSketch.HLL's existing behavior.The same six filter families now restore
:hash_strategyfrom the EXSK v2 metadata block ondeserialize/1(previously it was discarded, so a filter built with a non-default hash strategy and round-tripped through serialize/deserialize would silently query membership with the wrong algorithm afterward).ExDataSketch.merge/2on anExDataSketch.WindowandExDataSketch.merge_many/1on an empty list now raise the library's own typed errors instead of a rawUndefinedFunctionError/FunctionClauseError.The Apache DataSketches KLL decoder (
deserialize_datasketches/2) now validates thatnis not smaller than the reconstructed retained-item count, and that level boundaries are monotonically non-decreasing, rejecting corrupted input with aDeserializationErrorinstead of crashing or silently misreading it.ExDataSketch.Cuckoo.put!/2/update_many/2andExDataSketch.FilterChain.update/2now raiseExDataSketch.Errors.FilterFullErrorinstead of a bareRuntimeErrorwhen the underlying filter is full.Fixed a
Storage.resolve_backend/1ambiguity: a bare 2-tuple ref whose first element isn't an atom implementing theStoragebehaviour is now resolved as a whole against the configured default backend, instead of being misread as an explicit{backend_module, ref}pair.
Removed
livebooks/livedashboard_integration.livemdandlivebooks/phoenix_observability.livemd, superseded byphoenix_demo/-- both consisted mostly of commented-out router/application pseudocode wherephoenix_demohas working code.livebooks/rolling_telemetry.livemd-- itsExDataSketch.Windowcontent (basic usage, deterministic testing, persistence) was already covered in more depth byguides/windowing.md; its telemetry section moved toguides/telemetry.md; its live-dashboard demonstration is superseded byphoenix_demo/.
Changed
ExDataSketch.HLL's maximum precision raised fromp=16top=26, matchingExDataSketch.ULL's range. Investigation found no algorithmic reason for the oldp<=16ceiling: registers are a plain byte each (no bit-packing to overflow), and thealpha(m)bias-correction constant's general formula (0.7213 / (1 + 1.079/m)) is valid for anym >= 128-- it was simply never raised.p>=4remains a hard floor:alpha(m)only has defined cases form = 2^p in {16, 32, 64}plus the general formula form >= 128, which together coverp >= 4exactly. SeeExDataSketch.HLL's new "Precision Range" moduledoc section (andExDataSketch.ULL's, added for contrast -- ULL's ownp<=26ceiling is a hard limit, bounded by a 24-entry estimator lookup table).lib/ex_data_sketch/telemetry.ex's own moduledoc claimed:ingest'ssize_bytesmeasurement was "HLL only" -- wrong in both directions: 13 of the 14 families that emit:ingestreport it (all butCuckoo, whoseput_many/2returns a tagged tuple rather than a bare sketch), andXorFilter/FilterChaindon't emit:ingestat all. Corrected to describe actual per-family coverage.- Raised the minimum supported Elixir version from
~> 1.15to~> 1.18to match what CI actually tests (the 1.15-1.17 floor was never exercised by any CI leg). - Corrected
ExDataSketch.Storage.DETS's documentation, which previously overstatedmerge/3's atomicity under concurrent writers; it performs the same non-atomic read-modify-write cycle asExDataSketch.Storage.ETS. - Corrected the
ExDataSketch.Windowandguides/windowing.mdexplanation of tumbling-window history bounds, which had the start-of-slot/end-of-slot cases inverted. guides/observability.mdnow documentsExDataSketch.Telemetry.Metrics.all/1andExDataSketch.LiveDashboard.Page(added in the v0.10.0 Phase 5 work) instead of the hand-written:telemetry.attach/Telemetry.Metricssnippets they were built to replace.- Minor documentation corrections:
ExDataSketch.Sketch's claim about howSketchConsumerdispatches,Storage'smerge/3callback doc (which groupedExDataSketch.Storage.CubDBwith the non-atomic backends even though its ownmerge/3uses aCubDB.transaction/2),Storage'schild_spec/1callback doc (impliedCubDBimplements it; none of the five shipped backends do), and the bare-ref resolution error message (previously said "no backend module given" even when a structurally plausible but invalid one was given).
Migration
ExDataSketch.ULLbinary format bump (v1 -> v2). Sketches serialized by prior releases will fail to decode with"unsupported ULL state version 1, expected 2"rather than silently producing the old, significantly overestimated cardinality. This is intentional: the register encoding itself changed (not just a wrapper), so there is no way to reinterpret old state correctly. If you have persisted ULL sketches (snapshots, ETS/DETS/CubDB/Ecto storage backends,ExDataSketch.Serversnapshot files), rebuild them from source data after upgrading. HLL and every other family are unaffected -- this is aULL-only, algorithm-only fix.- ULL estimates will change for any existing sketch once rebuilt --
they are now correct rather than overestimated once a sketch's
registers fill up (
ncomparable to or larger thanm). If you assert specific numeric ULL estimates in tests, expect them to shift toward the true cardinality.
[0.10.0] - 2026-08-07
Release theme: Production Ergonomics. Closes the gap between "here is a
sketch struct" and "here is a production counter that answers questions
about the last five minutes": a unified sketch contract and facade
dispatch, a storage behaviour, windowing, supervised sketch processes
(Server/Sketches), ready-made Telemetry.Metrics + LiveDashboard
integration, raw-hashing NIF parity for the membership filters, Apache
KLL interop, and a format: :v1 rolling-upgrade escape hatch on every
sketch family.
Added
ExDataSketch.Sketch-- the unified behaviour every concrete sketch family now implements (serialize/1,deserialize/1,size_bytes/1,capabilities/0required;new/1,update/2,update_many/2,merge/2optional where a family does not support the operation). AddsExDataSketch.Sketch.implemented?/1to check compliance.ExDataSketch.sketches/0-- the registry mapping each family atom (:hll,:bloom, and so on) to its module; single source of truth for the capability-matrix test andExDataSketch.Sketch.implemented?/1.Top-level facade dispatch functions on
ExDataSketch:new/2,update/2,merge/2,merge_many/1,estimate/1,serialize/1,deserialize/2,size_bytes/1,capabilities/1. These are additive -- every per-family module's own API is unchanged and remains the documented primary interface. Seeguides/(forthcoming) andbaoulo/plans/0.10.0_phase1_stub_review.mdfor the full design, including the small set of documented family-specific exceptions (XorFilterhas no incrementalupdate/2;Cuckoo,XorFilter, andFilterChainhave nomerge/2;CMSand the membership filters have no single-valueestimate/1).update/2andupdate_many/2onBloom,Cuckoo,Quotient,CQF,IBLT, andFilterChain-- additive aliases forput/2/put_many/2(the family-idiomatic names, unchanged) so every family satisfiesExDataSketch.Sketch's generic callbacks.capabilities/0onHLL,CMS,Theta,KLL,DDSketch,REQ,FrequentItems,MisraGries, andULL, matching theMapSet.t(atom())vocabulary already shipped on the membership filter modules.ExDataSketch.Storagepromoted to a real behaviour (@callback save/3,load/3,merge/3,delete/2, optionalchild_spec/1), implemented by all five persistence backends (ETS,DETS,CubDB,Mnesia,Ecto). AddsExDataSketch.Storage.backends/0(the atom-to-module registry) and a dispatching facade --save/3,load/3,merge/3,delete/2onExDataSketch.Storageitself -- that accepts either an explicit{backend_module, ref}pair or a barerefresolved against a newly configurable default backend:config :ex_data_sketch, :storage, backend: ExDataSketch.Storage.ETSExDataSketch.Window-- a ring of tumbling sub-sketches for "in the last N" questions without a hand-rolled timer:window = ExDataSketch.Window.new(:hll, [p: 14], every: :timer.minutes(1), keep: 5) window = ExDataSketch.Window.update(window, user_id) ExDataSketch.Window.estimate(window)new/3accepts a registry atom (:hll) or a sketch module directly; windowing requires a mergeable family (Cuckoo,XorFilter, andFilterChainare rejected with a clear error). The clock defaults toSystem.monotonic_time/1and is fully injectable (:time_fnat construction, an explicitnowonupdate/3andtick/2), so tests never sleep. Adds the:windowtelemetry category and the[:ex_data_sketch, :window, :roll]event. Seeguides/windowing.mdandbaoulo/plans/0.10.0_phase3_stub_review.mdfor the full design, including what "the last N minutes" actually means for a tumbling (not exact sliding) window.ExDataSketch.Server-- a supervised, named, concurrently-updatable sketch process wrapping a single sketch orExDataSketch.Window:{:ok, _pid} = ExDataSketch.Server.start_link( name: :uniques, sketch: :hll, sketch_opts: [p: 14] ) ExDataSketch.Server.update(:uniques, user_id) ExDataSketch.Server.estimate(:uniques)update/2andupdate_many/2are casts, droppable under an optional:max_queuefor bounded-memory backpressure (emitting[:ex_data_sketch, :server, :drop]);update_sync/2is a call, never dropped. Optional:windowholds aExDataSketch.Windowinstead of a bare sketch, withtrack_all_time: truemaintaining a second un-windowed accumulator readable viaestimate(server, window: :all);merge/2is not supported on a windowed server this release (tracked inbaoulo/plans/plan-0.10.0.mdsection 9). Optional:snapshotpersists state to anyExDataSketch.Storagebackend periodically and on graceful shutdown, and restores on start (crash recovery); an untrappableProcess.exit(pid, :kill)bypasses the graceful-shutdown snapshot, so worst-case loss in that specific case is bounded by the snapshot interval, not zero. Optional:flushprovides a return-and-reset-on-a- timer pattern. Seeguides/supervised_sketches.mdandbaoulo/plans/0.10.0_phase4_design_review.mdfor the full design.ExDataSketch.Sketches-- a supervisor for starting manyExDataSketch.Serverprocesses at runtime, addressed by an arbitrary term instead of a compile-time name:children = [{ExDataSketch.Sketches, name: MyApp.Sketches}] {:ok, _pid} = ExDataSketch.Sketches.start_child(MyApp.Sketches, tenant_id, sketch: :hll, sketch_opts: [p: 14]) ExDataSketch.Server.update(ExDataSketch.Sketches.via(MyApp.Sketches, tenant_id), user_id)Backed by a
RegistryandDynamicSupervisorper instance. Adds the:servertelemetry category and the[:ex_data_sketch, :server, :snapshot],[:ex_data_sketch, :server, :restore],[:ex_data_sketch, :server, :flush], and[:ex_data_sketch, :server, :drop]events. Seeguides/supervised_sketches.md.ExDataSketch.Telemetry.Metrics.all/1-- a ready-madeTelemetry.Metricsdefinition for every one of the 17 eventsExDataSketch.Telemetry.all_event_names/0returns, for wiring straight into aTelemetry.Metricsreporter or Phoenix LiveDashboard instead of hand-writing one metric per event:def metrics do ExDataSketch.Telemetry.Metrics.all() ++ [ # ... your application's own metrics ] endAccepts
prefix:to namespace metric names differently; the underlying:telemetryevent listened to is unaffected. Adds{:telemetry_metrics, "~> 1.0"}as a normal (non-optional) dependency -- it defines only struct types, with no runtime process or side effect.ExDataSketch.LiveDashboard.Page-- an optionalPhoenix.LiveDashboard.PageBuilderpage listing every ExDataSketch telemetry event and the metric namesExDataSketch.Telemetry.Metrics.all/1derives from them:live_dashboard "/dashboard", additional_pages: [sketches: ExDataSketch.LiveDashboard.Page]A static reference page, not a live view of any particular running sketch (it cannot know which
ExDataSketch.Serverinstances a host application started). Only exists when the new optional{:phoenix_live_dashboard, "~> 0.8", optional: true}dependency is loaded. Seebaoulo/plans/0.10.0_phase5_stub_review.mdfor the full design.ExDataSketch.KLL.serialize_datasketches/2anddeserialize_datasketches/2-- Apache DataSketches KLL interop (compactKllFloatsSketch/KllDoublesSketch), replacing thenot_implemented!stubs (v0.10.0 Phase 7, closing G7 from the v0.9.0 code review). Unlike Theta, KLL does not hash its inputs, so this is a full item-level round trip with no hash-equality caveat -- decoding a Java/C++/Python-produced sketch and queryingquantile/2/rank/2answers using the exact retained values the other implementation selected. Takes a:variantoption (:floator:double, default:double); Apache's wire format doesn't self-describe item width, so the caller must know it in advance, same as choosing betweenKllFloatsSketch/KllDoublesSketchin Java. Backed by a newExDataSketch.DataSketches.KLLSketchcodec module (mirroringCompactSketch's shape) and aBackend.kll_from_components/5callback (mirroringtheta_from_components/3). Verified against the realdatasketchesPython package (not just against our own decoder) in both directions -- decoding its output and having it decode ours -- across a sweep of item counts andkvalues from 0 to 500,000; seeguides/apache_interop.md.test/fixtures/interop/kll/carries the first golden cross-language fixture corpus actually generated and committed for any family (the processtest/vectors/CROSS_LANGUAGE.mddocumented for Theta was never executed until now), pinned todatasketches5.2.0.guides/apache_interop.md-- consolidated reference for what interoperates with Apache DataSketches (Theta, KLL) and what doesn't (HLL -- now targeted at v0.11.0; CMS -- not planned, no standard format exists), superseding the scattered mentions previously spread across sketch moduledocs.serialize(sketch, format: :v1)-- the opt-in legacy EXSK v1 escape hatch, previously HLL-only, is now available on every sketch family exceptFilterChain(which has its own bespoke FCN1 container format with noCodec.sketch_idof its own -- see its moduledoc). v1 output is compatible with v0.7.x readers, for use during rolling upgrades. Families with a:hash_strategyoption require:phash2for v1, same as HLL; families with no hash-strategy concept (KLL, DDSketch, REQ, FrequentItems, MisraGries) have no such restriction.Codec.encode/4(already generic across everysketch_id_*) needed no changes -- every family's state binary is unchanged between v1 and v2 eras, the difference is purely the frame wrapper (v2 adds a hash-algorithm metadata block + CRC32C trailer; v1 has neither).ci/check_roadmap.exs-- assertsREADME.md's roadmap table has a row for the versionmix.exscurrently declares, and (once that version has no-devsuffix) that the row says "Released", the install snippet matches, andguides/roadmap.mdhas moved on to previewing the next release. Closes a twice-deferred carry-forward item (X-R2).
Changed
Bloom.put_many/2,Cuckoo.put_many/2,Quotient.put_many/2,CQF.put_many/2,IBLT.put_many/2, andXorFilter.build/2now hash items inside the Rust NIF itself when usingBackend.Rustwith the default hash strategy (:xxhash3/:murmur3), instead of hashing on the BEAM and passing pre-hashed integers across the NIF boundary -- extending the raw-hashing architecture HLL/CMS/Theta/ULL already had (guides/hll_performance.md) to the six membership filters (G6 from the v0.9.0 code review, deferred twice, now closed). Measured 2.2x- 12.8x faster than the pre-hashed Rust path and up to ~2,700x faster than Pure Elixir, depending on family -- seeguides/filter_performance.mdfor the full measured table. A custom:hash_fnor:hash_strategy: :phash2still falls back to the pre-hashed path exactly as before; behavior and serialized output are unchanged, only where the hash is computed.test/parity_test.exsalready asserted byte-identical Pure/Rust output for every affected family under default options, so it now exercises the raw path automatically; new tests were added only for the:hash_fnfallback case (one per family) and were not previously covered for any raw-hashing family.ExDataSketch.Broadway.PeriodicAggregatoris now a thin wrapper around a:flush-configuredExDataSketch.Server, delegating every call. Its public API (start_link/1,merge/2,flush/1,get/1,estimate/1) is unchanged. Correction (originally published as "unchanged," which was wrong): the[:ex_data_sketch, :pipeline, :periodic_flush]telemetry event's scope narrowed. In v0.9.0 it fired on every call toflush/1, manual or automatic. In v0.10.0 it fires only on the automatic, timer-driven path --Server.flush/1's manual call does not invoke the:flushcallback that emits it (seeExDataSketch.Server's moduledoc, which already documented this correctly; only this CHANGELOG entry had the wrong claim). If you relied on this event firing from a manualPeriodicAggregator.flush/1call, it no longer will; use the[:ex_data_sketch, :server, :flush]event instead, which fires on both paths.ExDataSketch.update_many/2now dispatches generically via theSketchbehaviour instead of 13 hand-written struct clauses, extending coverage to 15 of 16 families (all but the immutableXorFilter, which raisesExDataSketch.Errors.UnsupportedOperationError).ExDataSketch.GenStage.SketchConsumeringests raw event batches viasketch_module.update_many/2directly instead of afrom_enumerable/2capability probe followed by a merge; behavior is unchanged, with one fewer intermediate sketch allocation per batch.ExDataSketch.FilterChain's internal capability checks now useExDataSketch.Sketch.implemented?/1instead of rawfunction_exported?/3.Removed the unused
save_opts,load_opts,merge_opts, anddelete_optstypes fromExDataSketch.Storage-- they described a[table: atom()]keyword-list shape no backend ever used (every backend takes its table/db/repo as a positional argument) and were referenced nowhere inlib/ortest/.Fixed a
:telemetry.attach/4local-function-capture performance warning inExDataSketch.Telemetry.OpenTelemetry.setup/0(it now attaches a remote function capture).Wired up
doctestforDDSketch,REQ,FrequentItems,MisraGries, and all 7 membership filter modules -- their existing@docexamples were never executed by the test suite before this release.test/property_guarantees_test.exs's bit-flip corruption-propagation property now covers all 15Codec-backed sketch families (previously HLL/ULL/CMS only) instead of a hardcoded 3-family list. Closes a twice-deferred carry-forward item (P5R4). Construction is factored into a newtest/support/sketch_fixtures.ex(ExDataSketch.SketchFixtures) shared with the generalized v1 escape-hatch tests, which handles the three constructor return shapes in play (bare struct; Cuckoo's{:ok, t()} | {:error, :full, t()}; XorFilter'sbuild/2returning{:ok, t()} | {:error, :build_failed}since it has nofrom_enumerable/2).FilterChainis excluded (bespoke construction shape wrapping sub-sketches, each already covered independently).
Fixed
- Removed stray
erl_crash.dumpand.DS_Storefrom the repository;.DS_Storeis now gitignored.
[0.9.0] - 2026-05-19
Release theme: Streaming Integrations. Transforms ex_data_sketch from a collection of probabilistic algorithms into a BEAM-native streaming approximate analytics infrastructure layer. Stream/Collectable integration, Broadway/GenStage/Flow pipelines, five persistence backends, production-grade telemetry + OpenTelemetry, ULL accuracy fixes, and comprehensive educational materials.
Added
Stream and Collectable integration (Phase 1).
ExDataSketch.Stream-- terminal stream consumers (hll/2,cms/2,theta/2,ull/2,kll/2,ddsketch/2,req/2,bloom/2,quotient/2,cqf/2,iblt/2,frequent_items/2,misra_gries//2).ExDataSketch.Stream.reduce_into/3-- reduce an enumerable into a module or existing sketch.ExDataSketch.Stream.reduce_partitioned/3-- partitioned parallel reduction with merge.Collectableprotocol for all mergeable sketches --Enum.into/2andforcomprehensions.from_enumerable/2on all 13 mergeable sketch modules.reducer/1andmerger/1on all mergeable sketch modules forEnum.reduce/3andFlow.reduce/3ergonomics.
Broadway, GenStage, and Flow integration (Phase 2).
ExDataSketch.Broadway.accumulate/3andaccumulate_into/4-- build sketches from Broadway message batches.ExDataSketch.Broadway.PeriodicAggregator-- GenServer that accumulates sketches and flushes on a timer with optional callback.ExDataSketch.GenStage.SketchConsumer-- GenStage consumer that accumulates events into a sketch, supports periodic flush.ExDataSketch.GenStage.SketchProducer-- GenStage producer that emits accumulated sketches on demand.ExDataSketch.GenStage.SketchStage-- combined producer-consumer that accumulates and emits.ExDataSketch.Flow.reduce/3andmerge/2-- parallel partition-local reduction with merge for Flow pipelines.- All integration modules are optional and gated behind dependency availability checks (
ExDataSketch.Integration).
Persistence surfaces (Phase 3).
ExDataSketch.Storage.ETS-- in-memory persistence withsave/3,load/3,merge/3,delete/2.ExDataSketch.Storage.DETS-- disk-backed persistence with same API.ExDataSketch.Storage.CubDB-- CubDB persistence for atomic key-value storage.ExDataSketch.Storage.Mnesia-- distributed persistence for multi-node scenarios.ExDataSketch.Storage.Ecto-- SQL database persistence with schema and migration helpers.ExDataSketch.Storage.Ecto.SchemaandExDataSketch.Storage.Ecto.Migration-- Ecto schema and migration for sketch storage.ExDataSketch.Storage-- shared behaviour documentation and types for all backends.- All backends serialize via EXSK v2 binary format with CRC32C checksum; no raw state is ever stored.
- Configuration-driven backend availability via
config :ex_data_sketch, :persistence_backends.
Telemetry and observability (Phase 4).
ExDataSketch.Telemetry-- structured telemetry event emission at batch/compound operation boundaries (not per-update).- Four event categories:
:sketch(create, ingest, merge, serialize, deserialize),:persistence(save, load, merge, delete),:stream(reduce, partition_merge),:pipeline(accumulate, periodic_flush). Telemetry.execute/4,Telemetry.span/5,Telemetry.span_with_result/6-- timing wrappers with category-based enable/disable.Telemetry.event_name/2andall_event_names/0for programmatic handler attachment.ExDataSketch.Telemetry.OpenTelemetry-- OTEL span bridge (requires:opentelemetry_api ~> 1.0).- Configuration:
config :ex_data_sketch, telemetry_enabled: falseor per-categoryconfig :ex_data_sketch, :telemetry, sketch: true, persistence: false. - Telemetry events instrumented in all 13 sketch modules, all 5 storage backends,
Stream, andBroadway/GenStage/Flow.
ULL accuracy correction (Phase 5).
- ULL linear counting correction:
zeros > 0threshold (not HLL-styleraw_estimate <= 2.5*m && zeros > 0). Empirical validation shows linear counting always more accurate for ULL when empty registers exist. - ULL large range correction: bias correction for very high cardinality estimates, matching Ertl 2023.
- Both Pure Elixir and Rust NIF backends updated; property tests updated with tiered accuracy bounds (35%/25%/15% at p=8).
- ULL linear counting correction:
Configurable
update_manychunk size (Phase 5).update_many_chunk_sizeoption on HLL, ULL, CMS, and Theta (vianew/1opts). Default 10,000 (backward compatible).
EXSK v1 serialization escape hatch (Phase 5).
HLL.serialize(sketch, format: :v1)produces a backward-compatible v0.7.x binary (requires:phash2hash strategy, raisesArgumentErrorfor other strategies).Binary.encode_v1/4utility for custom v1 encoding.- v0.7.x binaries remain decodable via
Binary.decode/1(version sniffing).
Generalized corruption propagation properties (Phase 5).
- HLL, ULL, and CMS bit-flip properties in
property_guarantees_test.exsasserting that corrupted frames either fail CRC or produce estimates within 10x of the truthful estimate (never silently catastrophic). - Quotient filter delete property: count reduction (not
member?becomes false).
- HLL, ULL, and CMS bit-flip properties in
Benchmarks and property tests (Phase 6).
bench/persistence_bench.exs-- ETS save/load/merge overhead.bench/serialization_bench.exs-- serialize/deserialize throughput.bench/merge_throughput_bench.exs-- HLL/ULL/CMSmerge_manybenchmarks.bench/update_many_chunk_bench.exs-- configurable chunk size impact on throughput.bench/stream_ingestion_bench.exs-- stream ingestion latency and throughput.test/ex_data_sketch_serialization_stability_test.exs-- 7 round-trip properties (HLL v2/v1, ULL, CMS, Theta, Bloom, v1-v2 cross-version).- Expanded stream properties: ULL stream equivalence, ULL partition merge, ULL merge associativity, Theta stream equivalence, CMS merge associativity.
- Expanded storage properties: DETS save/load, DETS merge.
Educational materials.
guides/aggregation_wall.md(188 lines) -- why exact aggregation breaks at scale, BEAM's natural fit, common patterns.guides/distributed_merge_semantics.md(328 lines) -- associativity/commutativity proofs, fan-in/tree/partition patterns, anti-patterns.guides/livebooks.md-- Livebook catalogue with recommended order and learning objectives.- Updated
guides/telemetry.md-- pipeline/stream event tables,all_event_names/0reference. - Updated
guides/streaming_sketches.md-- Stream API, Collectable, partitioned reduction. - Updated
guides/broadway_integration.md--accumulate/3,accumulate_into/4,PeriodicAggregator. - Updated
guides/genstage_integration.md--SketchConsumer,SketchProducer,SketchStage. - Updated
guides/persistence.md-- all 5 backends, configuration, EXSK v2 storage contract. - Updated
guides/observability.md-- telemetry categories, event names, OTEL bridge.
Livebooks.
livebooks/streaming_cardinality.livemd-- Stream API, precision tradeoffs, ULL vs HLL.livebooks/broadway_integration.livemd-- accumulate, PeriodicAggregator, partition handling.livebooks/genstage_aggregation.livemd-- SketchConsumer, SketchProducer, flush patterns.livebooks/rolling_telemetry.livemd-- time-windowed sketches, ETS persistence.livebooks/distributed_merges.livemd-- associativity, tree aggregation, ETS sharding.livebooks/persistence_snapshots.livemd-- ETS/DETS, serialization, multi-backend strategy.livebooks/livedashboard_integration.livemd-- telemetry wiring, custom dashboard pages.livebooks/ai_token_analytics.livemd-- LLM workload multi-dimensional sketch dashboard.livebooks/phoenix_observability.livemd-- DAU, latency, rate limiting, ETS persistence.
Changed
ExDataSketch.HLL.new/1now acceptsupdate_many_chunk_sizeoption (default 10,000).ExDataSketch.ULL.new/1now acceptsupdate_many_chunk_sizeoption (default 10,000).ExDataSketch.CMS.new/1now acceptsupdate_many_chunk_sizeoption (default 10,000).ExDataSketch.Theta.new/1now acceptsupdate_many_chunk_sizeoption (default 10,000).- ULL accuracy at low cardinalities significantly improved via linear counting + large range correction. Users may see different estimates for sketches with very few items; the new estimates are more accurate.
ExDataSketch.ULLmoduledoc updated with estimation strategy description and p>=12 recommendation.
Fixed
- ULL low-precision accuracy: p=8 with n=1000 improved from ~62.5% relative error to ~0.8% via linear counting correction.
- ETS merge test tolerance: 60% tolerance for cardinality < 5 (HLL at p=10 has high relative error at tiny cardinalities).
- Quotient filter delete property: corrected from asserting
member?becomes false (not guaranteed) to asserting count reduction. - DETS API: corrected
close_fileto:dets.close/1in property tests. PeriodicAggregatortelemetry metadata: usessketch_typeonly (removed non-existentstate.id).- OTEL handler IDs: tuples
{"ex_data_sketch_opentelemetry", event_name}, not strings.
Migration
See guides/v0.8.0_migration_notes.md for the v0.7.x to v0.8.0 migration guide. For v0.8.0 to v0.9.0:
- No code changes required for most users. All new modules are additive; existing APIs are backward compatible.
- ULL estimates may change at very low cardinalities (p < 12, n < 500). The new estimates are more accurate. If you depend on exact numeric values in tests, add tolerance for small cardinalities.
update_many_chunk_sizedefaults to 10,000 (matching v0.8.0 behavior). No change needed unless you want to tune batch throughput.- v1 serialization is an opt-in escape hatch via
format: :v1. Default serialization remains EXSK v2. - Telemetry is enabled by default. Disable with
config :ex_data_sketch, telemetry_enabled: false. - Persistence backends are enabled by default when their runtime dependencies are available. Disable individuals via
config :ex_data_sketch, :persistence_backends, ets: [enabled: false].
Stats
- +21 new modules:
Stream,Broadway,Broadway.PeriodicAggregator,Flow,GenStage,GenStage.SketchConsumer,GenStage.SketchProducer,GenStage.SketchStage,Storage,Storage.ETS,Storage.DETS,Storage.CubDB,Storage.Mnesia,Storage.Ecto,Storage.Ecto.Schema,Storage.Ecto.Migration,Telemetry,Telemetry.OpenTelemetry,Integration,Binary,Binary.encode_v1/4utility. - 1558 tests, 204 doctests, 199 properties, 0 failures (NIF on).
- 9 Livebooks, 20 guides (3 new educational guides + 6 updated +
livebooks.mdindex). - 5 new benchmark suites, 7 new property test groups.
:telemetry ~> 1.0required dependency;:opentelemetry_api ~> 1.0,:broadway,:flow,:cubdb,:ecto_sql,:mnesiaoptional dependencies.
[0.8.0] - 2026-05-12
Release theme: Deterministic Foundations. Transforms ex_data_sketch from a collection of probabilistic algorithms into a production-grade probabilistic runtime for the BEAM. Focus: deterministic hashing, binary stability, corruption detection, hot-path performance, and installation reliability.
Added
Deterministic hashing infrastructure (Phase 1).
ExDataSketch.Hash.XXH3— focused XXHash3-64 wrapper. RaisesArgumentErrorwhen the Rust NIF is unavailable so hash drift cannot occur silently.ExDataSketch.Hash.Murmur3— fullMurmurHash3_x64_128returning the high 64 bits (Apache DataSketches convention). Pure Elixir and Rust NIF implementations are byte-identical, verified by property-based parity (200 random inputs per CI run) and against canonical Pythonmmh3regression vectors.ExDataSketch.Hash.Metadata— 16-byte versioned binary block recording(algorithm, seed, sketch_family, sketch_family_version, backend)with a forward-compatible extension trailer. The building block for the EXSK v2 binary header.ExDataSketch.Hash.Validation— centralized merge-compatibility checks (validate_options!/3,validate_metadata!/3,compatible_options?/2).- Public registry API on
ExDataSketch.Hash:default_algorithm/0,supported_algorithms/0,algorithm_info/1. ExDataSketch.Hash.resolve_strategy/1— single source of truth for sketch constructors. Honors a user-supplied:hash_strategyor falls back todefault_algorithm/0.HLL, ULL, Theta, CMS
new/1now respect user-supplied:hash_strategy(:xxhash3 | :murmur3 | :phash2). The option was silently overridden in v0.7.x.
Binary stability and corruption detection (Phase 2).
ExDataSketch.Binary— public facade for the EXSK v2 frame (encode/3,decode/1,peek_version/1,build_payload/2,metadata_from_opts/3).ExDataSketch.Binary.Header— EXSK v2 frame encoder/decoder. Layout: magic + version + sketch_family + family_version + flags + header_size +Hash.Metadatablock + payload_size + payload + CRC32C trailer.ExDataSketch.Binary.Validator— discrete defensive check helpers (check_minimum_v2_size,check_magic,check_version,check_crc).ExDataSketch.Binary.CRC— CRC32C (Castagnoli polynomial, reflected, init0xFFFFFFFF, final XOR0xFFFFFFFF). Pure Elixir and Rust NIF implementations are byte-identical, verified against the standard"123456789" -> 0xE3069283check vector and Pythoncrc32cregression vectors.- Rust
crc32c_nif— table-driven CRC32C, ~1 GB/s on commodity hardware.
HLL hot-path generalization (Phase 3).
- 8 new Rust NIFs:
{hll, ull, theta, cms}_update_many_raw_h_nif/_dirty_nif. Each accepts analgorithm: u8parameter (1 = xxhash3,2 = murmur3) and shares a singleMurmur3_x64_128implementation viapub(crate)export fromhash.rs. - End-to-end Murmur3 acceleration:
:murmur3callers now hit the in-Rust hashing fast path instead of falling off to the Elixir-side hash. bench/hll_hot_path_bench.exs— comprehensive benchmark across Pure phash2 / Pure xxhash3 / Rust raw XXH3 / Rust raw_h Murmur3 at 10k / 100k / 1M items.
- 8 new Rust NIFs:
Precompiled NIF platform matrix (Phase 4).
- Two Windows targets added:
x86_64-pc-windows-msvcandaarch64-pc-windows-msvc. Release matrix is now 8 targets × 2 NIF versions = 16 artifacts per tagged release. mix test.nif_onandmix test.nif_offaliases automatically reset the per-envrustler_precompiled :force_buildstate between local NIF-mode flips.test/ex_data_sketch/nif_availability_test.exs— 18 contract tests assertingHash.nif_available?/0stability, default-algorithm reflection, registry availability flags,XXH3.hash/2failure mode, Murmur3 NIF-less fallback, checksum file shape, andnif.ex↔release.ymltarget-list alignment.
- Two Windows targets added:
Property-based validation (Phase 5).
test/property_guarantees_test.exs— 14 new properties locking:- HLL / ULL cardinality monotonicity and error bounds within published RSE.
- KLL / REQ rank monotonicity and quantile/rank inversion within published epsilon.
- CMS overestimation-only (
estimate(item) >= true_count(item)). - Bloom / XorFilter / Cuckoo no-false-negative guarantees.
- Binary v2 bit-flip corruption never silently propagates to a sketch.
User-facing release guides (shipped to HexDocs):
guides/v0.8.0_migration_notes.md— v0.7.x to v0.8.0 upgrade guide.guides/v0.8.0_architecture.md— layered architecture overview.guides/serialization_compatibility.md— the v0.x EXSK stability contract.guides/hash_strategies.md— choosing between phash2, XXH3, Murmur3, and custom.guides/hll_performance.md— HLL hot-path architecture, benchmark numbers, and external-library context.guides/precompiled_nifs.md— platform matrix, release pipeline, and source-build fallback.guides/roadmap.md— v0.9.0 preview.
Internal plans and reviewer checklists (repo-only, not packaged):
plans/hash_binary_contract.mdplans/binary_contract.md,plans/corruption_detection.mdplans/hll_scheduler_safety.mdplans/property_testing.mdplans/0.8.0_implementation_plan.md, Phase 1-5 reviewer checklistsplans/0.8.0-risks.md(31-risk consolidated register)plans/0.8.0-review.md(pre-release code review)
Changed
- EXSK serialization format bumped to v2. Every sketch's
serialize/1now produces an EXSK v2 frame (magic + version 2 + sketch family + family version + flags + header_size + 16-byte hash metadata block + payload size + payload + CRC32C trailer). v0.7.x EXSK v1 frames remain decodable viaBinary.decode/1's version sniffing;ExDataSketch.Codecis preserved as the legacy v1 path. - Golden vectors regenerated as v2 under
test/vectors/. The previous v1 vectors are preserved undertest/vectors_v1/and exercised bytest/ex_data_sketch_v1_compat_test.exsas a permanent regression guard. README.mdroadmap rewritten to match the strategic roadmap inplans/next_steps.md: v0.8.0 = Deterministic Foundations; v0.9.0 = Streaming Integrations; v0.10.0 = Apache Interoperability; v0.11.0 = New Sketch Families (CPC, Tuple); v0.12.0 = Similarity & Sampling (MinHash, VarOpt); v1.0.0 = Stable Binary Contract.ExDataSketch.Hash.validate_merge_hash_compat!/3is preserved as a backward-compatible shim that delegates toExDataSketch.Hash.Validation.validate_options!/3.
Fixed
ExDataSketch.Hash.XXH3doctests are now NIF-safe: they exercise the success path when the NIF is available and explicitly verify the documentedArgumentErrorcontract when the NIF is unavailable, removing a CI failure on theEX_DATA_SKETCH_SKIP_NIF=truelane.test/ex_data_sketch/nif_availability_test.exschecksum-file assertions softened to "if present, parses as a map" so fresh checkouts (wherechecksum-Elixir.ExDataSketch.Nif.exshas not been populated by the release pipeline) pass CI.
Migration
See guides/v0.8.0_migration_notes.md (shipped in HexDocs) for the full v0.7.x -> v0.8.0 migration guide. Key points:
- No code changes required for most users. EXSK v1 frames are still decoded; the
serialize/1output format changes but downstream code that uses round-trip serialization sees no API difference. - One-way upgrade for persisted sketches. v0.7.x cannot read v0.8.0-produced binaries. Stage your rollout: deploy v0.8.0 readers first, then producers.
- Opt-in Murmur3. New
:murmur3strategy is opt-in viahash_strategy: :murmur3at sketch construction. Default remains:xxhash3.
Documentation
User-facing guides (shipped to HexDocs and the Hex package):
guides/v0.8.0_architecture.md— consolidated Phase 1-5 design overview.guides/serialization_compatibility.md— the v0.x stability contract.guides/roadmap.md— preview of the next release's streaming-integration scope.
Internal documentation (repo-only; linked from the user guides):
plans/0.8.0-risks.md— open risk register at release time.plans/0.8.0-review.md— pre-release code review.
Stats
- +10 new modules:
Hash.XXH3,Hash.Murmur3,Hash.Metadata,Hash.Validation,Binary,Binary.Header,Binary.Validator,Binary.CRC(Elixir); 2 Rust NIF entry points (hash,crc). - +11 Rust NIFs:
murmur3_x64_128_nif,murmur3_x64_128_full_nif,crc32c_nif, 4 ×*_update_many_raw_h_nif+ 4 ×*_dirty_nif. - +92 tests, +33 doctests, +19 properties since v0.7.1.
- Full suite (NIF on): 1,317 tests, 202 doctests, 171 properties, 0 failures.
- Full suite (NIF off): 1,088 tests, 202 doctests, 128 properties, 0 failures.
- Coverage: 92.7% line coverage (target was 70%).
- HLL throughput: 25-34 M items/sec at p=14 (XXH3, Rust raw); ~15x faster than the Pure path.
[0.7.1] - 2026-03-22
Added
- Move hashing into NIF batch calls:
update_manyfor HLL, ULL, Theta, and CMS now sends raw items to Rust and hashes inside the NIF, eliminating per-item Elixir heap allocations (94.6% memory reduction at 10M items). (#202) - Wire
:hash_fnand:seedoptions through HLL, ULL, Theta, and CMS, enabling custom hash functions and reproducible seeded hashing. (#198) - Merge hash-compatibility validation:
merge/2on HLL, ULL, Theta, and CMS now raisesIncompatibleSketchesErrorwhen hash strategy or seed differs between sketches. (#205) - Pure backend
hll_update_manyoptimization: pre-aggregate map with sorted binary splice replaces tuple-based per-hash full-tuple copies, reducing transient allocation from O(n * m) to O(n + m). - ListIterator-based NIF item decoding for zero-copy Erlang list iteration in Rust.
- Test infrastructure: configurable coverage baselines, Rust CI coverage reporting, 39 new tests covering deserialization edge cases, custom hash_fn paths, helper functions, and merge validation.
Fixed
- Quotient filter wrap-around bug:
extract_allin both Pure and Rust backends failed when a cluster wrapped from slot N-1 to slot 0, producing nil quotients (Pure crash) or silent corruption (Rust). (#203, #204) - CMS merge validation: replaced flawed
Keyword.delete(opts, :hash_strategy)comparison with explicit width/depth/counter_width checks. - Pure backend
update_manyregression: restored chunk + batch*_update_manypath for HLL, ULL, and Theta (was incorrectly using per-item*_update). - Seed clamping: all raw NIF functions now clamp seed values to u64 range before passing to Rust.
- Hash-dependent vector tests tagged with
@tag :rust_nifto prevent failures in pure-only CI (vectors were generated with xxhash3).
[0.7.0] - 2026-03-11
Added
- UltraLogLog sketch (
ExDataSketch.ULL) for improved cardinality estimation with ~20% lower relative error than HLL at the same memory footprint. ULL1 binary state format with 8-byte header + 2^p registers. EXSK serialization (sketch ID 15). - ULL register encoding from Ertl 2023:
register_value = 2 * geometric_rank - sub_bitdoubles the information per register compared to HLL. - FGRA estimator (Ertl 2017 sigma/tau convergence) for ULL cardinality estimation.
- Rust NIF acceleration for ULL:
update_many,merge, andestimateoperations with dirty scheduler thresholds. - Precision parameter
psupports range 4..26 (vs 4..16 for HLL), allowing higher accuracy at larger memory budgets. - Full API:
new,update,update_many,merge,estimate,count,serialize,deserialize,from_enumerable,merge_many,reducer,merger,size_bytes. - ULL test vectors, parity tests, merge law property tests, and benchmark suite.
[0.6.0] - 2026-03-11
Added
- REQ sketch (
ExDataSketch.REQ) for relative-error quantile estimation with configurable high-rank accuracy. REQ1 binary state format. EXSK serialization (sketch ID 13). - Misra-Gries sketch (
ExDataSketch.MisraGries) for deterministic heavy-hitter detection with configurable key encoding (:binary,:int,{:term, :external}). MG01 binary state format. EXSK serialization (sketch ID 14). - XXHash3 NIF integration (
ExDataSketch.Hash.xxhash3_64/1,2) for fast, cross-platform stable hashing via Rust NIF with phash2-based fallback. - KLL
cdf/2andpmf/2for cumulative distribution and probability mass functions. - DDSketch
rank/2for normalized rank queries. - Rust NIF acceleration for all membership filters: Bloom, Cuckoo, Quotient, CQF, XorFilter, and IBLT. Batch operations (
put_many,merge,build) automatically use compiled Rust NIFs when available, with dirty scheduler thresholds for large inputs. - Parity tests verifying byte-identical serialization between Pure Elixir and Rust NIF backends for all sketch algorithms.
- Benchmark suites for REQ sketch, Misra-Gries, and XXHash3 NIF throughput.
Quantilesfacade for unified quantile sketch API across KLL and DDSketch.
[0.5.0] - 2026-03-10
Added
- Cuckoo filter (
ExDataSketch.Cuckoo) with Pure Elixir backend. CKO1 binary state format. Partial-key cuckoo hashing with configurable fingerprint size, bucket size, and max kicks. Supports insertion, safe deletion, and membership testing. EXSK serialization (sketch ID 8). - Quotient filter (
ExDataSketch.Quotient) with Pure Elixir backend. QOT1 binary state format. Quotient/remainder fingerprint splitting with linear probing and metadata bits (is_occupied, is_continuation, is_shifted). Supports insertion, safe deletion, merge, and membership testing. EXSK serialization (sketch ID 9). - Counting Quotient Filter (
ExDataSketch.CQF) with Pure Elixir backend. CQF1 binary state format. Extends quotient filter with variable-length counter encoding for multiset membership and approximate counting viaestimate_count/2. Supports insertion, deletion, merge. EXSK serialization (sketch ID 10). - XorFilter (
ExDataSketch.XorFilter) with Pure Elixir backend. XOR1 binary state format. Static build-once immutable filter constructed viabuild/2with 8-bit or 16-bit fingerprints. Supports membership testing only. EXSK serialization (sketch ID 11). - IBLT (
ExDataSketch.IBLT) with Pure Elixir backend. IBL1 binary state format. Invertible Bloom Lookup Table for set reconciliation viasubtract/2andlist_entries/1. Supports set mode and key-value mode, insertion, deletion, merge. EXSK serialization (sketch ID 12). - FilterChain (
ExDataSketch.FilterChain) for capability-aware membership filter composition. FCN1 binary state format. Lifecycle-tier patterns (hot/warm/cold) with stage position enforcement. Supportsadd_stage/2,put/2,member?/2,delete/2. Serializes all stages in order. - Benchmark suites for Cuckoo, Quotient, CQF, XorFilter, IBLT, and FilterChain (
bench/*.exs). UnsupportedOperationErrorfor operations not supported by a structure (used by FilterChain).InvalidChainCompositionErrorfor invalid FilterChain stage composition.capabilities/0function on Bloom, Cuckoo, Quotient, CQF, XorFilter, IBLT, and FilterChain modules.- Cuckoo, Quotient, CQF, XorFilter, IBLT, and FilterChain backend callbacks on
ExDataSketch.Backend.
[0.4.0] - 2026-03-06
Added
- Bloom filter (
ExDataSketch.Bloom) with Pure Elixir backend. - BLM1 binary state format (40-byte header + LSB-first packed bitset).
- Double hashing (Kirsch-Mitzenmacher) deriving k bit positions from a single 64-bit hash.
- Bloom backend callbacks:
bloom_new/1,bloom_put/3,bloom_put_many/3,bloom_member?/3,bloom_merge/3,bloom_count/2. - Automatic parameter derivation from capacity and false_positive_rate options.
- Bloom merge via bitwise OR with validation of matching bit_count, hash_count, and seed.
- Bloom popcount-based cardinality estimation.
- Bloom serialization via EXSK envelope (sketch ID 7).
- Bloom property tests (no false negatives, merge commutativity/associativity/identity, serialization round-trip).
- Bloom statistical validation tests (observed FPR within 2x of target).
- Bloom merge law properties in merge_laws_test.exs.
- Bloom parity test stubs for future Rust NIF backend.
- Bloom benchmark suite (
bench/bloom_bench.exs). - Bloom options section in usage guide.
[0.3.0] - 2026-03-06
Added
- FrequentItems sketch (
ExDataSketch.FrequentItems) using the SpaceSaving algorithm with Pure Elixir and Rust NIF backends. - FI1 binary state format (32-byte header + variable-length sorted entries).
- FrequentItems backend callbacks:
fi_new/1,fi_update/3,fi_update_many/3,fi_merge/3,fi_estimate/3,fi_top_k/3,fi_count/2,fi_entry_count/2. - Batch optimization via pre-aggregation (
Enum.frequencies/1) with weighted updates. - Deterministic tie-breaking on eviction (lexicographically smallest item_bytes).
- Key encoding policies:
:binary,:int(signed 64-bit LE),{:term, :external}. - Commutative merge via additive count combination and canonical replay.
- Rust NIF acceleration for
fi_update_manyandfi_mergewith dirty scheduler support. - FrequentItems support in
ExDataSketch.update_many/2facade. - FrequentItems merge law properties (commutativity, identity, count conservation).
- FrequentItems golden vector test fixtures.
- FrequentItems parity tests ensuring byte-identical output between Pure and Rust backends.
- FrequentItems benchmark suite (
bench/frequent_items_bench.exs). - EXSK codec sketch ID 6 for FrequentItems.
- FrequentItems usage documentation in usage guide with SpaceSaving algorithm overview.
- Mox test dependency for backend contract testing.
- Theta
compact/1function for explicit compaction.
[0.2.1] - 2026-03-05
Added
- DDSketch quantiles sketch (
ExDataSketch.DDSketch) with Pure Elixir and Rust NIF backends. - DDSketch backend callbacks:
ddsketch_new/1,ddsketch_update/3,ddsketch_update_many/3,ddsketch_merge/3,ddsketch_quantile/3,ddsketch_count/2,ddsketch_min/2,ddsketch_max/2. - Rust NIF acceleration for
ddsketch_update_manyandddsketch_mergewith dirty scheduler support. - DDSketch support in
ExDataSketch.Quantilesfacade (type: :ddsketch). - DDSketch merge law properties (commutativity, identity, count additivity, min/max preservation).
- DDSketch golden vector test fixtures (empty, single, small_set, merge, zeros).
- DDSketch parity tests ensuring byte-identical output between Pure and Rust backends.
- DDSketch benchmark suite (
bench/ddsketch_bench.exs). - EXSK codec sketch ID 5 for DDSketch.
- DDSketch usage documentation in usage guide with KLL vs DDSketch comparison table.
[0.2.0] - 2026-03-04
Added
- KLL quantiles sketch (
ExDataSketch.KLL) with Pure Elixir and Rust NIF backends. ExDataSketch.Quantilesfacade module for type-dispatched quantile sketch access.- KLL backend callbacks:
kll_new/1,kll_update/3,kll_update_many/3,kll_merge/3,kll_quantile/3,kll_rank/3,kll_count/2,kll_min/2,kll_max/2. - Rust NIF acceleration for
kll_update_manyandkll_mergewith dirty scheduler support. - KLL merge law properties (associativity, commutativity, identity, count additivity, min/max preservation).
- KLL golden vector test fixtures (empty, single, small_set, merge).
- KLL parity tests ensuring byte-identical output between Pure and Rust backends.
- KLL benchmark suite (
bench/kll_bench.exs). - EXSK codec sketch ID 4 for KLL.
[0.1.1] - 2026-03-04
Added
- Deterministic golden vector test fixtures for HLL, CMS, and Theta (JSON format with versioned schema).
- Pure vs Rust parity test suite ensuring byte-identical serialization and estimates.
- Merge-law property tests (associativity, commutativity, identity, chunking equivalence) for all sketch types.
- Compatibility and Stability section in README documenting serialization and parity guarantees.
- CI regression tracking for coverage and benchmark baselines.
Changed
- Stabilized benchmark scripts with deterministic datasets and JSON output.
- Clarified HLL and CMS DataSketches interop stubs as intentionally unimplemented (not "future").
- Removed stale "Phase 2" language from module documentation.
[0.1.0] - 2026-03-02
Added
- Precompiled Rust NIF binaries for macOS (ARM64, x86_64) and Linux (x86_64 gnu/musl, aarch64 gnu/musl).
- Optional Rust NIF acceleration backend (
ExDataSketch.Backend.Rust). - Rust NIFs for HLL (update_many, merge, estimate), CMS (update_many, merge), and Theta (update_many, merge).
- Normal and dirty CPU scheduler NIF variants with configurable thresholds.
- Automatic fallback to Pure Elixir backend when Rust NIF is unavailable.
- Cross-backend parity tests ensuring byte-identical output between Pure and Rust.
- Side-by-side Pure vs Rust benchmark scenarios.
- CI jobs for Rust NIF compilation and testing (
test-rust,bench-rust). - Pure Elixir Theta sketch implementation (new, update, update_many, compact, merge, estimate).
- Apache DataSketches CompactSketch codec (serialize/deserialize) for Theta interop.
- MurmurHash3 seed hash computation for DataSketches compatibility.
- Deterministic test vectors for Theta sketch.
- Cross-language vector harness specification for Java interop testing.
- Theta Benchee benchmarks.
- Pure Elixir HLL implementation (new, update, update_many, merge, estimate).
- Pure Elixir CMS implementation (new, update, update_many, merge, estimate).
- Deterministic test vectors for HLL and CMS.
- Real Benchee benchmarks for HLL and CMS.
- Project skeleton with directory structure and dependencies.
- Public API stubs for HLL, CMS, and Theta sketch modules.
- ExDataSketch-native binary codec (EXSK format).
- Hash module with stable 64-bit hash interface.
- Backend behaviour with Pure Elixir stub implementation.
- Quick Start and Usage Guide documentation.
- GitHub Actions CI workflow.
- Integration convenience functions (
from_enumerable/2,merge_many/1,reducer/1,merger/1) on all sketch modules. - Integration guide with ecosystem examples (Flow, Broadway, Explorer, Nx, ex_arrow/ExZarr).
- Documented merge properties (associativity, commutativity) for HLL, CMS, and Theta.