This is an automated email from the ASF dual-hosted git repository.
Jackie-Jiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push:
new 967bbe2aa34 Fix sketch accumulator initialization, tuple sizing, and
KLL null handling (#20228)
967bbe2aa34 is described below
commit 967bbe2aa349f45533eff2345be89ffd2ec3efeb
Author: Xiaotian (Jackie) Jiang <[email protected]>
AuthorDate: Thu Nov 12 16:54:18 2026 -0800
Fix sketch accumulator initialization, tuple sizing, and KLL null handling
(#20228)
---
...ValueIntegerTupleSketchAggregationFunction.java | 3 -
.../DistinctCountCPCSketchAggregationFunction.java | 10 +-
...CountIntegerTupleSketchAggregationFunction.java | 3 -
...stinctCountRawCPCSketchAggregationFunction.java | 2 -
...inctCountRawThetaSketchAggregationFunction.java | 2 -
...istinctCountThetaSketchAggregationFunction.java | 9 +-
.../IntegerTupleSketchAggregationFunction.java | 13 +--
.../function/PercentileKLLAggregationFunction.java | 23 ++--
...aluesIntegerTupleSketchAggregationFunction.java | 3 -
.../IntermediateResultByteBufferSerDeTest.java | 15 ++-
.../PercentileKLLBytesAggregationTest.java | 113 ++++++++++++++++++++
.../SketchAccumulatorSerializationTest.java | 117 +++++++++++++++++++++
.../IntegerTupleSketchValueAggregator.java | 20 ++--
.../local/customobject/CpcSketchAccumulator.java | 33 ++----
.../customobject/CustomObjectAccumulator.java | 40 +++----
.../local/customobject/ThetaSketchAccumulator.java | 48 +++------
.../customobject/TupleIntSketchAccumulator.java | 55 +++-------
.../IntegerTupleSketchValueAggregatorTest.java | 37 ++++++-
.../customobject/CpcSketchAccumulatorTest.java | 67 +++++++++---
.../customobject/ThetaSketchAccumulatorTest.java | 72 ++++++++++---
.../TupleIntSketchAccumulatorTest.java | 102 +++++++++++++++---
21 files changed, 547 insertions(+), 240 deletions(-)
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AvgValueIntegerTupleSketchAggregationFunction.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AvgValueIntegerTupleSketchAggregationFunction.java
index 1d34b244d27..5532a709031 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AvgValueIntegerTupleSketchAggregationFunction.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AvgValueIntegerTupleSketchAggregationFunction.java
@@ -57,9 +57,6 @@ public class AvgValueIntegerTupleSketchAggregationFunction
if (accumulator == null) {
return null;
}
- accumulator.setNominalEntries(_nominalEntries);
- accumulator.setSetOperations(_setOps);
- accumulator.setThreshold(_accumulatorThreshold);
TupleSketch<IntegerSummary> result = accumulator.getResult();
if (result.isEmpty() || result.getRetainedEntries() == 0) {
// there is nothing to average, return null
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountCPCSketchAggregationFunction.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountCPCSketchAggregationFunction.java
index 4a16b767ad6..d9856b6a2e0 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountCPCSketchAggregationFunction.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountCPCSketchAggregationFunction.java
@@ -598,8 +598,6 @@ public class DistinctCountCPCSketchAggregationFunction
if (intermediateResult2.isEmpty()) {
return intermediateResult1;
}
- intermediateResult1.setLgNominalEntries(_lgNominalEntries);
- intermediateResult1.setThreshold(_accumulatorThreshold);
intermediateResult1.merge(intermediateResult2);
return intermediateResult1;
}
@@ -616,10 +614,8 @@ public class DistinctCountCPCSketchAggregationFunction
@Override
public CpcSketchAccumulator deserializeIntermediateResult(CustomObject
customObject) {
- // The accumulator serializes as its sketch, so a new accumulator takes
the sketch read back
- CpcSketchAccumulator cpcSketchAccumulator = new CpcSketchAccumulator();
-
cpcSketchAccumulator.apply(CpcSketch.heapify(MemorySegment.ofBuffer(customObject.getBuffer())));
- return cpcSketchAccumulator;
+ return new CpcSketchAccumulator(_lgNominalEntries, _accumulatorThreshold,
+ CpcSketch.heapify(MemorySegment.ofBuffer(customObject.getBuffer())));
}
@Override
@@ -632,8 +628,6 @@ public class DistinctCountCPCSketchAggregationFunction
if (intermediateResult == null) {
return 0L;
}
- intermediateResult.setLgNominalEntries(_lgNominalEntries);
- intermediateResult.setThreshold(_accumulatorThreshold);
return Math.round(intermediateResult.getResult().getEstimate());
}
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountIntegerTupleSketchAggregationFunction.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountIntegerTupleSketchAggregationFunction.java
index 4e071706eb4..0dac654caab 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountIntegerTupleSketchAggregationFunction.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountIntegerTupleSketchAggregationFunction.java
@@ -51,9 +51,6 @@ public class
DistinctCountIntegerTupleSketchAggregationFunction extends IntegerT
if (accumulator == null) {
return 0L;
}
- accumulator.setNominalEntries(_nominalEntries);
- accumulator.setSetOperations(_setOps);
- accumulator.setThreshold(_accumulatorThreshold);
return Double.valueOf(accumulator.getResult().getEstimate()).longValue();
}
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawCPCSketchAggregationFunction.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawCPCSketchAggregationFunction.java
index 819bbc49987..0e305b03bfa 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawCPCSketchAggregationFunction.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawCPCSketchAggregationFunction.java
@@ -55,8 +55,6 @@ public class DistinctCountRawCPCSketchAggregationFunction
extends DistinctCountC
if (intermediateResult == null) {
return null;
}
- intermediateResult.setLgNominalEntries(_lgNominalEntries);
- intermediateResult.setThreshold(_accumulatorThreshold);
return new SerializedCPCSketch(intermediateResult.getResult());
}
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawThetaSketchAggregationFunction.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawThetaSketchAggregationFunction.java
index f12861f0317..85544b87821 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawThetaSketchAggregationFunction.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawThetaSketchAggregationFunction.java
@@ -61,8 +61,6 @@ public class DistinctCountRawThetaSketchAggregationFunction
extends DistinctCoun
for (Object object : accumulators) {
ThetaSketchAccumulator accumulator = convertSketchAccumulator(object);
- accumulator.setThreshold(_accumulatorThreshold);
- accumulator.setSetOperationBuilder(_setOperationBuilder);
mergedSketches.add(accumulator.getResult());
}
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountThetaSketchAggregationFunction.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountThetaSketchAggregationFunction.java
index ac30ec148d4..e1ea546eec6 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountThetaSketchAggregationFunction.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountThetaSketchAggregationFunction.java
@@ -1321,8 +1321,6 @@ public class DistinctCountThetaSketchAggregationFunction
mergedAccumulators.add(thetaSketchAccumulator1);
continue;
}
- thetaSketchAccumulator1.setSetOperationBuilder(_setOperationBuilder);
- thetaSketchAccumulator1.setThreshold(_accumulatorThreshold);
thetaSketchAccumulator1.merge(thetaSketchAccumulator2);
mergedAccumulators.add(thetaSketchAccumulator1);
}
@@ -1381,9 +1379,8 @@ public class DistinctCountThetaSketchAggregationFunction
int numBytes = buffer.getInt();
ByteBuffer slice = buffer.slice();
slice.limit(numBytes);
- ThetaSketchAccumulator thetaSketchAccumulator = new
ThetaSketchAccumulator();
-
thetaSketchAccumulator.apply(ObjectSerDeUtils.DATA_SKETCH_THETA_SER_DE.deserialize(slice));
- thetaSketchAccumulators.add(thetaSketchAccumulator);
+ thetaSketchAccumulators.add(new
ThetaSketchAccumulator(_setOperationBuilder, _accumulatorThreshold,
+ ObjectSerDeUtils.DATA_SKETCH_THETA_SER_DE.deserialize(slice)));
buffer.position(buffer.position() + numBytes);
}
return thetaSketchAccumulators;
@@ -1404,8 +1401,6 @@ public class DistinctCountThetaSketchAggregationFunction
for (Object accumulatorObject : accumulators) {
ThetaSketchAccumulator accumulator =
convertSketchAccumulator(accumulatorObject);
- accumulator.setThreshold(_accumulatorThreshold);
- accumulator.setSetOperationBuilder(_setOperationBuilder);
mergedSketches.add(accumulator.getResult());
}
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/IntegerTupleSketchAggregationFunction.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/IntegerTupleSketchAggregationFunction.java
index cf5396b8586..e9f80a1c6af 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/IntegerTupleSketchAggregationFunction.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/IntegerTupleSketchAggregationFunction.java
@@ -236,9 +236,6 @@ public class IntegerTupleSketchAggregationFunction
if (intermediateResult2.isEmpty()) {
return intermediateResult1;
}
- intermediateResult1.setThreshold(_accumulatorThreshold);
- intermediateResult1.setNominalEntries(_nominalEntries);
- intermediateResult1.setSetOperations(_setOps);
intermediateResult1.merge(intermediateResult2);
return intermediateResult1;
}
@@ -255,11 +252,8 @@ public class IntegerTupleSketchAggregationFunction
@Override
public TupleIntSketchAccumulator deserializeIntermediateResult(CustomObject
customObject) {
- // The accumulator serializes as its sketch, so a new accumulator takes
the sketch read back
- TupleIntSketchAccumulator tupleIntSketchAccumulator = new
TupleIntSketchAccumulator();
-
tupleIntSketchAccumulator.apply(TupleSketch.heapifySketch(MemorySegment.ofBuffer(customObject.getBuffer()),
- new IntegerSummaryDeserializer()));
- return tupleIntSketchAccumulator;
+ return new TupleIntSketchAccumulator(_setOps, _nominalEntries,
_accumulatorThreshold,
+
TupleSketch.heapifySketch(MemorySegment.ofBuffer(customObject.getBuffer()), new
IntegerSummaryDeserializer()));
}
@Override
@@ -278,9 +272,6 @@ public class IntegerTupleSketchAggregationFunction
}
accumulator = emptyAccumulator();
}
- accumulator.setNominalEntries(_nominalEntries);
- accumulator.setSetOperations(_setOps);
- accumulator.setThreshold(_accumulatorThreshold);
return
Base64.getEncoder().encodeToString(accumulator.getResult().toByteArray());
}
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileKLLAggregationFunction.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileKLLAggregationFunction.java
index d6ce82f0c4d..da4952c5225 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileKLLAggregationFunction.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileKLLAggregationFunction.java
@@ -104,10 +104,10 @@ public class PercentileKLLAggregationFunction
DataType dataType = valueSet.getValueType();
boolean singleValue = valueSet.isSingleValue();
if (dataType == DataType.BYTES && singleValue) {
- KllDoublesSketch[] deserializedSketches =
deserializeSketches(blockValSetMap.get(_expression).getBytesValuesSV());
+ byte[][] serializedSketches = valueSet.getBytesValuesSV();
forEachNotNull(length, valueSet, (from, to) -> {
for (int i = from; i < to; i++) {
- sketch.merge(deserializedSketches[i]);
+ sketch.merge(deserializeSketch(serializedSketches[i]));
}
});
return;
@@ -148,11 +148,11 @@ public class PercentileKLLAggregationFunction
DataType dataType = valueSet.getValueType();
boolean singleValue = valueSet.isSingleValue();
if (dataType == DataType.BYTES && singleValue) {
- KllDoublesSketch[] deserializedSketches =
deserializeSketches(blockValSetMap.get(_expression).getBytesValuesSV());
+ byte[][] serializedSketches = valueSet.getBytesValuesSV();
forEachNotNull(length, valueSet, (from, to) -> {
for (int i = from; i < to; i++) {
KllDoublesSketch sketch = getOrCreateSketch(groupByResultHolder,
groupKeyArray[i]);
- sketch.merge(deserializedSketches[i]);
+ sketch.merge(deserializeSketch(serializedSketches[i]));
}
});
return;
@@ -197,12 +197,13 @@ public class PercentileKLLAggregationFunction
DataType dataType = valueSet.getValueType();
boolean singleValue = valueSet.isSingleValue();
if (dataType == DataType.BYTES && singleValue) {
- KllDoublesSketch[] deserializedSketches =
deserializeSketches(blockValSetMap.get(_expression).getBytesValuesSV());
+ byte[][] serializedSketches = valueSet.getBytesValuesSV();
forEachNotNull(length, valueSet, (from, to) -> {
for (int i = from; i < to; i++) {
+ KllDoublesSketch rowSketch =
deserializeSketch(serializedSketches[i]);
for (int groupKey : groupKeysArray[i]) {
KllDoublesSketch sketch = getOrCreateSketch(groupByResultHolder,
groupKey);
- sketch.merge(deserializedSketches[i]);
+ sketch.merge(rowSketch);
}
}
});
@@ -265,13 +266,9 @@ public class PercentileKLLAggregationFunction
return sketch;
}
- /// Deserializes the sketches from the bytes.
- protected KllDoublesSketch[] deserializeSketches(byte[][]
serializedSketches) {
- KllDoublesSketch[] sketches = new
KllDoublesSketch[serializedSketches.length];
- for (int i = 0; i < serializedSketches.length; i++) {
- sketches[i] =
KllDoublesSketch.wrap(MemorySegment.ofArray(serializedSketches[i]).asReadOnly());
- }
- return sketches;
+ /// Wraps the serialized sketch for a non-null input row without copying its
bytes.
+ private static KllDoublesSketch deserializeSketch(byte[] serializedSketch) {
+ return
KllDoublesSketch.wrap(MemorySegment.ofArray(serializedSketch).asReadOnly());
}
@Nullable
diff --git
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumValuesIntegerTupleSketchAggregationFunction.java
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumValuesIntegerTupleSketchAggregationFunction.java
index d3b8c1bed8c..48cc151dd88 100644
---
a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumValuesIntegerTupleSketchAggregationFunction.java
+++
b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumValuesIntegerTupleSketchAggregationFunction.java
@@ -56,9 +56,6 @@ public class SumValuesIntegerTupleSketchAggregationFunction
extends IntegerTuple
return _nullHandlingEnabled ? null : 0L;
}
double retainedTotal = 0L;
- accumulator.setNominalEntries(_nominalEntries);
- accumulator.setSetOperations(_setOps);
- accumulator.setThreshold(_accumulatorThreshold);
TupleSketch<IntegerSummary> result = accumulator.getResult();
TupleSketchIterator<IntegerSummary> summaries = result.iterator();
while (summaries.next()) {
diff --git
a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/IntermediateResultByteBufferSerDeTest.java
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/IntermediateResultByteBufferSerDeTest.java
index a0c5463060d..5208272911f 100644
---
a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/IntermediateResultByteBufferSerDeTest.java
+++
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/IntermediateResultByteBufferSerDeTest.java
@@ -50,9 +50,11 @@ import org.apache.datasketches.cpc.CpcSketch;
import org.apache.datasketches.frequencies.FrequentItemsSketch;
import org.apache.datasketches.frequencies.FrequentLongsSketch;
import org.apache.datasketches.kll.KllDoublesSketch;
+import org.apache.datasketches.theta.ThetaSetOperationBuilder;
import org.apache.datasketches.theta.ThetaSketch;
import org.apache.datasketches.theta.UpdatableThetaSketch;
import org.apache.datasketches.tuple.aninteger.IntegerSummary;
+import org.apache.datasketches.tuple.aninteger.IntegerSummarySetOperations;
import org.apache.datasketches.tuple.aninteger.IntegerTupleSketch;
import org.apache.pinot.common.CustomObject;
import org.apache.pinot.common.request.Literal;
@@ -228,24 +230,19 @@ public class IntermediateResultByteBufferSerDeTest {
CpcSketch sketch = new CpcSketch(12);
sketch.update(1);
sketch.update(2);
- CpcSketchAccumulator accumulator = new CpcSketchAccumulator();
- accumulator.apply(sketch);
- return accumulator;
+ return new CpcSketchAccumulator(12, 2, sketch);
}
private static TupleIntSketchAccumulator tupleIntSketchAccumulator() {
IntegerTupleSketch sketch = new IntegerTupleSketch(12,
IntegerSummary.Mode.Sum);
sketch.update(1L, 1);
sketch.update(2L, 1);
- TupleIntSketchAccumulator accumulator = new TupleIntSketchAccumulator();
- accumulator.apply(sketch.compact());
- return accumulator;
+ return new TupleIntSketchAccumulator(
+ new IntegerSummarySetOperations(IntegerSummary.Mode.Sum,
IntegerSummary.Mode.Sum), 16384, 2, sketch.compact());
}
private static ThetaSketchAccumulator thetaSketchAccumulator() {
- ThetaSketchAccumulator accumulator = new ThetaSketchAccumulator();
- accumulator.apply(thetaSketch(1, 2));
- return accumulator;
+ return new ThetaSketchAccumulator(new ThetaSetOperationBuilder(), 2,
thetaSketch(1, 2));
}
private static ThetaSketch thetaSketch(long... values) {
diff --git
a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileKLLBytesAggregationTest.java
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileKLLBytesAggregationTest.java
new file mode 100644
index 00000000000..2bf3ca26f1c
--- /dev/null
+++
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileKLLBytesAggregationTest.java
@@ -0,0 +1,113 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.core.query.aggregation.function;
+
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.datasketches.kll.KllDoublesSketch;
+import org.apache.pinot.common.request.Literal;
+import org.apache.pinot.common.request.context.ExpressionContext;
+import org.apache.pinot.core.common.BlockValSet;
+import org.apache.pinot.core.common.SyntheticBlockValSets;
+import org.apache.pinot.core.query.aggregation.AggregationResultHolder;
+import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder;
+import org.roaringbitmap.RoaringBitmap;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+
+/// Checks that KLL aggregation only deserializes included, non-null BYTES
rows.
+/// Test state is local to each invocation, so these tests can run
concurrently.
+public class PercentileKLLBytesAggregationTest {
+ private static final ExpressionContext COLUMN =
ExpressionContext.forIdentifier("column");
+
+ @DataProvider
+ public Object[][] bytesInputs() {
+ KllDoublesSketch sketch = KllDoublesSketch.newHeapInstance();
+ sketch.update(42.0);
+ byte[] valid = sketch.toByteArray();
+ byte[] invalid = new byte[0];
+ return new Object[][]{
+ // Null BYTES values use an empty default, which is not a serialized
KLL sketch.
+ {true, 2, RoaringBitmap.bitmapOf(1), new byte[][]{valid, invalid}, 1},
+ // Entries after length must not be read, regardless of null handling.
+ {true, 1, null, new byte[][]{valid, invalid}, 1},
+ {false, 1, null, new byte[][]{valid, invalid}, 1},
+ {true, 0, null, new byte[][]{invalid}, 0},
+ {false, 0, null, new byte[][]{invalid}, 0},
+ {true, 2, RoaringBitmap.bitmapOf(0, 1), new byte[][]{invalid,
invalid}, 0}
+ };
+ }
+
+ @Test(dataProvider = "bytesInputs")
+ public void testAggregate(boolean nullHandlingEnabled, int length, @Nullable
RoaringBitmap nullBitmap,
+ byte[][] values, int expectedCount) {
+ PercentileKLLAggregationFunction function = function(nullHandlingEnabled);
+ AggregationResultHolder holder = function.createAggregationResultHolder();
+ function.aggregate(length, holder, block(nullBitmap, values));
+ assertResult(function.extractAggregationResult(holder), expectedCount);
+ }
+
+ @Test(dataProvider = "bytesInputs")
+ public void testAggregateGroupBySV(boolean nullHandlingEnabled, int length,
@Nullable RoaringBitmap nullBitmap,
+ byte[][] values, int expectedCount) {
+ PercentileKLLAggregationFunction function = function(nullHandlingEnabled);
+ GroupByResultHolder holder = function.createGroupByResultHolder(1, 1);
+ function.aggregateGroupBySV(length, new int[length], holder,
block(nullBitmap, values));
+ assertResult(function.extractGroupByResult(holder, 0), expectedCount);
+ }
+
+ @Test(dataProvider = "bytesInputs")
+ public void testAggregateGroupByMV(boolean nullHandlingEnabled, int length,
@Nullable RoaringBitmap nullBitmap,
+ byte[][] values, int expectedCount) {
+ PercentileKLLAggregationFunction function = function(nullHandlingEnabled);
+ GroupByResultHolder holder = function.createGroupByResultHolder(2, 2);
+ int[][] groupKeys = new int[length][];
+ for (int i = 0; i < length; i++) {
+ groupKeys[i] = new int[]{0, 1};
+ }
+ function.aggregateGroupByMV(length, groupKeys, holder, block(nullBitmap,
values));
+ assertResult(function.extractGroupByResult(holder, 0), expectedCount);
+ assertResult(function.extractGroupByResult(holder, 1), expectedCount);
+ }
+
+ private static PercentileKLLAggregationFunction function(boolean
nullHandlingEnabled) {
+ return new PercentileKLLAggregationFunction(
+ List.of(COLUMN,
ExpressionContext.forLiteral(Literal.doubleValue(50.0))), nullHandlingEnabled);
+ }
+
+ private static Map<ExpressionContext, BlockValSet> block(@Nullable
RoaringBitmap nullBitmap, byte[][] values) {
+ return Map.of(COLUMN, SyntheticBlockValSets.Bytes.create(nullBitmap,
values));
+ }
+
+ private static void assertResult(@Nullable KllDoublesSketch sketch, int
expectedCount) {
+ if (expectedCount == 0) {
+ assertTrue(sketch == null || sketch.isEmpty());
+ } else {
+ assertNotNull(sketch);
+ assertEquals(sketch.getN(), (long) expectedCount);
+ assertEquals(sketch.getQuantile(0.5), 42.0);
+ }
+ }
+}
diff --git
a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/SketchAccumulatorSerializationTest.java
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/SketchAccumulatorSerializationTest.java
new file mode 100644
index 00000000000..32352218bea
--- /dev/null
+++
b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/SketchAccumulatorSerializationTest.java
@@ -0,0 +1,117 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.core.query.aggregation.function;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import org.apache.datasketches.cpc.CpcSketch;
+import org.apache.datasketches.cpc.CpcUnion;
+import org.apache.datasketches.theta.ThetaSetOperationBuilder;
+import org.apache.datasketches.theta.ThetaUnion;
+import org.apache.datasketches.theta.UpdatableThetaSketch;
+import org.apache.datasketches.tuple.TupleUnion;
+import org.apache.datasketches.tuple.aninteger.IntegerSummary;
+import org.apache.datasketches.tuple.aninteger.IntegerSummarySetOperations;
+import org.apache.datasketches.tuple.aninteger.IntegerTupleSketch;
+import org.apache.pinot.common.CustomObject;
+import org.apache.pinot.common.request.Literal;
+import org.apache.pinot.common.request.context.ExpressionContext;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+
+/// Checks that deserialized sketches retain their bytes and use the receiving
query's settings when merged.
+/// All mutable state belongs to individual test invocations, so the tests can
run concurrently.
+public class SketchAccumulatorSerializationTest {
+ @DataProvider
+ public Object[][] thresholds() {
+ return new Object[][]{{1}, {3}};
+ }
+
+ @Test(dataProvider = "thresholds")
+ public void testTuple(int threshold) {
+ IntegerTupleSketch left = new IntegerTupleSketch(12,
IntegerSummary.Mode.Sum);
+ IntegerTupleSketch right = new IntegerTupleSketch(12,
IntegerSummary.Mode.Sum);
+ for (int i = 0; i < 1000; i++) {
+ left.update(i, 1);
+ right.update(i + 500, 2);
+ }
+ TupleUnion<IntegerSummary> expected =
+ new TupleUnion<>(128, new
IntegerSummarySetOperations(IntegerSummary.Mode.Max, IntegerSummary.Mode.Max));
+ expected.union(left.compact());
+ expected.union(right.compact());
+ assertLifecycle(new
IntegerTupleSketchAggregationFunction(arguments(threshold),
IntegerSummary.Mode.Max, false),
+ 48, left.compact().toByteArray(), right.compact().toByteArray(),
expected.getResult().toByteArray());
+ }
+
+ @Test(dataProvider = "thresholds")
+ public void testCpc(int threshold) {
+ CpcSketch left = new CpcSketch(12);
+ CpcSketch right = new CpcSketch(12);
+ for (int i = 0; i < 1000; i++) {
+ left.update(i);
+ right.update(i + 500);
+ }
+ CpcUnion expected = new CpcUnion(7);
+ expected.update(left);
+ expected.update(right);
+ assertLifecycle(new
DistinctCountCPCSketchAggregationFunction(arguments(threshold), false), 49,
+ left.toByteArray(), right.toByteArray(),
expected.getResult().toByteArray());
+ }
+
+ @Test(dataProvider = "thresholds")
+ public void testTheta(int threshold) {
+ UpdatableThetaSketch left = UpdatableThetaSketch.builder().build();
+ UpdatableThetaSketch right = UpdatableThetaSketch.builder().build();
+ for (int i = 0; i < 1000; i++) {
+ left.update(i);
+ right.update(i + 500);
+ }
+ ThetaUnion expected = new
ThetaSetOperationBuilder().setNominalEntries(128).buildUnion();
+ expected.union(left.compact());
+ expected.union(right.compact());
+ assertLifecycle(new
DistinctCountThetaSketchAggregationFunction(arguments(threshold), false), 21,
+ thetaList(left.compact().toByteArray()),
thetaList(right.compact().toByteArray()),
+ thetaList(expected.getResult(false, null).toByteArray()));
+ }
+
+ private static List<ExpressionContext> arguments(int threshold) {
+ return List.of(ExpressionContext.forIdentifier("column"),
ExpressionContext.forLiteral(
+ Literal.stringValue("nominalEntries=128;accumulatorThreshold=" +
threshold)));
+ }
+
+ private static byte[] thetaList(byte[] sketch) {
+ return ByteBuffer.allocate(3 * Integer.BYTES +
sketch.length).putInt(1).putInt(47).putInt(sketch.length)
+ .put(sketch).array();
+ }
+
+ private static <I> void assertLifecycle(AggregationFunction<I, ?> function,
int type, byte[] leftBytes,
+ byte[] rightBytes, byte[] expectedBytes) {
+ I left = function.deserializeIntermediateResult(new CustomObject(type,
ByteBuffer.wrap(leftBytes)));
+ // Receiving one sketch must not downsample it, including when the
accumulator threshold is one.
+ AggregationFunction.SerializedResult serialized =
function.serializeIntermediateResult(left);
+ assertEquals(serialized.getType(), type);
+ assertEquals(serialized.getBytes(), leftBytes);
+ I right = function.deserializeIntermediateResult(new CustomObject(type,
ByteBuffer.wrap(rightBytes)));
+ I merged = function.merge(left, right);
+ assertEquals(function.serializeIntermediateResult(merged).getBytes(),
expectedBytes);
+ }
+}
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/aggregator/IntegerTupleSketchValueAggregator.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/aggregator/IntegerTupleSketchValueAggregator.java
index ec37d68e6d0..4a75aa66dd2 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/aggregator/IntegerTupleSketchValueAggregator.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/aggregator/IntegerTupleSketchValueAggregator.java
@@ -22,6 +22,7 @@ import com.google.common.annotations.VisibleForTesting;
import java.lang.foreign.MemorySegment;
import java.util.List;
import javax.annotation.Nullable;
+import org.apache.datasketches.common.Util;
import org.apache.datasketches.tuple.TupleSketch;
import org.apache.datasketches.tuple.TupleUnion;
import org.apache.datasketches.tuple.aninteger.IntegerSummary;
@@ -126,22 +127,13 @@ public class IntegerTupleSketchValueAggregator implements
ValueAggregator<byte[]
return true;
}
- /// Returns the maximum number of storage bytes required for a Compact
Integer Tuple Sketch with the given
- /// number of actual entries. Note that this assumes the worst case of the
sketch in
- /// estimation mode, which requires storing theta and count.
- /// @return the maximum number of storage bytes required for a Compact
Integer Tuple Sketch with the given number
- /// of entries.
+ /// Returns the maximum serialized size after unioning at the configured
nominal entries. The union rounds the
+ /// nominal entries up to a power of two. In estimation mode the compact
sketch has a 24-byte header, followed by
+ /// an 8-byte hash and a 5-byte integer summary (value and mode) for each
retained entry.
@Override
public int getMaxAggregatedValueByteSize() {
- if (_nominalEntries == 0) {
- return 8;
- }
- if (_nominalEntries == 1) {
- return 16;
- }
- int longSizeInBytes = Long.BYTES;
- int intSizeInBytes = Integer.BYTES;
- return (_nominalEntries * (longSizeInBytes + intSizeInBytes)) + 24;
+ long nominalEntries = Util.ceilingPowerOf2(_nominalEntries);
+ return Math.toIntExact(nominalEntries * (Long.BYTES + Integer.BYTES +
Byte.BYTES) + 24);
}
@Override
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/customobject/CpcSketchAccumulator.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/customobject/CpcSketchAccumulator.java
index abcd6e9380b..82c4c23fffd 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/customobject/CpcSketchAccumulator.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/customobject/CpcSketchAccumulator.java
@@ -22,26 +22,20 @@ import org.apache.datasketches.cpc.CpcSketch;
import org.apache.datasketches.cpc.CpcUnion;
-/// Intermediate state used by `DistinctCountCPCSketchAggregationFunction`
which gives
-/// the end user more control over how sketches are merged for performance.
-/// The end user can set parameters that trade-off more memory usage for more
pre-aggregation.
+/// Buffers CPC sketches for deferred merges with the configured precision.
+/// Instances are mutable and not thread-safe.
public class CpcSketchAccumulator extends CustomObjectAccumulator<CpcSketch> {
- private int _lgNominalEntries = 4;
+ private final int _lgNominalEntries;
private CpcUnion _union;
- public CpcSketchAccumulator() {
- }
-
- // Note: The accumulator is serialized as a sketch. This means that the
majority of the processing
- // happens on serialization. Therefore, when deserialized, the values may be
null and will
- // require re-initialisation. Since the primary use case is at query time
for the Broker
- // and Server, these properties are already in memory and are re-set.
public CpcSketchAccumulator(int lgNominalEntries, int threshold) {
super(threshold);
_lgNominalEntries = lgNominalEntries;
}
- public void setLgNominalEntries(int lgNominalEntries) {
+ /// Retains a deserialized sketch without unioning it, using the receiving
query's configuration for future merges.
+ public CpcSketchAccumulator(int lgNominalEntries, int threshold, CpcSketch
initialSketch) {
+ super(threshold, initialSketch);
_lgNominalEntries = lgNominalEntries;
}
@@ -61,20 +55,13 @@ public class CpcSketchAccumulator extends
CustomObjectAccumulator<CpcSketch> {
@Override
public CpcSketch getResult() {
+ // Return a single pending sketch unchanged. Its precision is not adjusted
until a union is needed.
+ if (getNumInputs() == 1 && _accumulator.size() == 1) {
+ return _accumulator.get(0);
+ }
if (_union == null) {
_union = new CpcUnion(_lgNominalEntries);
}
- // Return the default update "gadget" sketch as a compact sketch
- if (isEmpty()) {
- return _union.getResult();
- }
- // Corner-case: the parameters are not strictly respected when there is a
single sketch.
- // This single sketch might have been the result of a previously
accumulated union and
- // would already have the parameters set. The sketch is returned as-is
without adjusting
- // nominal entries which requires an additional union operation.
- if (getNumInputs() == 1 && _accumulator != null && _accumulator.size() ==
1) {
- return _accumulator.get(0);
- }
flush();
return _union.getResult();
}
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/customobject/CustomObjectAccumulator.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/customobject/CustomObjectAccumulator.java
index 55cba5452a7..c02d37f030d 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/customobject/CustomObjectAccumulator.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/customobject/CustomObjectAccumulator.java
@@ -22,34 +22,27 @@ import com.google.common.base.Preconditions;
import java.util.ArrayList;
-/// Intermediate state used by some aggregation functions which gives the end
user more control over how custom objects
-/// are merged for performance reasons. Some custom objects such as
DataSketches have better merge performance when
-/// more than two items are merged at once through the elimination of
intermediate bookkeeping overheads.
-///
-/// The end user can set a value for the "threshold" parameter that defers the
merge operation until at least as many
-/// items are ready to be merged, or the callee forces the merge directly via
"getResult" - e.g. on serialization.
-/// This data structure trades-off more memory usage for a greater degree of
pre-aggregation in the accumulator state.
+/// Buffers intermediate values until the configured threshold is reached or a
result is requested.
+/// Subclasses merge pending values in [#flush()]. Instances are mutable and
not thread-safe.
public abstract class CustomObjectAccumulator<T> {
protected ArrayList<T> _accumulator;
- private int _threshold;
+ private final int _threshold;
private int _numInputs = 0;
- public CustomObjectAccumulator() {
- this(2);
- }
-
- public CustomObjectAccumulator(int threshold) {
- setThreshold(threshold);
- }
-
- /// Sets the threshold that determines how much memory to use for the
internal accumulator state before
- /// the intermediate state is merged.
- /// @param threshold the threshold \[> 0\].
- public void setThreshold(int threshold) {
+ protected CustomObjectAccumulator(int threshold) {
Preconditions.checkArgument(threshold > 0, "Invalid threshold: %s, must be
positive", threshold);
_threshold = threshold;
}
+ /// Retains an initial result without flushing, even when the threshold is
one. A deserialized sketch can then
+ /// be returned unchanged until another input is added, rather than being
unioned again during deserialization.
+ protected CustomObjectAccumulator(int threshold, T initialResult) {
+ this(threshold);
+ _accumulator = new ArrayList<>(1);
+ _accumulator.add(Preconditions.checkNotNull(initialResult));
+ _numInputs = 1;
+ }
+
/// Returns the configured threshold for this accumulator.
public int getThreshold() {
return _threshold;
@@ -65,9 +58,7 @@ public abstract class CustomObjectAccumulator<T> {
return _numInputs == 0;
}
- /// Forces the item T in internal state to be merged with all pending items
in the accumulator state
- /// and returns the result.
- /// @return T result of the merge.
+ /// Returns the accumulated result, merging pending inputs as needed.
public abstract T getResult();
/// Merges the pending items into the internal state and clears them,
without building a result.
@@ -83,8 +74,7 @@ public abstract class CustomObjectAccumulator<T> {
applyInternal(result);
}
- /// Adds a new item to the accumulator state. If the accumulator state is
equal to the threshold value,
- /// the internal state is updated and the accumulator state is cleared.
+ /// Adds an input and flushes pending inputs when their count reaches the
threshold.
/// @param item the item to add to the accumulator state, cannot be null.
public void apply(T item) {
Preconditions.checkNotNull(item);
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/customobject/ThetaSketchAccumulator.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/customobject/ThetaSketchAccumulator.java
index 94dba63d18d..178f05ebdf9 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/customobject/ThetaSketchAccumulator.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/customobject/ThetaSketchAccumulator.java
@@ -24,30 +24,21 @@ import org.apache.datasketches.theta.ThetaSketch;
import org.apache.datasketches.theta.ThetaUnion;
-/// Intermediate state used by `DistinctCountThetaSketchAggregationFunction`
which gives
-/// the end user more control over how sketches are merged for performance.
-/// In particular, the Theta Sketch Union "early-stop" optimisation can be
used - ordered sketches require no further
-/// processing beyond the minimum Theta value.
-/// The union operation initialises an empty "gadget" bookkeeping sketch that
is updated with hashed entries
-/// that fall below the minimum Theta value for all input sketches ("Broder
Rule"). When the initial Theta value is
-/// set to the minimum immediately, further gains can be realised.
+/// Buffers theta sketches and unions pending inputs in ascending theta order
to reduce intermediate retained entries.
+/// Instances are mutable and not thread-safe. The supplied builder must not
be modified after construction.
public class ThetaSketchAccumulator extends
CustomObjectAccumulator<ThetaSketch> {
- private ThetaSetOperationBuilder _setOperationBuilder = new
ThetaSetOperationBuilder();
+ private final ThetaSetOperationBuilder _setOperationBuilder;
private ThetaUnion _union;
- public ThetaSketchAccumulator() {
- }
-
- // Note: The accumulator is serialized as a sketch. This means that the
majority of the processing
- // happens on serialization. Therefore, when deserialized, the values may be
null and will
- // require re-initialisation. Since the primary use case is at query time
for the Broker
- // and Server, these properties are already in memory and are re-set.
public ThetaSketchAccumulator(ThetaSetOperationBuilder setOperationBuilder,
int threshold) {
super(threshold);
_setOperationBuilder = setOperationBuilder;
}
- public void setSetOperationBuilder(ThetaSetOperationBuilder
setOperationBuilder) {
+ /// Retains a deserialized sketch without unioning it, using the receiving
query's configuration for future merges.
+ public ThetaSketchAccumulator(ThetaSetOperationBuilder setOperationBuilder,
int threshold,
+ ThetaSketch initialSketch) {
+ super(threshold, initialSketch);
_setOperationBuilder = setOperationBuilder;
}
@@ -60,15 +51,7 @@ public class ThetaSketchAccumulator extends
CustomObjectAccumulator<ThetaSketch>
_union = _setOperationBuilder.buildUnion();
}
- // Performance optimization: ensure that the minimum Theta is used for
"early stop".
- // The "early stop" optimization is implemented in the Apache Datasketches
ThetaUnion operation for
- // ordered and compact Theta sketches. Internally, a compact and ordered
Theta sketch can be
- // compared to a sorted array of K items. When performing a union, only
those items from
- // the input sketch less than Theta need to be processed. The loop
terminates as soon as a hash
- // is seen that is > Theta.
- // The following "sort" improves on this further by selecting the minimal
Theta value up-front,
- // which results in fewer redundant entries being retained and
subsequently discarded during the
- // union operation.
+ // Start with the lowest theta so later inputs do not retain hashes that
would be discarded by that input.
_accumulator.sort(Comparator.comparingDouble(ThetaSketch::getTheta));
for (ThetaSketch accumulatedSketch : _accumulator) {
_union.union(accumulatedSketch);
@@ -78,20 +61,13 @@ public class ThetaSketchAccumulator extends
CustomObjectAccumulator<ThetaSketch>
@Override
public ThetaSketch getResult() {
+ // Return a single pending sketch unchanged. Its nominal entries are not
adjusted until a union is needed.
+ if (getNumInputs() == 1 && _accumulator.size() == 1) {
+ return _accumulator.get(0);
+ }
if (_union == null) {
_union = _setOperationBuilder.buildUnion();
}
- // Return the default update "gadget" sketch as a compact sketch
- if (isEmpty()) {
- return _union.getResult(false, null);
- }
- // Corner-case: the parameters are not strictly respected when there is a
single sketch.
- // This single sketch might have been the result of a previously
accumulated union and
- // would already have the parameters set. The sketch is returned as-is
without adjusting
- // nominal entries which requires an additional union operation.
- if (getNumInputs() == 1 && _accumulator != null && _accumulator.size() ==
1) {
- return _accumulator.get(0);
- }
flush();
return _union.getResult(false, null);
}
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/customobject/TupleIntSketchAccumulator.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/customobject/TupleIntSketchAccumulator.java
index 3f5a16392e6..ccc11c40aaf 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/customobject/TupleIntSketchAccumulator.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/customobject/TupleIntSketchAccumulator.java
@@ -25,38 +25,25 @@ import
org.apache.datasketches.tuple.aninteger.IntegerSummary;
import org.apache.datasketches.tuple.aninteger.IntegerSummarySetOperations;
-/// Intermediate state used by `IntegerTupleSketchAggregationFunction` which
gives
-/// the end user more control over how sketches are merged for performance.
-/// In particular, the Theta Sketch Union "early-stop" optimisation can be
used - ordered sketches require no further
-/// processing beyond the minimum Theta value. This applies to Tuple sketches
because they are an extension of the
-/// Theta sketch.
-/// The union operation initialises an empty "gadget" bookkeeping sketch that
is updated with hashed entries
-/// that fall below the minimum Theta value for all input sketches ("Broder
Rule"). When the initial Theta value is
-/// set to the minimum immediately, further gains can be realised.
+/// Buffers integer tuple sketches and reuses a union with the configured
summary operations and nominal entries.
+/// Instances are mutable and not thread-safe.
public class TupleIntSketchAccumulator extends
CustomObjectAccumulator<TupleSketch<IntegerSummary>> {
- private IntegerSummarySetOperations _setOperations;
- private int _nominalEntries;
+ private final IntegerSummarySetOperations _setOperations;
+ private final int _nominalEntries;
private TupleUnion<IntegerSummary> _union;
- public TupleIntSketchAccumulator() {
- }
-
- // Note: The accumulator is serialized as a sketch. This means that the
majority of the processing
- // happens on serialization. Therefore, when deserialized, the values may be
null and will
- // require re-initialisation. Since the primary use case is at query time
for the Broker
- // and Server, these properties are already in memory and are re-set.
public TupleIntSketchAccumulator(IntegerSummarySetOperations setOperations,
int nominalEntries, int threshold) {
super(threshold);
_nominalEntries = nominalEntries;
_setOperations = setOperations;
}
- public void setSetOperations(IntegerSummarySetOperations setOperations) {
- _setOperations = setOperations;
- }
-
- public void setNominalEntries(int nominalEntries) {
+ /// Retains a deserialized sketch without unioning it, using the receiving
query's configuration for future merges.
+ public TupleIntSketchAccumulator(IntegerSummarySetOperations setOperations,
int nominalEntries, int threshold,
+ TupleSketch<IntegerSummary> initialSketch) {
+ super(threshold, initialSketch);
_nominalEntries = nominalEntries;
+ _setOperations = setOperations;
}
@Override
@@ -68,15 +55,6 @@ public class TupleIntSketchAccumulator extends
CustomObjectAccumulator<TupleSket
_union = new TupleUnion<>(_nominalEntries, _setOperations);
}
- // Performance optimization: ensure that the minimum Theta is used for
"early stop".
- // The "early stop" optimization is implemented in the Apache Datasketches
TupleUnion operation for
- // ordered and compact Theta sketches. Internally, a compact and ordered
Theta sketch can be
- // compared to a sorted array of K items. When performing a union, only
those items from
- // the input sketch less than Theta need to be processed. The loop
terminates as soon as a hash
- // is seen that is > Theta.
- // The following "sort" improves on this further by selecting the minimal
Theta value up-front,
- // which results in fewer redundant entries being retained and
subsequently discarded during the
- // union operation.
_accumulator.sort(Comparator.comparingDouble(TupleSketch::getTheta));
for (TupleSketch<IntegerSummary> accumulatedSketch : _accumulator) {
_union.union(accumulatedSketch);
@@ -86,20 +64,13 @@ public class TupleIntSketchAccumulator extends
CustomObjectAccumulator<TupleSket
@Override
public TupleSketch<IntegerSummary> getResult() {
+ // Return a single pending sketch unchanged. Its nominal entries are not
adjusted until a union is needed.
+ if (getNumInputs() == 1 && _accumulator.size() == 1) {
+ return _accumulator.get(0);
+ }
if (_union == null) {
_union = new TupleUnion<>(_nominalEntries, _setOperations);
}
- // Return the default update "gadget" sketch as a compact sketch
- if (isEmpty()) {
- return _union.getResult();
- }
- // Corner-case: the parameters are not strictly respected when there is a
single sketch.
- // This single sketch might have been the result of a previously
accumulated union and
- // would already have the parameters set. The sketch is returned as-is
without adjusting
- // nominal entries which requires an additional union operation.
- if (getNumInputs() == 1 && _accumulator != null && _accumulator.size() ==
1) {
- return _accumulator.get(0);
- }
flush();
return _union.getResult();
}
diff --git
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/aggregator/IntegerTupleSketchValueAggregatorTest.java
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/aggregator/IntegerTupleSketchValueAggregatorTest.java
index 4ea2d49fab5..6e2825511b3 100644
---
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/aggregator/IntegerTupleSketchValueAggregatorTest.java
+++
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/aggregator/IntegerTupleSketchValueAggregatorTest.java
@@ -23,9 +23,13 @@ import org.apache.datasketches.tuple.TupleSketch;
import org.apache.datasketches.tuple.TupleUnion;
import org.apache.datasketches.tuple.aninteger.IntegerSummary;
import org.apache.datasketches.tuple.aninteger.IntegerTupleSketch;
+import org.apache.pinot.common.request.Literal;
+import org.apache.pinot.common.request.context.ExpressionContext;
+import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
public class IntegerTupleSketchValueAggregatorTest {
@@ -60,7 +64,7 @@ public class IntegerTupleSketchValueAggregatorTest {
new IntegerTupleSketchValueAggregator(List.of(),
IntegerSummary.Mode.Sum);
TupleSketch<IntegerSummary> merged = toSketch(agg.applyAggregatedValue(s1,
s2));
assertEquals(merged.getEstimate(), 2.0);
- assertEquals(agg.getMaxAggregatedValueByteSize(), 196632);
+ assertEquals(agg.getMaxAggregatedValueByteSize(), 213016);
}
@Test
@@ -73,7 +77,36 @@ public class IntegerTupleSketchValueAggregatorTest {
new IntegerTupleSketchValueAggregator(List.of(),
IntegerSummary.Mode.Sum);
TupleSketch<IntegerSummary> merged = toSketch(agg.applyRawValue(s1,
agg.serializeAggregatedValue(s2)));
assertEquals(merged.getEstimate(), 2.0);
- assertEquals(agg.getMaxAggregatedValueByteSize(), 196632);
+ assertEquals(agg.getMaxAggregatedValueByteSize(), 213016);
+ }
+
+ @DataProvider
+ public Object[][] nominalEntries() {
+ return new Object[][]{{null, 16384}, {1, 1}, {3, 4}, {1000, 1024}};
+ }
+
+ @Test(dataProvider = "nominalEntries")
+ public void testMaxSerializedSize(Integer nominalEntries, int
retainedEntries) {
+ List<ExpressionContext> arguments = nominalEntries == null
+ ? List.of()
+ :
List.of(ExpressionContext.forLiteral(Literal.intValue(nominalEntries)));
+ IntegerTupleSketchValueAggregator agg = new
IntegerTupleSketchValueAggregator(arguments, IntegerSummary.Mode.Sum);
+ int maxSize = agg.getMaxAggregatedValueByteSize();
+ for (int inputSize : new int[]{0, 1, retainedEntries * 2}) {
+ IntegerTupleSketch input = new IntegerTupleSketch(16,
IntegerSummary.Mode.Sum);
+ for (int i = 0; i < inputSize; i++) {
+ input.update(i, 1);
+ }
+ Object result =
agg.getInitialAggregatedValue(input.compact().toByteArray());
+ byte[] bytes = agg.serializeAggregatedValue(result);
+ assertTrue(bytes.length <= maxSize, "Serialized sketch exceeds the size
reserved by the index writer");
+ if (inputSize > retainedEntries) {
+ TupleSketch<IntegerSummary> sketch = toSketch(result);
+ assertTrue(sketch.isEstimationMode());
+ assertEquals(sketch.getRetainedEntries(), retainedEntries);
+ assertEquals(bytes.length, maxSize);
+ }
+ }
}
@SuppressWarnings("unchecked")
diff --git
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/customobject/CpcSketchAccumulatorTest.java
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/customobject/CpcSketchAccumulatorTest.java
index 86326de573e..9d3e88f0436 100644
---
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/customobject/CpcSketchAccumulatorTest.java
+++
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/customobject/CpcSketchAccumulatorTest.java
@@ -22,9 +22,13 @@ package org.apache.pinot.segment.local.customobject;
import java.util.stream.IntStream;
import org.apache.datasketches.cpc.CpcSketch;
import org.apache.datasketches.cpc.CpcUnion;
-import org.testng.Assert;
import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertSame;
+import static org.testng.Assert.assertTrue;
+
public class CpcSketchAccumulatorTest {
private final int _lgNominalEntries = 20;
@@ -33,8 +37,8 @@ public class CpcSketchAccumulatorTest {
@Test
public void testEmptyAccumulator() {
CpcSketchAccumulator accumulator = new
CpcSketchAccumulator(_lgNominalEntries, 2);
- Assert.assertTrue(accumulator.isEmpty());
- Assert.assertEquals(accumulator.getResult().getEstimate(), 0.0);
+ assertTrue(accumulator.isEmpty());
+ assertEquals(accumulator.getResult().getEstimate(), 0.0);
}
@Test
@@ -45,8 +49,47 @@ public class CpcSketchAccumulatorTest {
CpcSketchAccumulator accumulator = new
CpcSketchAccumulator(_lgNominalEntries, 2);
accumulator.apply(sketch);
- Assert.assertFalse(accumulator.isEmpty());
- Assert.assertEquals(accumulator.getResult().getEstimate(),
sketch.getEstimate());
+ assertFalse(accumulator.isEmpty());
+ assertEquals(accumulator.getResult().getEstimate(), sketch.getEstimate());
+ }
+
+ @Test
+ public void testSeededSketchIsReturnedUnchanged() {
+ for (int numEntries : new int[]{0, 1000}) {
+ CpcSketch sketch = new CpcSketch(12);
+ IntStream.range(0, numEntries).forEach(sketch::update);
+ for (int threshold = 1; threshold <= 3; threshold++) {
+ CpcSketchAccumulator accumulator = new CpcSketchAccumulator(10,
threshold, sketch);
+
+ assertFalse(accumulator.isEmpty());
+ assertEquals(accumulator.getNumInputs(), 1);
+ assertSame(accumulator.getResult(), sketch);
+ assertSame(accumulator.getResult(), sketch);
+ }
+ }
+ }
+
+ @Test
+ public void testMergeAfterReadingSeededSketch() {
+ CpcSketch input1 = new CpcSketch(12);
+ IntStream.range(0, 1000).forEach(input1::update);
+ byte[] bytes = input1.toByteArray();
+ CpcSketch sketch2 = new CpcSketch(12);
+ IntStream.range(500, 1500).forEach(sketch2::update);
+ for (int threshold = 1; threshold <= 3; threshold++) {
+ CpcSketch sketch1 = CpcSketch.heapify(bytes);
+ CpcSketchAccumulator accumulator = new CpcSketchAccumulator(10,
threshold, sketch1);
+ assertEquals(accumulator.getResult().toByteArray(), bytes);
+ accumulator.merge(new CpcSketchAccumulator(10, threshold, sketch2));
+
+ CpcUnion expected = new CpcUnion(10);
+ expected.update(sketch1);
+ expected.update(sketch2);
+ CpcSketch result = accumulator.getResult();
+ assertEquals(accumulator.getNumInputs(), 2);
+ assertEquals(result.getLgK(), 10);
+ assertEquals(result.getEstimate(), expected.getResult().getEstimate(),
0.0);
+ }
}
@Test
@@ -62,7 +105,7 @@ public class CpcSketchAccumulatorTest {
accumulator2.apply(sketch2);
accumulator1.merge(accumulator2);
- Assert.assertEquals(accumulator1.getResult().getEstimate(),
sketch1.getEstimate() + sketch2.getEstimate(),
+ assertEquals(accumulator1.getResult().getEstimate(), sketch1.getEstimate()
+ sketch2.getEstimate(),
_epsilon);
}
@@ -77,7 +120,7 @@ public class CpcSketchAccumulatorTest {
accumulator.apply(sketch1);
accumulator.apply(sketch2);
- Assert.assertEquals(accumulator.getResult().getEstimate(),
sketch1.getEstimate() + sketch2.getEstimate(), _epsilon);
+ assertEquals(accumulator.getResult().getEstimate(), sketch1.getEstimate()
+ sketch2.getEstimate(), _epsilon);
}
@Test
@@ -92,7 +135,7 @@ public class CpcSketchAccumulatorTest {
accumulator.apply(sketch);
expected.update(sketch);
}
- Assert.assertEquals(accumulator.getResult().getEstimate(),
expected.getResult().getEstimate(), _epsilon,
+ assertEquals(accumulator.getResult().getEstimate(),
expected.getResult().getEstimate(), _epsilon,
"threshold " + threshold);
}
}
@@ -107,8 +150,8 @@ public class CpcSketchAccumulatorTest {
accumulator.apply(sketch);
}
double first = accumulator.getResult().getEstimate();
- Assert.assertEquals(accumulator.getResult().getEstimate(), first, 0.0);
- Assert.assertEquals(accumulator.getResult().getEstimate(), first, 0.0);
+ assertEquals(accumulator.getResult().getEstimate(), first, 0.0);
+ assertEquals(accumulator.getResult().getEstimate(), first, 0.0);
}
@Test
@@ -118,7 +161,7 @@ public class CpcSketchAccumulatorTest {
accumulator.merge(emptyAccumulator);
- Assert.assertTrue(accumulator.isEmpty());
- Assert.assertEquals(accumulator.getResult().getEstimate(), 0.0);
+ assertTrue(accumulator.isEmpty());
+ assertEquals(accumulator.getResult().getEstimate(), 0.0);
}
}
diff --git
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/customobject/ThetaSketchAccumulatorTest.java
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/customobject/ThetaSketchAccumulatorTest.java
index 088d4f9ddfc..00e4296a510 100644
---
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/customobject/ThetaSketchAccumulatorTest.java
+++
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/customobject/ThetaSketchAccumulatorTest.java
@@ -19,15 +19,20 @@
package org.apache.pinot.segment.local.customobject;
+import java.lang.foreign.MemorySegment;
import java.util.stream.IntStream;
import org.apache.datasketches.theta.ThetaSetOperationBuilder;
import org.apache.datasketches.theta.ThetaSketch;
import org.apache.datasketches.theta.ThetaUnion;
import org.apache.datasketches.theta.UpdatableThetaSketch;
-import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertSame;
+import static org.testng.Assert.assertTrue;
+
public class ThetaSketchAccumulatorTest {
private ThetaSetOperationBuilder _setOperationBuilder;
@@ -40,8 +45,8 @@ public class ThetaSketchAccumulatorTest {
@Test
public void testEmptyAccumulator() {
ThetaSketchAccumulator accumulator = new
ThetaSketchAccumulator(_setOperationBuilder, 2);
- Assert.assertTrue(accumulator.isEmpty());
- Assert.assertEquals(accumulator.getResult().getEstimate(), 0.0);
+ assertTrue(accumulator.isEmpty());
+ assertEquals(accumulator.getResult().getEstimate(), 0.0);
}
@Test
@@ -53,8 +58,51 @@ public class ThetaSketchAccumulatorTest {
ThetaSketchAccumulator accumulator = new
ThetaSketchAccumulator(_setOperationBuilder, 2);
accumulator.apply(sketch);
- Assert.assertFalse(accumulator.isEmpty());
- Assert.assertEquals(accumulator.getResult().getEstimate(),
sketch.getEstimate());
+ assertFalse(accumulator.isEmpty());
+ assertEquals(accumulator.getResult().getEstimate(), sketch.getEstimate());
+ }
+
+ @Test
+ public void testSeededSketchIsReturnedUnchanged() {
+ ThetaSetOperationBuilder builder = new
ThetaSetOperationBuilder().setNominalEntries(128);
+ for (int numEntries : new int[]{0, 1000}) {
+ UpdatableThetaSketch input = UpdatableThetaSketch.builder().build();
+ IntStream.range(0, numEntries).forEach(input::update);
+ ThetaSketch sketch = input.compact();
+ for (int threshold = 1; threshold <= 3; threshold++) {
+ ThetaSketchAccumulator accumulator = new
ThetaSketchAccumulator(builder, threshold, sketch);
+
+ assertFalse(accumulator.isEmpty());
+ assertEquals(accumulator.getNumInputs(), 1);
+ assertSame(accumulator.getResult(), sketch);
+ assertSame(accumulator.getResult(), sketch);
+ }
+ }
+ }
+
+ @Test
+ public void testMergeAfterReadingSeededSketch() {
+ UpdatableThetaSketch input1 = UpdatableThetaSketch.builder().build();
+ IntStream.range(0, 1000).forEach(input1::update);
+ byte[] bytes = input1.compact().toByteArray();
+ UpdatableThetaSketch input2 = UpdatableThetaSketch.builder().build();
+ IntStream.range(500, 1500).forEach(input2::update);
+ ThetaSketch sketch2 = input2.compact();
+ ThetaSetOperationBuilder builder = new
ThetaSetOperationBuilder().setNominalEntries(128);
+ for (int threshold = 1; threshold <= 3; threshold++) {
+ ThetaSketch sketch1 = ThetaSketch.heapify(MemorySegment.ofArray(bytes));
+ ThetaSketchAccumulator accumulator = new ThetaSketchAccumulator(builder,
threshold, sketch1);
+ assertEquals(accumulator.getResult().toByteArray(), bytes);
+ accumulator.merge(new ThetaSketchAccumulator(builder, threshold,
sketch2));
+
+ ThetaUnion expected = builder.buildUnion();
+ expected.union(sketch1);
+ expected.union(sketch2);
+ ThetaSketch result = accumulator.getResult();
+ assertEquals(accumulator.getNumInputs(), 2);
+ assertEquals(result.getRetainedEntries(true), 128);
+ assertEquals(result.getEstimate(), expected.getResult().getEstimate(),
0.0);
+ }
}
@Test
@@ -72,7 +120,7 @@ public class ThetaSketchAccumulatorTest {
accumulator2.apply(sketch2);
accumulator1.merge(accumulator2);
- Assert.assertEquals(accumulator1.getResult().getEstimate(),
sketch1.getEstimate() + sketch2.getEstimate());
+ assertEquals(accumulator1.getResult().getEstimate(), sketch1.getEstimate()
+ sketch2.getEstimate());
}
@Test
@@ -88,7 +136,7 @@ public class ThetaSketchAccumulatorTest {
accumulator.apply(sketch1);
accumulator.apply(sketch2);
- Assert.assertEquals(accumulator.getResult().getEstimate(),
sketch1.getEstimate() + sketch2.getEstimate());
+ assertEquals(accumulator.getResult().getEstimate(), sketch1.getEstimate()
+ sketch2.getEstimate());
}
@Test
@@ -104,7 +152,7 @@ public class ThetaSketchAccumulatorTest {
accumulator.apply(sketch);
expected.union(sketch);
}
- Assert.assertEquals(accumulator.getResult().getEstimate(),
expected.getResult().getEstimate(), 0.0,
+ assertEquals(accumulator.getResult().getEstimate(),
expected.getResult().getEstimate(), 0.0,
"threshold " + threshold);
}
}
@@ -119,8 +167,8 @@ public class ThetaSketchAccumulatorTest {
accumulator.apply(input.compact());
}
double first = accumulator.getResult().getEstimate();
- Assert.assertEquals(accumulator.getResult().getEstimate(), first, 0.0);
- Assert.assertEquals(accumulator.getResult().getEstimate(), first, 0.0);
+ assertEquals(accumulator.getResult().getEstimate(), first, 0.0);
+ assertEquals(accumulator.getResult().getEstimate(), first, 0.0);
}
@Test
@@ -130,7 +178,7 @@ public class ThetaSketchAccumulatorTest {
accumulator.merge(emptyAccumulator);
- Assert.assertTrue(accumulator.isEmpty());
- Assert.assertEquals(accumulator.getResult().getEstimate(), 0.0);
+ assertTrue(accumulator.isEmpty());
+ assertEquals(accumulator.getResult().getEstimate(), 0.0);
}
}
diff --git
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/customobject/TupleIntSketchAccumulatorTest.java
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/customobject/TupleIntSketchAccumulatorTest.java
index ba90de81b65..91567c30207 100644
---
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/customobject/TupleIntSketchAccumulatorTest.java
+++
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/customobject/TupleIntSketchAccumulatorTest.java
@@ -19,18 +19,24 @@
package org.apache.pinot.segment.local.customobject;
+import java.lang.foreign.MemorySegment;
import java.util.stream.IntStream;
import org.apache.datasketches.tuple.CompactTupleSketch;
import org.apache.datasketches.tuple.TupleSketch;
import org.apache.datasketches.tuple.TupleSketchIterator;
import org.apache.datasketches.tuple.TupleUnion;
import org.apache.datasketches.tuple.aninteger.IntegerSummary;
+import org.apache.datasketches.tuple.aninteger.IntegerSummaryDeserializer;
import org.apache.datasketches.tuple.aninteger.IntegerSummarySetOperations;
import org.apache.datasketches.tuple.aninteger.IntegerTupleSketch;
-import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertSame;
+import static org.testng.Assert.assertTrue;
+
public class TupleIntSketchAccumulatorTest {
private IntegerSummarySetOperations _setOps;
@@ -45,8 +51,8 @@ public class TupleIntSketchAccumulatorTest {
@Test
public void testEmptyAccumulator() {
TupleIntSketchAccumulator accumulator = new
TupleIntSketchAccumulator(_setOps, _nominalEntries, 2);
- Assert.assertTrue(accumulator.isEmpty());
- Assert.assertEquals(accumulator.getResult().getEstimate(), 0.0);
+ assertTrue(accumulator.isEmpty());
+ assertEquals(accumulator.getResult().getEstimate(), 0.0);
}
@Test
@@ -58,8 +64,78 @@ public class TupleIntSketchAccumulatorTest {
TupleIntSketchAccumulator accumulator = new
TupleIntSketchAccumulator(_setOps, _nominalEntries, 2);
accumulator.apply(sketch);
- Assert.assertFalse(accumulator.isEmpty());
- Assert.assertEquals(accumulator.getResult().getEstimate(),
sketch.getEstimate());
+ assertFalse(accumulator.isEmpty());
+ assertEquals(accumulator.getResult().getEstimate(), sketch.getEstimate());
+ }
+
+ @Test
+ public void testSeededSketchIsReturnedUnchanged() {
+ for (int numEntries : new int[]{0, 1000}) {
+ IntegerTupleSketch input = new IntegerTupleSketch(_lgK,
IntegerSummary.Mode.Sum);
+ IntStream.range(0, numEntries).forEach(i -> input.update(i, 1));
+ CompactTupleSketch<IntegerSummary> sketch = input.compact();
+ for (int threshold = 1; threshold <= 3; threshold++) {
+ TupleIntSketchAccumulator accumulator = new
TupleIntSketchAccumulator(_setOps, 16, threshold, sketch);
+
+ // A received sketch stays intact until another input needs to be
merged, even at threshold 1.
+ assertFalse(accumulator.isEmpty());
+ assertEquals(accumulator.getNumInputs(), 1);
+ assertSame(accumulator.getResult(), sketch);
+ assertSame(accumulator.getResult(), sketch);
+ }
+ }
+ }
+
+ @Test
+ public void testMergeSeededSketches() {
+ IntegerTupleSketch input1 = new IntegerTupleSketch(_lgK,
IntegerSummary.Mode.Sum);
+ input1.update(1L, 2);
+ IntegerTupleSketch input2 = new IntegerTupleSketch(_lgK,
IntegerSummary.Mode.Sum);
+ input2.update(1L, 3);
+ input2.update(2L, 4);
+ for (int threshold = 1; threshold <= 3; threshold++) {
+ TupleIntSketchAccumulator accumulator =
+ new TupleIntSketchAccumulator(_setOps, _nominalEntries, threshold,
input1.compact());
+ TupleIntSketchAccumulator other =
+ new TupleIntSketchAccumulator(_setOps, _nominalEntries, threshold,
input2.compact());
+ accumulator.merge(other);
+
+ TupleSketch<IntegerSummary> result = accumulator.getResult();
+ assertEquals(accumulator.getNumInputs(), 2);
+ assertEquals(result.getEstimate(), 2.0);
+ assertEquals(summarySum(result), 9L);
+ assertEquals(summarySum(accumulator.getResult()), 9L);
+ }
+ }
+
+ @Test
+ public void testMergeAfterReadingSeededSketch() {
+ IntegerTupleSketch input1 = new IntegerTupleSketch(_lgK,
IntegerSummary.Mode.Sum);
+ IntegerTupleSketch input2 = new IntegerTupleSketch(_lgK,
IntegerSummary.Mode.Sum);
+ IntStream.range(0, 1000).forEach(i -> {
+ input1.update(i, 1);
+ input2.update(i, 2);
+ });
+ byte[] bytes = input1.compact().toByteArray();
+ CompactTupleSketch<IntegerSummary> sketch2 = input2.compact();
+ IntegerSummarySetOperations setOps =
+ new IntegerSummarySetOperations(IntegerSummary.Mode.Max,
IntegerSummary.Mode.Max);
+ for (int threshold = 1; threshold <= 3; threshold++) {
+ TupleSketch<IntegerSummary> sketch1 =
+ TupleSketch.heapifySketch(MemorySegment.ofArray(bytes), new
IntegerSummaryDeserializer());
+ TupleIntSketchAccumulator accumulator = new
TupleIntSketchAccumulator(setOps, 16, threshold, sketch1);
+ assertEquals(accumulator.getResult().toByteArray(), bytes);
+ accumulator.merge(new TupleIntSketchAccumulator(setOps, 16, threshold,
sketch2));
+
+ TupleUnion<IntegerSummary> expected = new TupleUnion<>(16, setOps);
+ expected.union(sketch1);
+ expected.union(sketch2);
+ TupleSketch<IntegerSummary> result = accumulator.getResult();
+ TupleSketch<IntegerSummary> expectedResult = expected.getResult();
+ assertEquals(result.getRetainedEntries(),
expectedResult.getRetainedEntries());
+ assertEquals(result.getTheta(), expectedResult.getTheta());
+ assertEquals(summarySum(result), summarySum(expectedResult));
+ }
}
@Test
@@ -77,7 +153,7 @@ public class TupleIntSketchAccumulatorTest {
accumulator2.apply(sketch2);
accumulator1.merge(accumulator2);
- Assert.assertEquals(accumulator1.getResult().getEstimate(),
sketch1.getEstimate() + sketch2.getEstimate());
+ assertEquals(accumulator1.getResult().getEstimate(), sketch1.getEstimate()
+ sketch2.getEstimate());
}
@Test
@@ -93,7 +169,7 @@ public class TupleIntSketchAccumulatorTest {
accumulator.apply(sketch1);
accumulator.apply(sketch2);
- Assert.assertEquals(accumulator.getResult().getEstimate(),
sketch1.getEstimate() + sketch2.getEstimate());
+ assertEquals(accumulator.getResult().getEstimate(), sketch1.getEstimate()
+ sketch2.getEstimate());
}
// Summaries are summed, so a sketch merged twice doubles a summary while
leaving the estimate right.
@@ -112,8 +188,8 @@ public class TupleIntSketchAccumulatorTest {
}
TupleSketch<IntegerSummary> actual = accumulator.getResult();
TupleSketch<IntegerSummary> want = expected.getResult();
- Assert.assertEquals(actual.getEstimate(), want.getEstimate(), 0.0,
"threshold " + threshold);
- Assert.assertEquals(summarySum(actual), summarySum(want), "threshold " +
threshold);
+ assertEquals(actual.getEstimate(), want.getEstimate(), 0.0, "threshold "
+ threshold);
+ assertEquals(summarySum(actual), summarySum(want), "threshold " +
threshold);
}
}
@@ -127,8 +203,8 @@ public class TupleIntSketchAccumulatorTest {
accumulator.apply(input.compact());
}
long first = summarySum(accumulator.getResult());
- Assert.assertEquals(summarySum(accumulator.getResult()), first);
- Assert.assertEquals(summarySum(accumulator.getResult()), first);
+ assertEquals(summarySum(accumulator.getResult()), first);
+ assertEquals(summarySum(accumulator.getResult()), first);
}
private static long summarySum(TupleSketch<IntegerSummary> sketch) {
@@ -147,7 +223,7 @@ public class TupleIntSketchAccumulatorTest {
accumulator.merge(emptyAccumulator);
- Assert.assertTrue(accumulator.isEmpty());
- Assert.assertEquals(accumulator.getResult().getEstimate(), 0.0);
+ assertTrue(accumulator.isEmpty());
+ assertEquals(accumulator.getResult().getEstimate(), 0.0);
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]