You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datasketches.apache.org by fr...@apache.org on 2026/04/22 12:09:11 UTC
(datasketches-rust) branch main updated: fix(hll): update silently dropped after deserializing a compact List sketch (#117)
This is an automated email from the ASF dual-hosted git repository.
notfilippo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datasketches-rust.git
The following commit(s) were added to refs/heads/main by this push:
new b1544aa fix(hll): update silently dropped after deserializing a compact List sketch (#117)
b1544aa is described below
commit b1544aa873cf7f91a7998f382eec842bf15eb023
Author: Filippo <12...@users.noreply.github.com>
AuthorDate: Wed Apr 22 14:09:05 2026 +0200
fix(hll): update silently dropped after deserializing a compact List sketch (#117)
When an `HllSketch` in **List mode** is serialized it uses compact format:
only the live coupons are written to disk, with no trailing `COUPON_EMPTY`
sentinels. On deserialization, the container was allocated with exactly
`coupon_count` slots, leaving the array fully packed.
`list::update()` scans linearly for a `COUPON_EMPTY` (`0`) sentinel to find
an insertion slot. Finding none, it fell through silently — discarding every
value added after the round-trip.
Always allocate `1 << lg_arr` slots (initialized to `COUPON_EMPTY`) in
`List::deserialize()`, reading only `coupon_count` elements from the byte
stream in compact mode. The trailing empty slots are then available as
sentinels for subsequent `update()` calls.
Fixes #115.
---
datasketches/src/hll/list.rs | 14 +++++++++-----
datasketches/tests/hll_serialization_test.rs | 28 ++++++++++++++++++++++++++++
2 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/datasketches/src/hll/list.rs b/datasketches/src/hll/list.rs
index 1f0b3e7..689e42c 100644
--- a/datasketches/src/hll/list.rs
+++ b/datasketches/src/hll/list.rs
@@ -82,13 +82,17 @@ impl List {
empty: bool,
compact: bool,
) -> Result<Self, Error> {
- // Compute array size
- let array_size = if compact { coupon_count } else { 1 << lg_arr };
-
- // Read coupons
+ // Always allocate the full-sized array (1 << lg_arr) so Coupon::EMPTY sentinel
+ // slots are available for future update() calls. In compact format only
+ // coupon_count values are stored on disk, but memory must hold the full capacity
+ // so the linear scan in update() can find an empty slot to insert into.
+ let array_size = 1 << lg_arr;
+ let read_count = if compact { coupon_count } else { array_size };
+
+ // Read coupons into the front of the full-sized array; remaining slots stay Coupon::EMPTY.
let mut coupons = vec![Coupon::EMPTY; array_size];
if !empty && coupon_count > 0 {
- for (i, coupon) in coupons.iter_mut().enumerate() {
+ for (i, coupon) in coupons.iter_mut().take(read_count).enumerate() {
let raw = cursor.read_u32_le().map_err(|_| {
Error::insufficient_data(format!(
"expect {coupon_count} coupons, failed at index {i}"
diff --git a/datasketches/tests/hll_serialization_test.rs b/datasketches/tests/hll_serialization_test.rs
index a7e00e6..4f7291b 100644
--- a/datasketches/tests/hll_serialization_test.rs
+++ b/datasketches/tests/hll_serialization_test.rs
@@ -22,6 +22,7 @@ use std::path::PathBuf;
use common::serialization_test_data;
use datasketches::hll::HllSketch;
+use datasketches::hll::HllType;
fn test_sketch_file(path: PathBuf, expected_cardinality: usize, expected_lg_k: u8) {
let expected = expected_cardinality as f64;
@@ -102,6 +103,33 @@ fn test_sketch_file(path: PathBuf, expected_cardinality: usize, expected_lg_k: u
);
}
+/// Reproducer for https://github.com/apache/datasketches-rust/issues/115
+///
+/// A compact-serialized List has no trailing COUPON_EMPTY (0) sentinels.
+/// Before the fix, update() would scan the fully-packed array, find no
+/// empty slot, and silently drop the new value.
+#[test]
+fn test_update_after_deserialize_list_mode() {
+ const LG_K: u8 = 11;
+ for hll_type in [HllType::Hll4, HllType::Hll6, HllType::Hll8] {
+ let mut sketch = HllSketch::new(LG_K, hll_type);
+ sketch.update(1u64);
+
+ // Round-trip through serialization (compact format, List mode)
+ let bytes = sketch.serialize();
+ let mut sketch = HllSketch::deserialize(&bytes).unwrap();
+
+ // This update was silently dropped before the fix
+ sketch.update(2u64);
+
+ let est = sketch.estimate();
+ assert!(
+ (est - 2.0).abs() < 0.1,
+ "{hll_type:?}: expected estimate close to 2.0 after update post-deserialize, got {est}"
+ );
+ }
+}
+
#[test]
fn test_java_hll4_compatibility() {
let test_cases = [0, 1, 10, 100, 1000, 10000, 100000, 1000000];
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datasketches.apache.org
For additional commands, e-mail: commits-help@datasketches.apache.org