This is an automated email from the ASF dual-hosted git repository.

xiangfu0 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 474efb1c1bf Return ImmutableRoaringBitmap from 
JsonIndexReader#getMatchingDocIds to avoid copying the JSON_MATCH posting 
(#18694)
474efb1c1bf is described below

commit 474efb1c1bf881b6cdbc27787e31a4942737ae85
Author: Xiang Fu <[email protected]>
AuthorDate: Sun Jun 7 13:07:40 2026 -0700

    Return ImmutableRoaringBitmap from JsonIndexReader#getMatchingDocIds to 
avoid copying the JSON_MATCH posting (#18694)
    
    JsonMatchFilterOperator only iterates the matching-docId bitmap 
(BitmapDocIdSet /
    BitmapCollection / getCardinality) within the segment's acquired lifetime 
and never
    mutates it, yet JsonIndexReader#getMatchingDocIds was contractually 
required to return a
    caller-owned MutableRoaringBitmap. A reader whose posting list is backed by 
read-only /
    memory-mapped storage therefore had to copy the posting on every JSON_MATCH 
call.
    
    Narrow the JsonIndexReader#getMatchingDocIds overloads to return 
ImmutableRoaringBitmap and
    document the read-only / borrowed-bitmap contract: the returned bitmap may 
be a read-only
    view backed by the index's underlying (possibly memory-mapped) storage, is 
valid only while
    the segment/index is held open, and must not be mutated. Callers that need 
to mutate must
    copy via toMutableRoaringBitmap().
    
    This brings JSON_MATCH in line with the inverted index 
(InvertedIndexReader#getDocIds already
    returns ImmutableRoaringBitmap). The stock readers 
(ImmutableJsonIndexReader,
    MutableJsonIndexImpl) keep returning a freshly-allocated 
MutableRoaringBitmap via covariant
    override, so behavior is unchanged; an mmap-backed reader may override to 
return the posting
    list directly and avoid the per-call copy.
    
    Backward-incompatible SPI change: the getMatchingDocIds return type changes 
from
    MutableRoaringBitmap to ImmutableRoaringBitmap. Downstream implementers are 
unaffected
    (covariant return); downstream callers that assigned the result to 
MutableRoaringBitmap or
    mutated it must adjust (copy via toMutableRoaringBitmap()).
    
    Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
---
 .../operator/filter/JsonMatchFilterOperator.java   |  3 +++
 .../segment/local/segment/index/JsonIndexTest.java | 10 ++++----
 .../segment/spi/index/reader/JsonIndexReader.java  | 28 +++++++++++++---------
 3 files changed, 25 insertions(+), 16 deletions(-)

diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/JsonMatchFilterOperator.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/JsonMatchFilterOperator.java
index ea6986556ec..01ff5435273 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/JsonMatchFilterOperator.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/JsonMatchFilterOperator.java
@@ -128,6 +128,9 @@ public class JsonMatchFilterOperator extends 
BaseFilterOperator {
   }
 
   private ImmutableRoaringBitmap getMatchingDocIdBitmap() {
+    // getMatchingDocIds may return a read-only bitmap backed by the index's 
(possibly memory-mapped) storage. This
+    // operator only iterates it (BitmapDocIdSet / BitmapCollection / 
getCardinality) within the segment's acquired
+    // lifetime and never mutates it, so a borrowed posting list can be 
consumed directly without a copy.
     if (_predicate != null) {
       return _jsonIndex.getMatchingDocIds(_predicate.getValue(), 
_predicate.getCountPredicate());
     } else {
diff --git 
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/JsonIndexTest.java
 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/JsonIndexTest.java
index b576a443942..ec2f0307de7 100644
--- 
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/JsonIndexTest.java
+++ 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/JsonIndexTest.java
@@ -45,7 +45,7 @@ import org.apache.pinot.spi.config.table.FieldConfig;
 import org.apache.pinot.spi.config.table.JsonIndexConfig;
 import org.apache.pinot.spi.utils.JsonUtils;
 import org.roaringbitmap.RoaringBitmap;
-import org.roaringbitmap.buffer.MutableRoaringBitmap;
+import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -269,7 +269,7 @@ public class JsonIndexTest implements 
PinotBuffersAfterMethodCheckRule {
   }
 
   private void assertDocIds(JsonIndexReader indexReader, String filter, int[] 
expected) {
-    MutableRoaringBitmap matchingDocIds = getMatchingDocIds(indexReader, 
filter);
+    ImmutableRoaringBitmap matchingDocIds = getMatchingDocIds(indexReader, 
filter);
     try {
       assertEquals(matchingDocIds.toArray(), expected);
     } catch (AssertionError ae) {
@@ -321,7 +321,7 @@ public class JsonIndexTest implements 
PinotBuffersAfterMethodCheckRule {
 
         assertDocIds(reader, "name = 'adam-100000' AND \"addresses[*].street\" 
= 'us-100001'", empty());
 
-        MutableRoaringBitmap matchingDocIds = getMatchingDocIds(reader, "name 
!= 'adam-100000'");
+        ImmutableRoaringBitmap matchingDocIds = getMatchingDocIds(reader, 
"name != 'adam-100000'");
         try {
           assertEquals(matchingDocIds.getCardinality(), 123_455);
         } catch (AssertionError ae) {
@@ -360,7 +360,7 @@ public class JsonIndexTest implements 
PinotBuffersAfterMethodCheckRule {
 
       JsonIndexReader[] indexReaders = new JsonIndexReader[]{onHeapReader, 
offHeapReader, mutableIndex};
       for (JsonIndexReader reader : indexReaders) {
-        MutableRoaringBitmap docIds = getMatchingDocIds(reader, 
"key1='value1'");
+        ImmutableRoaringBitmap docIds = getMatchingDocIds(reader, 
"key1='value1'");
         assertEquals(docIds.toArray(), ids(0));
 
         docIds = getMatchingDocIds(reader, "key2='longValue2'");
@@ -422,7 +422,7 @@ public class JsonIndexTest implements 
PinotBuffersAfterMethodCheckRule {
     }
   }
 
-  private MutableRoaringBitmap getMatchingDocIds(JsonIndexReader indexReader, 
String filter) {
+  private ImmutableRoaringBitmap getMatchingDocIds(JsonIndexReader 
indexReader, String filter) {
     return indexReader.getMatchingDocIds(filter);
   }
 
diff --git 
a/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/reader/JsonIndexReader.java
 
b/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/reader/JsonIndexReader.java
index 06a095b4e20..355c2e2e07e 100644
--- 
a/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/reader/JsonIndexReader.java
+++ 
b/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/reader/JsonIndexReader.java
@@ -23,7 +23,7 @@ import java.util.Set;
 import javax.annotation.Nullable;
 import org.apache.pinot.segment.spi.index.IndexReader;
 import org.roaringbitmap.RoaringBitmap;
-import org.roaringbitmap.buffer.MutableRoaringBitmap;
+import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
 
 
 /**
@@ -31,18 +31,24 @@ import org.roaringbitmap.buffer.MutableRoaringBitmap;
  */
 public interface JsonIndexReader extends IndexReader {
 
-  MutableRoaringBitmap getMatchingDocIds(String filterString);
+  /// Returns the matching document ids for the given filter string.
+  ///
+  /// The returned bitmap MUST be treated as read-only. It may be a borrowed 
view backed by the index's underlying
+  /// (possibly memory-mapped) storage, so it is valid only while the 
segment/index is held open and must not be
+  /// mutated. Callers that need to mutate the result should first copy it via
+  /// [ImmutableRoaringBitmap#toMutableRoaringBitmap()]. Stock readers still 
return a freshly-allocated bitmap; an
+  /// mmap-backed reader may return the posting list directly to avoid a 
per-call copy.
+  ImmutableRoaringBitmap getMatchingDocIds(String filterString);
 
-  /**
-   * Returns the matching document ids for the given filter Context.
-   */
-  MutableRoaringBitmap getMatchingDocIds(Object filterCtx);
+  /// Returns the matching document ids for the given filter context. See 
[#getMatchingDocIds(String)] for the
+  /// read-only borrowed-bitmap contract.
+  ImmutableRoaringBitmap getMatchingDocIds(Object filterCtx);
 
-  /**
-   * Returns the matching document ids for the given filter.
-   * @param flatDocCountFilter predicate used to filter results based on 
number of matched flattened document
-   */
-  default MutableRoaringBitmap getMatchingDocIds(String documentFilter, String 
flatDocCountFilter) {
+  /// Returns the matching document ids for the given filter. See 
[#getMatchingDocIds(String)] for the read-only
+  /// borrowed-bitmap contract.
+  ///
+  /// @param flatDocCountFilter predicate used to filter results based on 
number of matched flattened document
+  default ImmutableRoaringBitmap getMatchingDocIds(String documentFilter, 
String flatDocCountFilter) {
     return getMatchingDocIds(documentFilter);
   }
 


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

Reply via email to