KKcorps commented on code in PR #19032:
URL: https://github.com/apache/pinot/pull/19032#discussion_r3680837764


##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/invertedindex/H3IndexHandler.java:
##########
@@ -215,7 +214,10 @@ private void 
handleDictionaryBasedColumn(SegmentDirectory.Writer segmentWriter,
       int numDocs = columnMetadata.getTotalDocs();
       for (int i = 0; i < numDocs; i++) {
         int dictId = forwardIndexReader.getDictId(i, readerContext);
-        
h3IndexCreator.add(GeometrySerializer.deserialize(dictionary.getBytesValue(dictId)));
+        // Route through add(value, dictId) so that empty/default geometry 
values (e.g. old segments reloaded after
+        // the geo column was added, which have no source data to build a 
Point from) are tolerated the same way the
+        // segment-creation path tolerates them, instead of failing the whole 
reload with a BufferUnderflowException.
+        h3IndexCreator.add(dictionary.getBytesValue(dictId), dictId);

Review Comment:
   **Critical — null rows can match negative H3 filters**
   
   This change lets the index skip an empty geometry and continue. The skipped 
row still keeps its document ID, but it has no H3 posting. When null handling 
is enabled, the same row is also marked as null in the null-value vector.
   
   The H3 filter operators do not read that null vector. 
`H3InclusionIndexFilterOperator` and `H3IndexFilterOperator` use 
`super(numDocs, false)` and build complements or match-all results across every 
document ID. Because the skipped null rows have no posting, they can be added 
to results for `ST_Contains/Within(...)=false`, lower-bound-only distance 
filters, or match-all distance ranges. That is a silent wrong result.
   
   Please limit complement and match-all results to non-null document IDs when 
query null handling is enabled. Please also add a query-level test that 
compares the H3-index path with the scan path for all-null rows.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/loader/invertedindex/H3IndexHandler.java:
##########
@@ -234,7 +236,8 @@ private void 
handleNonDictionaryBasedColumn(SegmentDirectory.Writer segmentWrite
         GeoSpatialIndexCreator h3IndexCreator = 
StandardIndexes.h3().createIndexCreator(context, config)) {
       int numDocs = columnMetadata.getTotalDocs();
       for (int i = 0; i < numDocs; i++) {
-        
h3IndexCreator.add(GeometrySerializer.deserialize(forwardIndexReader.getBytes(i,
 readerContext)));
+        // See handleDictionaryBasedColumn: add(value, dictId) tolerates 
empty/default geometry values on reload.
+        h3IndexCreator.add(forwardIndexReader.getBytes(i, readerContext), -1);

Review Comment:
   **Major — avoid one exception for every empty row**
   
   In the old-segment case described by this PR, every row can contain the same 
empty byte array. `add(value, dictId)` tries to deserialize each value, catches 
a `BufferUnderflowException`, and updates the indexing-error metric for each 
skipped row. A segment with millions of rows can therefore create millions of 
exceptions and metric updates, causing heavy CPU and garbage-collection work 
during reload. The dictionary loop has the same problem and may repeat this 
work for one dictionary ID.
   
   Please fast-path empty byte arrays before deserialization. For dictionary 
columns, cache the decoded or invalid result by dictionary ID. Keep advancing 
one document ID for each skipped row, while avoiding repeated exception and 
metric-key work. A large all-default test or benchmark would help protect this 
path.



##########
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/loader/SegmentPreProcessorTest.java:
##########
@@ -1665,6 +1671,39 @@ public void testV3CleanupH3AndTextIndices()
     assertEquals(singleFileIndex.length(), initFileSize);
   }
 
+  /// Regression test for the H3 index builder crashing a segment on 
empty/default geometry values.
+  ///
+  /// When a geo column is added to the schema after a segment was built, old 
segments have no source
+  /// data to derive it from, so the derived BYTES column is filled with its 
default null value -- the
+  /// empty byte array. Building an H3 index over those rows used to call
+  /// 
[org.apache.pinot.segment.local.utils.GeometrySerializer#deserialize(byte\[\])] 
directly on the
+  /// empty bytes, throwing a `BufferUnderflowException` that propagated out 
of the reload and parked
+  /// the segment in an ERROR state. The handler now routes through the 
creator's tolerant add path,
+  /// which skips undeserializable/default values when `continueOnError` is 
enabled (set by
+  /// [#resetIndexConfigs()]), exactly like the segment-creation path.
+  @Test(dataProvider = "bothV1AndV3")
+  public void testH3IndexCreationOnEmptyDefaultValue(SegmentVersion 
segmentVersion)
+      throws Exception {
+    buildSegment(segmentVersion);
+
+    // Add newH3Col as a derived column whose default null value is the empty 
byte array (no explicit
+    // defaultNullValue in the schema), mirroring old segments reloaded after 
a geo column was added.
+    runPreProcessor(_newColumnsSchemaWithH3EmptyDefault);
+    SegmentMetadataImpl segmentMetadata = new SegmentMetadataImpl(INDEX_DIR);
+    assertNotNull(segmentMetadata.getColumnMetadataFor("newH3Col"));
+
+    // Build the H3 index over the empty/default values. This must not throw 
and must produce the index.
+    _fieldConfigMap.put("newH3Col",
+        new FieldConfig("newH3Col", FieldConfig.EncodingType.DICTIONARY, 
List.of(FieldConfig.IndexType.H3), null,

Review Comment:
   **Minor — also test the raw, no-dictionary path**
   
   The production change updates both the dictionary and raw reload paths, but 
this test always uses `EncodingType.DICTIONARY`. This means the changed 
`forwardIndexReader.getBytes(...)` path is not covered.
   
   Please extend the test to cover `V1/V3 × DICTIONARY/RAW`. For the raw case, 
add `newH3Col` to `_noDictionaryColumns`, use `EncodingType.RAW`, assert that 
the column has no dictionary, and verify that the H3 index exists and can be 
opened.



-- 
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