apache / apache/datasketches-java

KllItemsSketch: querying before serialization corrupts the round-tripped sorted view

未关闭 适合新手
#756 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
主要语言
Java
星标
958
派生
226
平均合并
3 天 9 小时
30 天内合并 PR
8

描述

Querying a heap `KllItemsSketch` before serializing it makes the round-trip return wrong quantiles. The bytes are fine, the flag inside them is not.

```java
KllItemsSketch sk = KllItemsSketch.newHeapInstance(8, Comparator.naturalOrder(), new ArrayOfStringsSerDe());
sk.update("a"); sk.update("b"); sk.update("c"); sk.update("d");
sk.getQuantile(0.5, INCLUSIVE); // any query is enough
KllItemsSketch rt = KllItemsSketch.heapify(
MemorySegment.ofArray(sk.toByteArray()), Comparator.naturalOrder(), serDe);
```

```
orig SV = [a, b, c, d]
heapified SV = [a, d, c, b, a, d]
rank=0.50 orig=b heapified=c
rank=0.55 orig=c heapified=b
```

Four items in, a six-element sorted view out, in raw insertion order with duplicated min and max. `wrap()` behaves the same. Without the query first there is no difference at all. At n=8, 15 of 21 probed ranks disagree.

`KllItemsSketch.CreateSortedView.getSV()` sorts level 0 and then records that it did:

```java
final T[] srcQuantiles = getTotalItemsArray();
...
if (!isLevelZeroSorted()) {
Arrays.sort(srcQuantiles, srcLevelsArr[0], srcLevelsArr[1], comparator);
if (!hasMemorySegment()) { setLevelZeroSorted(true); }
}
```

For the heap items variant `getTotalItemsArray()` hands back a defensive copy (`KllHeapItemsSketch:255-260` does a `System.arraycopy`), so the sort lands on the copy while the flag is set on the sketch. `KllHelper` then writes that flag into the serialized image and `heapify`/`wrap` trust it and skip the sort.

The doubles path does the same thing correctly because `KllHeapDoublesSketch.getDoubleItemsArray()` returns the live array, which is what makes the comment at `KllDoublesSketch:562` true:

```java
//we don't sort level0 in MemorySegment, only our copy.
```

So this looks specific to the generic Items variant rather than a design choice. Floats, Longs, Req and classic quantiles are all unaffected.

Live sketches recover on their own, because `updateItem` re-sorts level 0 and resets the flag, and I could not reproduce it through `merge()` in 30000 cases, so the damage seems confined to serializing a sketch that has been queried.

Either dropping the `setLevelZeroSorted(true)` here or returning the live array from `KllHeapItemsSketch.getTotalItemsArray()` would fix it. I did not send a patch because I have another PR open here (#755) and did not want two at once, but I am happy to put one up.

Found while fuzzing quantile invariants across the families: about 95000 randomized configurations over KllDoubles/Floats/Longs/Items, ReqSketch and classic quantiles, heap and direct, heapify and wrap, ten data distributions. This was the only invariant violation. Related but not the same as the closed #527, which was about which comparator level 0 is sorted with.

AI disclosure: I used Claude Code for the fuzzing harness and to narrow this down. I ran and checked the repro myself.

贡献指南

这个仓库没有索引到贡献指南

调研方向

从 KllItemsSketch.CreateSortedView.getSV() 和 KllHeapItemsSketch.getTotalItemsArray() 开始,然后跟踪 KllHelper 如何序列化 level-zero-sorted 标志,以及 heapify/wrap 如何使用它。重现所提供的四项 heap sketch 用例,包括 wrap(),并比较原始的排序视图和分位数与 round-trip 后的排序视图和分位数。完成的标准是:序列化前进行查询不再改变 round-trip 后的结果。

由索引模型根据 Issue 内容生成。

评估

技术栈
java
领域
data
Issue 类型
缺陷
难度
2/5
预计耗时
1-3 小时
活跃度
活跃
描述清晰度
描述清楚
新手友好度
76/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。