xiangfu0 commented on code in PR #19795:
URL: https://github.com/apache/pinot/pull/19795#discussion_r4215688364


##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/TDigestUtils.java:
##########
@@ -207,331 +183,144 @@ public static TDigest deserialize(byte[] bytes) {
     return deserialize(ByteBuffer.wrap(bytes));
   }
 
-  /// Validates the structure and declared capacities of a serialized merging 
digest.
-  public static void validateSerialized(byte[] bytes) {
-    validateEncodedDigest(ByteBuffer.wrap(bytes));
+  /// Validates serialized lengths and centroid values without allocating 
centroid arrays.
+  public static double validateSerialized(byte[] bytes) {
+    return validateSerialized(ByteBuffer.wrap(bytes));
   }
 
-  /// Deserializes a finite-valued digest into a merging digest with the 
temporary-buffer footprint used by 3.2.
+  /// Validates one legacy digest without changing `input` and returns its 
total centroid weight.
   ///
-  /// Unlike the standard verbose decoder, this method does not retain the 
substantially larger default 3.3
-  /// temporary arrays. It also repairs legacy weighted boundary centroids 
before the first 3.3 merge.
-  public static TDigest deserializeFiniteWithLegacyBuffer(byte[] bytes) {
-    ByteBuffer input = ByteBuffer.wrap(bytes);
-    validateEncodedDigest(input.duplicate());
-
-    int encoding = input.getInt();
-    double min = input.getDouble();
-    double max = input.getDouble();
-    double compression;
-    int centroidCount;
-    boolean verbose;
-    if (encoding == VERBOSE_ENCODING) {
-      compression = input.getDouble();
-      centroidCount = input.getInt();
-      verbose = true;
-    } else if (encoding == SMALL_ENCODING) {
-      compression = input.getFloat();
-      input.getShort();
-      input.getShort();
-      centroidCount = input.getShort();
-      verbose = false;
-    } else {
-      throw new IllegalStateException("Invalid format for serialized 
histogram");
-    }
-
-    if (centroidCount == 0) {
-      return createMergingDigestWithLegacyBuffer(compression);
-    }
-    double[] means = new double[centroidCount + 2];
-    double[] weights = new double[centroidCount + 2];
-    for (int i = 0; i < centroidCount; i++) {
-      double weight = verbose ? input.getDouble() : input.getFloat();
-      double mean = verbose ? input.getDouble() : input.getFloat();
-      if (!(weight > 0.0) || !Double.isFinite(weight)) {
-        throw new IllegalArgumentException("Invalid TDigest centroid weight: " 
+ weight);
-      }
-      if (!Double.isFinite(mean)) {
-        throw new IllegalArgumentException("Expected a finite TDigest centroid 
mean: " + mean);
-      }
-      weights[i] = weight;
-      means[i] = mean;
-    }
-
-    int normalizedCount = weights[0] != 1.0 || weights[centroidCount - 1] != 
1.0
-        ? normalizeBoundaries(means, weights, centroidCount, min, max) : 
centroidCount;
-    long addCount = 0L;
-    for (int i = 0; i < normalizedCount; i++) {
-      double weight = weights[i];
-      if (weight != Math.rint(weight) || weight >= 0x1p63) {
-        return copyIntoLegacyBuffer(
-            deserialize(toVerboseBytes(min, max, compression, means, weights, 
normalizedCount)), normalizedCount);
-      }
-      long integralWeight = (long) weight;
-      addCount = Math.addExact(addCount, 1L + (integralWeight - 1L) / 
Integer.MAX_VALUE);
-    }
-
-    int minimumMainCapacity = Math.addExact(Math.toIntExact(addCount), 2);
-    TDigest digest = createMergingDigestWithLegacyBuffer(compression, 
minimumMainCapacity);
-    for (int i = 0; i < normalizedCount; i++) {
-      long remainingWeight = (long) weights[i];
-      while (remainingWeight > 0L) {
-        int weight = (int) Math.min(remainingWeight, Integer.MAX_VALUE);
-        digest.add(means[i], weight);
-        remainingWeight -= weight;
-      }
-    }
-    return digest;
+  /// Trailing bytes remain available to the caller for intermediate values 
containing several objects.
+  public static double validateSerialized(ByteBuffer input) {
+    return validateSerialized(input, true);
   }
 
-  /// Deserializes a digest and advances `input` past its encoded bytes.
-  public static TDigest deserialize(ByteBuffer input) {
-    ByteBuffer encoded = input.slice();
-    validateEncodedDigest(encoded.duplicate());
-
+  private static double validateSerialized(ByteBuffer input, boolean 
checkCapacity) {
+    ByteBuffer encoded = input.slice().order(ByteOrder.BIG_ENDIAN);
     int encoding = encoded.getInt();
+    if (encoding != VERBOSE_ENCODING && encoding != SMALL_ENCODING) {
+      throw new IllegalStateException("Invalid format for serialized 
histogram");
+    }
     double min = encoded.getDouble();
     double max = encoded.getDouble();
     double compression;
     int mainCapacity;
-    int bufferCapacity;
     int centroidCount;
     int centroidSize;
     if (encoding == VERBOSE_ENCODING) {
       compression = encoded.getDouble();
+      validateCompression(compression);
+      mainCapacity = Math.max(getDefaultCentroidCapacity(compression), 
getLegacyDefaultCentroidCapacity(compression));
       centroidCount = encoded.getInt();
-      mainCapacity = getDefaultCentroidCapacity(compression);
-      bufferCapacity = 5 * mainCapacity;
       centroidSize = VERBOSE_CENTROID_SIZE;
-    } else if (encoding == SMALL_ENCODING) {
+    } else {
       compression = encoded.getFloat();
+      validateCompression(compression);
       mainCapacity = encoded.getShort();
-      bufferCapacity = encoded.getShort();
-      centroidCount = encoded.getShort();
-      centroidSize = SMALL_CENTROID_SIZE;
-    } else {
-      // MergingDigest.fromBytes() has already produced the canonical 
exception for this case.
-      throw new IllegalStateException("Invalid format for serialized 
histogram");
-    }
-    int centroidOffset = encoded.position();
-    if (centroidCount == 0) {
-      return configureScaleFunction(MergingDigest.fromBytes(input));
-    }
-
-    double firstWeight = readWeight(encoded, centroidOffset, centroidSize);
-    double lastWeight = readWeight(encoded, centroidOffset + (centroidCount - 
1) * centroidSize, centroidSize);
-    int defaultMainCapacity = getDefaultCentroidCapacity(compression);
-    boolean needsBoundaryRepair = firstWeight != 1.0 || lastWeight != 1.0;
-    boolean exceedsDefaultVerboseCapacity = encoding == VERBOSE_ENCODING && 
centroidCount > defaultMainCapacity;
-    if (!needsBoundaryRepair && !exceedsDefaultVerboseCapacity) {
-      return configureScaleFunction(MergingDigest.fromBytes(input));
-    }
-
-    double[] means = new double[centroidCount + 2];
-    double[] weights = new double[centroidCount + 2];
-    encoded.position(centroidOffset);
-    for (int i = 0; i < centroidCount; i++) {
-      if (centroidSize == VERBOSE_CENTROID_SIZE) {
-        weights[i] = encoded.getDouble();
-        means[i] = encoded.getDouble();
-      } else {
-        weights[i] = encoded.getFloat();
-        means[i] = encoded.getFloat();
-      }
-    }
-    int encodedLength = encoded.position();
-    int normalizedCount = needsBoundaryRepair
-        ? normalizeBoundaries(means, weights, centroidCount, min, max) : 
centroidCount;
-    byte[] normalizedBytes = toVerboseBytes(min, max, compression, means, 
weights, normalizedCount);
-    MergingDigest result;
-    if (normalizedCount <= defaultMainCapacity) {
-      result = MergingDigest.fromBytes(ByteBuffer.wrap(normalizedBytes));
-    } else {
-      int resolvedMainCapacity = mainCapacity == -1 ? defaultMainCapacity : 
mainCapacity;
-      int resolvedBufferCapacity = bufferCapacity == -1
-          ? DEFAULT_MERGE_BUFFER_MULTIPLIER * resolvedMainCapacity : 
bufferCapacity;
-      int smallMainCapacity = Math.max(resolvedMainCapacity, normalizedCount);
-      long smallBufferCapacity = Math.max(Math.max((long) 
resolvedBufferCapacity, smallMainCapacity + 1L),
-          2L * smallMainCapacity + 1L);
-      if ((double) (float) compression == compression && normalizedCount <= 
Short.MAX_VALUE
-          && smallMainCapacity <= Short.MAX_VALUE && smallBufferCapacity <= 
Short.MAX_VALUE
-          && areFloatExact(means, weights, normalizedCount)) {
-        ByteBuffer normalized = ByteBuffer.allocate(SMALL_HEADER_SIZE + 
SMALL_CENTROID_SIZE * normalizedCount);
-        normalized.putInt(SMALL_ENCODING);
-        normalized.putDouble(min);
-        normalized.putDouble(max);
-        normalized.putFloat((float) compression);
-        normalized.putShort((short) smallMainCapacity);
-        normalized.putShort((short) smallBufferCapacity);
-        normalized.putShort((short) normalizedCount);
-        for (int i = 0; i < normalizedCount; i++) {
-          normalized.putFloat((float) weights[i]);
-          normalized.putFloat((float) means[i]);
-        }
-        normalized.flip();
-        result = MergingDigest.fromBytes(normalized);
-      } else {
-        result = MergingDigest.fromBytes(
-            ByteBuffer.wrap(reduceVerboseCentroids(normalizedBytes, 
defaultMainCapacity)));
-      }
-    }
-    input.position(Math.addExact(input.position(), encodedLength));
-    return configureScaleFunction(result);
-  }
-
-  private static TDigest configureScaleFunction(MergingDigest digest) {
-    digest.setScaleFunction(ScaleFunction.K_1);
-    return digest;
-  }
-
-  private static TDigest copyIntoLegacyBuffer(TDigest source, int 
centroidCount) {
-    TDigest digest = createMergingDigestWithLegacyBuffer(source.compression(), 
Math.addExact(centroidCount, 2));
-    digest.add(java.util.List.of(source));
-    return digest;
-  }
-
-  private static boolean areFloatExact(double[] means, double[] weights, int 
count) {
-    for (int i = 0; i < count; i++) {
-      if ((double) (float) weights[i] != weights[i] || (double) (float) 
means[i] != means[i]) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  private static byte[] toVerboseBytes(double min, double max, double 
compression, double[] means, double[] weights,
-      int count) {
-    ByteBuffer normalized = ByteBuffer.allocate(VERBOSE_HEADER_SIZE + 
VERBOSE_CENTROID_SIZE * count);
-    normalized.putInt(VERBOSE_ENCODING);
-    normalized.putDouble(min);
-    normalized.putDouble(max);
-    normalized.putDouble(compression);
-    normalized.putInt(count);
-    for (int i = 0; i < count; i++) {
-      normalized.putDouble(weights[i]);
-      normalized.putDouble(means[i]);
-    }
-    return normalized.array();
-  }
-
-  private static double readWeight(ByteBuffer encoded, int offset, int 
centroidSize) {
-    return centroidSize == VERBOSE_CENTROID_SIZE ? encoded.getDouble(offset) : 
encoded.getFloat(offset);
-  }
-
-  private static void validateEncodedDigest(ByteBuffer input) {
-    int encoding = input.getInt();
-    if (encoding != VERBOSE_ENCODING && encoding != SMALL_ENCODING) {
-      throw new IllegalStateException("Invalid format for serialized 
histogram");
-    }
-    input.getDouble();
-    input.getDouble();
-    double compression;
-    int mainCapacity;
-    int centroidCount;
-    int centroidSize;
-    if (encoding == VERBOSE_ENCODING) {
-      compression = input.getDouble();
-      mainCapacity = getDefaultCentroidCapacity(compression);
-      centroidCount = input.getInt();
-      centroidSize = VERBOSE_CENTROID_SIZE;
-    } else {
-      compression = input.getFloat();
-      mainCapacity = input.getShort();
-      int bufferCapacity = input.getShort();
+      int bufferCapacity = encoded.getShort();
       checkSmallArrayCapacity(mainCapacity);
       checkSmallArrayCapacity(bufferCapacity);
       if (mainCapacity == -1) {
         mainCapacity = getDefaultCentroidCapacity(compression);
       }
-      centroidCount = input.getShort();
+      centroidCount = encoded.getShort();
       centroidSize = SMALL_CENTROID_SIZE;
     }
     if (centroidCount < 0) {
       throw new IllegalArgumentException("Invalid negative TDigest centroid 
count: " + centroidCount);
     }
-    if (centroidCount > input.remaining() / centroidSize) {
+    if (centroidCount > encoded.remaining() / centroidSize) {
       throw new BufferUnderflowException();
     }
-    int readableMainCapacity = mainCapacity;
-    if (encoding == VERBOSE_ENCODING && compression > 0.0 && 
Double.isFinite(compression)) {
-      readableMainCapacity = Math.max(mainCapacity, 
getLegacyDefaultCentroidCapacity(compression));
+    if (checkCapacity && centroidCount > mainCapacity) {
+      throw new IllegalArgumentException("TDigest centroid count exceeds 
capacity: " + centroidCount);
     }
-    if (centroidCount > readableMainCapacity) {
-      throw new ArrayIndexOutOfBoundsException(
-          "Index " + readableMainCapacity + " out of bounds for length " + 
readableMainCapacity);
+    if (Double.isNaN(min) || Double.isNaN(max) || (centroidCount > 0 && min > 
max)) {
+      throw new IllegalArgumentException("Invalid TDigest extrema: " + min + 
", " + max);

Review Comment:
   Added extrema checks for every usable centroid, including the exact 
float-endpoint rounding allowance needed by compact bytes. Materialization 
restores those rounded endpoints to the double bounds before verbose output. 
Regression tests reject the inconsistent {min=5, mean=1} payload and cover 
compact read/add/write.
   
   Addressed in 
[1fc37ba77c](https://github.com/apache/pinot/commit/1fc37ba77c631c574df59a6e211bb978954fb2b2).
 All 463 targeted tests and 322 legacy reader cases pass.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/TDigestUtils.java:
##########
@@ -148,15 +125,14 @@ private static ByteBuffer prepareScratchBuffer(ByteBuffer 
scratchBuffer, int req
   /// otherwise reduces interior centroids without narrowing doubles.
   public static byte[] makeLegacyCompatible(byte[] verboseBytes) {
     ByteBuffer verbose = ByteBuffer.wrap(verboseBytes);
+    validateSerialized(verbose, false);

Review Comment:
   Mean interpolation now uses the normalized weight ratio and clamps the 
result to the input mean range. Tests cover the skewed 2^53-weight backward 
merge that inverted adjacent means, and finite means near 1e308 that previously 
overflowed the multiplication.
   
   Addressed in 
[1fc37ba77c](https://github.com/apache/pinot/commit/1fc37ba77c631c574df59a6e211bb978954fb2b2).
 All 463 targeted tests and 322 legacy reader cases pass.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/TDigestUtils.java:
##########
@@ -48,35 +45,34 @@ public final class TDigestUtils {
   private static final int SMALL_HEADER_SIZE = 30;
   private static final int SMALL_CENTROID_SIZE = 8;
   private static final int DEFAULT_MERGE_BUFFER_MULTIPLIER = 5;
+  private static final double MAX_COMPRESSION = 1_000_000.0;
 
   private TDigestUtils() {
   }
 
-  /// Creates a merging digest with Pinot's accuracy-preserving scale function.
+  /// Creates a digest using Pinot's accuracy-preserving K1 implementation.
   public static TDigest createMergingDigest(double compression) {
-    return configureScaleFunction(new MergingDigest(compression));
+    return PercentileTDigestAccumulator.forLegacyAggregation(compression);
   }
 
-  /// Creates a merging digest with the temporary-buffer footprint used by 
t-digest 3.2.
-  ///
-  /// Segment aggregation can keep many digests live at once. T-digest 3.3 
more than doubled the default temporary
-  /// buffer to support two-level compression, adding about 11 KiB to every 
compression-100 digest. This factory keeps
-  /// the 3.3 main-buffer capacity and Pinot's K1 policy, but restores the 
smaller temporary buffer for that
-  /// high-cardinality use case.
+  /// Creates Pinot's buffered digest for segment aggregation.
   public static TDigest createMergingDigestWithLegacyBuffer(double 
compression) {
     return createMergingDigestWithLegacyBuffer(compression, 0);
   }
 
-  /// Creates a reduced-buffer merging digest whose main array can hold at 
least `minimumMainCapacity` centroids.
+  /// Creates a digest whose dynamically grown arrays can hold the requested 
centroid capacity.
   public static TDigest createMergingDigestWithLegacyBuffer(double 
compression, int minimumMainCapacity) {
     if (minimumMainCapacity < 0) {
       throw new IllegalArgumentException("Minimum main capacity must not be 
negative: " + minimumMainCapacity);
     }
-    double normalizedCompression = Math.max(compression, 10.0);
-    int mainCapacity = Math.max(minimumMainCapacity, 
getDefaultCentroidCapacity(normalizedCompression));
-    int legacyBufferCapacity = Math.toIntExact(Math.multiplyExact(5L, (long) 
Math.ceil(normalizedCompression)));
-    int bufferCapacity = Math.max(legacyBufferCapacity, 
Math.addExact(mainCapacity, 1));
-    return configureScaleFunction(new MergingDigest(compression, 
bufferCapacity, mainCapacity));
+    return createMergingDigest(compression);
+  }
+
+  /// Rejects compression values that could allocate unbounded centroid state.
+  public static void validateCompression(double compression) {

Review Comment:
   Removed the arbitrary compression cap. Positive finite values remain 
accepted, including stored compression=1e7 and Double.MAX_VALUE in the 
regression tests. Capacity arithmetic saturates safely and buffers grow 
according to actual input counts.
   
   Addressed in 
[1fc37ba77c](https://github.com/apache/pinot/commit/1fc37ba77c631c574df59a6e211bb978954fb2b2).
 All 463 targeted tests and 322 legacy reader cases pass.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/TDigestUtils.java:
##########
@@ -207,331 +183,144 @@ public static TDigest deserialize(byte[] bytes) {
     return deserialize(ByteBuffer.wrap(bytes));
   }
 
-  /// Validates the structure and declared capacities of a serialized merging 
digest.
-  public static void validateSerialized(byte[] bytes) {
-    validateEncodedDigest(ByteBuffer.wrap(bytes));
+  /// Validates serialized lengths and centroid values without allocating 
centroid arrays.
+  public static double validateSerialized(byte[] bytes) {
+    return validateSerialized(ByteBuffer.wrap(bytes));
   }
 
-  /// Deserializes a finite-valued digest into a merging digest with the 
temporary-buffer footprint used by 3.2.
+  /// Validates one legacy digest without changing `input` and returns its 
total centroid weight.
   ///
-  /// Unlike the standard verbose decoder, this method does not retain the 
substantially larger default 3.3
-  /// temporary arrays. It also repairs legacy weighted boundary centroids 
before the first 3.3 merge.
-  public static TDigest deserializeFiniteWithLegacyBuffer(byte[] bytes) {
-    ByteBuffer input = ByteBuffer.wrap(bytes);
-    validateEncodedDigest(input.duplicate());
-
-    int encoding = input.getInt();
-    double min = input.getDouble();
-    double max = input.getDouble();
-    double compression;
-    int centroidCount;
-    boolean verbose;
-    if (encoding == VERBOSE_ENCODING) {
-      compression = input.getDouble();
-      centroidCount = input.getInt();
-      verbose = true;
-    } else if (encoding == SMALL_ENCODING) {
-      compression = input.getFloat();
-      input.getShort();
-      input.getShort();
-      centroidCount = input.getShort();
-      verbose = false;
-    } else {
-      throw new IllegalStateException("Invalid format for serialized 
histogram");
-    }
-
-    if (centroidCount == 0) {
-      return createMergingDigestWithLegacyBuffer(compression);
-    }
-    double[] means = new double[centroidCount + 2];
-    double[] weights = new double[centroidCount + 2];
-    for (int i = 0; i < centroidCount; i++) {
-      double weight = verbose ? input.getDouble() : input.getFloat();
-      double mean = verbose ? input.getDouble() : input.getFloat();
-      if (!(weight > 0.0) || !Double.isFinite(weight)) {
-        throw new IllegalArgumentException("Invalid TDigest centroid weight: " 
+ weight);
-      }
-      if (!Double.isFinite(mean)) {
-        throw new IllegalArgumentException("Expected a finite TDigest centroid 
mean: " + mean);
-      }
-      weights[i] = weight;
-      means[i] = mean;
-    }
-
-    int normalizedCount = weights[0] != 1.0 || weights[centroidCount - 1] != 
1.0
-        ? normalizeBoundaries(means, weights, centroidCount, min, max) : 
centroidCount;
-    long addCount = 0L;
-    for (int i = 0; i < normalizedCount; i++) {
-      double weight = weights[i];
-      if (weight != Math.rint(weight) || weight >= 0x1p63) {
-        return copyIntoLegacyBuffer(
-            deserialize(toVerboseBytes(min, max, compression, means, weights, 
normalizedCount)), normalizedCount);
-      }
-      long integralWeight = (long) weight;
-      addCount = Math.addExact(addCount, 1L + (integralWeight - 1L) / 
Integer.MAX_VALUE);
-    }
-
-    int minimumMainCapacity = Math.addExact(Math.toIntExact(addCount), 2);
-    TDigest digest = createMergingDigestWithLegacyBuffer(compression, 
minimumMainCapacity);
-    for (int i = 0; i < normalizedCount; i++) {
-      long remainingWeight = (long) weights[i];
-      while (remainingWeight > 0L) {
-        int weight = (int) Math.min(remainingWeight, Integer.MAX_VALUE);
-        digest.add(means[i], weight);
-        remainingWeight -= weight;
-      }
-    }
-    return digest;
+  /// Trailing bytes remain available to the caller for intermediate values 
containing several objects.
+  public static double validateSerialized(ByteBuffer input) {
+    return validateSerialized(input, true);
   }
 
-  /// Deserializes a digest and advances `input` past its encoded bytes.
-  public static TDigest deserialize(ByteBuffer input) {
-    ByteBuffer encoded = input.slice();
-    validateEncodedDigest(encoded.duplicate());
-
+  private static double validateSerialized(ByteBuffer input, boolean 
checkCapacity) {
+    ByteBuffer encoded = input.slice().order(ByteOrder.BIG_ENDIAN);
     int encoding = encoded.getInt();
+    if (encoding != VERBOSE_ENCODING && encoding != SMALL_ENCODING) {
+      throw new IllegalStateException("Invalid format for serialized 
histogram");
+    }
     double min = encoded.getDouble();
     double max = encoded.getDouble();
     double compression;
     int mainCapacity;
-    int bufferCapacity;
     int centroidCount;
     int centroidSize;
     if (encoding == VERBOSE_ENCODING) {
       compression = encoded.getDouble();
+      validateCompression(compression);
+      mainCapacity = Math.max(getDefaultCentroidCapacity(compression), 
getLegacyDefaultCentroidCapacity(compression));
       centroidCount = encoded.getInt();
-      mainCapacity = getDefaultCentroidCapacity(compression);
-      bufferCapacity = 5 * mainCapacity;
       centroidSize = VERBOSE_CENTROID_SIZE;
-    } else if (encoding == SMALL_ENCODING) {
+    } else {
       compression = encoded.getFloat();
+      validateCompression(compression);
       mainCapacity = encoded.getShort();
-      bufferCapacity = encoded.getShort();
-      centroidCount = encoded.getShort();
-      centroidSize = SMALL_CENTROID_SIZE;
-    } else {
-      // MergingDigest.fromBytes() has already produced the canonical 
exception for this case.
-      throw new IllegalStateException("Invalid format for serialized 
histogram");
-    }
-    int centroidOffset = encoded.position();
-    if (centroidCount == 0) {
-      return configureScaleFunction(MergingDigest.fromBytes(input));
-    }
-
-    double firstWeight = readWeight(encoded, centroidOffset, centroidSize);
-    double lastWeight = readWeight(encoded, centroidOffset + (centroidCount - 
1) * centroidSize, centroidSize);
-    int defaultMainCapacity = getDefaultCentroidCapacity(compression);
-    boolean needsBoundaryRepair = firstWeight != 1.0 || lastWeight != 1.0;
-    boolean exceedsDefaultVerboseCapacity = encoding == VERBOSE_ENCODING && 
centroidCount > defaultMainCapacity;
-    if (!needsBoundaryRepair && !exceedsDefaultVerboseCapacity) {
-      return configureScaleFunction(MergingDigest.fromBytes(input));
-    }
-
-    double[] means = new double[centroidCount + 2];
-    double[] weights = new double[centroidCount + 2];
-    encoded.position(centroidOffset);
-    for (int i = 0; i < centroidCount; i++) {
-      if (centroidSize == VERBOSE_CENTROID_SIZE) {
-        weights[i] = encoded.getDouble();
-        means[i] = encoded.getDouble();
-      } else {
-        weights[i] = encoded.getFloat();
-        means[i] = encoded.getFloat();
-      }
-    }
-    int encodedLength = encoded.position();
-    int normalizedCount = needsBoundaryRepair
-        ? normalizeBoundaries(means, weights, centroidCount, min, max) : 
centroidCount;
-    byte[] normalizedBytes = toVerboseBytes(min, max, compression, means, 
weights, normalizedCount);
-    MergingDigest result;
-    if (normalizedCount <= defaultMainCapacity) {
-      result = MergingDigest.fromBytes(ByteBuffer.wrap(normalizedBytes));
-    } else {
-      int resolvedMainCapacity = mainCapacity == -1 ? defaultMainCapacity : 
mainCapacity;
-      int resolvedBufferCapacity = bufferCapacity == -1
-          ? DEFAULT_MERGE_BUFFER_MULTIPLIER * resolvedMainCapacity : 
bufferCapacity;
-      int smallMainCapacity = Math.max(resolvedMainCapacity, normalizedCount);
-      long smallBufferCapacity = Math.max(Math.max((long) 
resolvedBufferCapacity, smallMainCapacity + 1L),
-          2L * smallMainCapacity + 1L);
-      if ((double) (float) compression == compression && normalizedCount <= 
Short.MAX_VALUE
-          && smallMainCapacity <= Short.MAX_VALUE && smallBufferCapacity <= 
Short.MAX_VALUE
-          && areFloatExact(means, weights, normalizedCount)) {
-        ByteBuffer normalized = ByteBuffer.allocate(SMALL_HEADER_SIZE + 
SMALL_CENTROID_SIZE * normalizedCount);
-        normalized.putInt(SMALL_ENCODING);
-        normalized.putDouble(min);
-        normalized.putDouble(max);
-        normalized.putFloat((float) compression);
-        normalized.putShort((short) smallMainCapacity);
-        normalized.putShort((short) smallBufferCapacity);
-        normalized.putShort((short) normalizedCount);
-        for (int i = 0; i < normalizedCount; i++) {
-          normalized.putFloat((float) weights[i]);
-          normalized.putFloat((float) means[i]);
-        }
-        normalized.flip();
-        result = MergingDigest.fromBytes(normalized);
-      } else {
-        result = MergingDigest.fromBytes(
-            ByteBuffer.wrap(reduceVerboseCentroids(normalizedBytes, 
defaultMainCapacity)));
-      }
-    }
-    input.position(Math.addExact(input.position(), encodedLength));
-    return configureScaleFunction(result);
-  }
-
-  private static TDigest configureScaleFunction(MergingDigest digest) {
-    digest.setScaleFunction(ScaleFunction.K_1);
-    return digest;
-  }
-
-  private static TDigest copyIntoLegacyBuffer(TDigest source, int 
centroidCount) {
-    TDigest digest = createMergingDigestWithLegacyBuffer(source.compression(), 
Math.addExact(centroidCount, 2));
-    digest.add(java.util.List.of(source));
-    return digest;
-  }
-
-  private static boolean areFloatExact(double[] means, double[] weights, int 
count) {
-    for (int i = 0; i < count; i++) {
-      if ((double) (float) weights[i] != weights[i] || (double) (float) 
means[i] != means[i]) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  private static byte[] toVerboseBytes(double min, double max, double 
compression, double[] means, double[] weights,
-      int count) {
-    ByteBuffer normalized = ByteBuffer.allocate(VERBOSE_HEADER_SIZE + 
VERBOSE_CENTROID_SIZE * count);
-    normalized.putInt(VERBOSE_ENCODING);
-    normalized.putDouble(min);
-    normalized.putDouble(max);
-    normalized.putDouble(compression);
-    normalized.putInt(count);
-    for (int i = 0; i < count; i++) {
-      normalized.putDouble(weights[i]);
-      normalized.putDouble(means[i]);
-    }
-    return normalized.array();
-  }
-
-  private static double readWeight(ByteBuffer encoded, int offset, int 
centroidSize) {
-    return centroidSize == VERBOSE_CENTROID_SIZE ? encoded.getDouble(offset) : 
encoded.getFloat(offset);
-  }
-
-  private static void validateEncodedDigest(ByteBuffer input) {
-    int encoding = input.getInt();
-    if (encoding != VERBOSE_ENCODING && encoding != SMALL_ENCODING) {
-      throw new IllegalStateException("Invalid format for serialized 
histogram");
-    }
-    input.getDouble();
-    input.getDouble();
-    double compression;
-    int mainCapacity;
-    int centroidCount;
-    int centroidSize;
-    if (encoding == VERBOSE_ENCODING) {
-      compression = input.getDouble();
-      mainCapacity = getDefaultCentroidCapacity(compression);
-      centroidCount = input.getInt();
-      centroidSize = VERBOSE_CENTROID_SIZE;
-    } else {
-      compression = input.getFloat();
-      mainCapacity = input.getShort();
-      int bufferCapacity = input.getShort();
+      int bufferCapacity = encoded.getShort();
       checkSmallArrayCapacity(mainCapacity);
       checkSmallArrayCapacity(bufferCapacity);
       if (mainCapacity == -1) {
         mainCapacity = getDefaultCentroidCapacity(compression);
       }
-      centroidCount = input.getShort();
+      centroidCount = encoded.getShort();
       centroidSize = SMALL_CENTROID_SIZE;
     }
     if (centroidCount < 0) {
       throw new IllegalArgumentException("Invalid negative TDigest centroid 
count: " + centroidCount);
     }
-    if (centroidCount > input.remaining() / centroidSize) {
+    if (centroidCount > encoded.remaining() / centroidSize) {
       throw new BufferUnderflowException();
     }
-    int readableMainCapacity = mainCapacity;
-    if (encoding == VERBOSE_ENCODING && compression > 0.0 && 
Double.isFinite(compression)) {
-      readableMainCapacity = Math.max(mainCapacity, 
getLegacyDefaultCentroidCapacity(compression));
+    if (checkCapacity && centroidCount > mainCapacity) {
+      throw new IllegalArgumentException("TDigest centroid count exceeds 
capacity: " + centroidCount);
     }
-    if (centroidCount > readableMainCapacity) {
-      throw new ArrayIndexOutOfBoundsException(
-          "Index " + readableMainCapacity + " out of bounds for length " + 
readableMainCapacity);
+    if (Double.isNaN(min) || Double.isNaN(max) || (centroidCount > 0 && min > 
max)) {
+      throw new IllegalArgumentException("Invalid TDigest extrema: " + min + 
", " + max);
     }
-  }
-
-  private static void checkSmallArrayCapacity(int capacity) {
-    if (capacity < -1) {
-      throw new NegativeArraySizeException(Integer.toString(capacity));
-    }
-  }
-
-  private static int normalizeBoundaries(double[] means, double[] weights, int 
count, double min, double max) {
-    if (count == 1) {
-      double weight = weights[0];
-      if (weight <= 1.0) {
-        return count;
+    double totalWeight = 0.0;
+    double previousMean = Double.NEGATIVE_INFINITY;
+    for (int i = 0; i < centroidCount; i++) {
+      double weight = centroidSize == VERBOSE_CENTROID_SIZE ? 
encoded.getDouble() : encoded.getFloat();
+      double mean = centroidSize == VERBOSE_CENTROID_SIZE ? 
encoded.getDouble() : encoded.getFloat();
+      if (!(weight > 0.0) || !Double.isFinite(weight) || Double.isNaN(mean) || 
mean < previousMean) {

Review Comment:
   Historical payloads with poisoned numerical state remain byte-readable and 
retain their bytes; unknown statistics return NaN and never enter the sorted K1 
kernel. Finite unordered means are sorted without dropping weights. Benchmark 
buckets use bounded convex means; regressions cover duplicate 0.1, extreme 
values, old negative repair residuals, zero weights, and unordered finite 
centroids with infinite tails.
   
   Addressed in 
[1fc37ba77c](https://github.com/apache/pinot/commit/1fc37ba77c631c574df59a6e211bb978954fb2b2).
 All 463 targeted tests and 322 legacy reader cases pass.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/customobject/PercentileTDigestAccumulator.java:
##########
@@ -1144,7 +1113,7 @@ private void normalizeBoundaryCentroids() {
 
     if (_numCentroids == 1) {
       double weight = _centroidWeights[0];
-      if (weight <= 1.0) {
+      if (weight < 2.0) {

Review Comment:
   Removed repeated repair attempts for fractional singleton mass below two and 
preserve its exact bytes and mass. Unit endpoints are impossible for these 
inputs without changing their mass. The class documentation and PR migration 
note explicitly describe the existing old-3.3 recompression/assertion 
limitation; ordinary fractional replay remains precise.
   
   Addressed in 
[1fc37ba77c](https://github.com/apache/pinot/commit/1fc37ba77c631c574df59a6e211bb978954fb2b2).
 All 463 targeted tests and 322 legacy reader cases pass.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to