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 63cd8e006d1 Let segment directories serve mapped segment metadata 
(#20114)
63cd8e006d1 is described below

commit 63cd8e006d1e194ad072fc8a5fb1d7eae53d761d
Author: Xiang Fu <[email protected]>
AuthorDate: Mon Nov 2 13:27:10 2026 -0800

    Let segment directories serve mapped segment metadata (#20114)
    
    Mapped segment metadata (#19998) is only reachable for 
SegmentLocalFSDirectory:
    ImmutableSegmentLoader switches to it through an instanceof check, and the 
mapped
    SegmentMetadataImpl constructor is private behind loadMapped(), which always
    returns the base class. Another SegmentDirectory implementation, or one 
whose
    metadata subclasses SegmentMetadataImpl, has no way to opt in.
    
    - Add SegmentDirectory#loadMappedMetadata(), a no-op by default.
      ImmutableSegmentLoader calls it, when mapped metadata is enabled, on any
      directory after preprocessing and before the segment serves, so 
preprocessing
      keeps running on eager metadata for every implementation.
      SegmentLocalFSDirectory's existing method now overrides it.
    - Make the mapped SegmentMetadataImpl constructor protected. It behaves like
      loadMapped(): the caller owns and closes the mapping.
    - detachedCopy() keeps what getIndexDir() reports, so a subclass that hides 
its
      staging directory keeps hiding it in the copy.
---
 .../immutable/ImmutableSegmentLoader.java          |  6 +--
 .../segment/store/SegmentLocalFSDirectory.java     |  1 +
 .../segment/index/SegmentMetadataImplTest.java     | 60 ++++++++++++++++++++++
 .../spi/index/metadata/SegmentMetadataImpl.java    | 10 +++-
 .../pinot/segment/spi/store/SegmentDirectory.java  |  7 +++
 5 files changed, 78 insertions(+), 6 deletions(-)

diff --git 
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/indexsegment/immutable/ImmutableSegmentLoader.java
 
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/indexsegment/immutable/ImmutableSegmentLoader.java
index 40c4d47ea20..742d823e693 100644
--- 
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/indexsegment/immutable/ImmutableSegmentLoader.java
+++ 
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/indexsegment/immutable/ImmutableSegmentLoader.java
@@ -33,7 +33,6 @@ import 
org.apache.pinot.segment.local.segment.index.converter.SegmentFormatConve
 import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig;
 import org.apache.pinot.segment.local.segment.index.loader.SegmentPreProcessor;
 import 
org.apache.pinot.segment.local.segment.index.readers.text.MultiColumnLuceneTextIndexReader;
-import org.apache.pinot.segment.local.segment.store.SegmentLocalFSDirectory;
 import 
org.apache.pinot.segment.local.segment.virtualcolumn.VirtualColumnContext;
 import 
org.apache.pinot.segment.local.segment.virtualcolumn.VirtualColumnProvider;
 import 
org.apache.pinot.segment.local.segment.virtualcolumn.VirtualColumnProviderFactory;
@@ -213,9 +212,8 @@ public class ImmutableSegmentLoader {
       @Nullable Schema schema)
       throws Exception {
     // Preprocessing uses mutable metadata. Only the final immutable serving 
path uses the mapped representation.
-    if (indexLoadingConfig.isMappedSegmentMetadataEnabled()
-        && segmentDirectory instanceof SegmentLocalFSDirectory localDirectory) 
{
-      localDirectory.loadMappedMetadata();
+    if (indexLoadingConfig.isMappedSegmentMetadataEnabled()) {
+      segmentDirectory.loadMappedMetadata();
     }
     SegmentMetadataImpl segmentMetadata = 
segmentDirectory.getSegmentMetadata();
     if (segmentMetadata.getTotalDocs() == 0) {
diff --git 
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/store/SegmentLocalFSDirectory.java
 
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/store/SegmentLocalFSDirectory.java
index 864027eccc8..fd5ba12f2bd 100644
--- 
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/store/SegmentLocalFSDirectory.java
+++ 
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/store/SegmentLocalFSDirectory.java
@@ -166,6 +166,7 @@ public class SegmentLocalFSDirectory extends 
SegmentDirectory {
   }
 
   /// Replaces mutable metadata with the mapped representation after 
preprocessing and before the segment is queryable.
+  @Override
   public void loadMappedMetadata()
       throws IOException {
     if (_segmentMetadata.isMapped()) {
diff --git 
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/SegmentMetadataImplTest.java
 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/SegmentMetadataImplTest.java
index e8a3362b927..34a7c16983e 100644
--- 
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/SegmentMetadataImplTest.java
+++ 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/SegmentMetadataImplTest.java
@@ -65,6 +65,7 @@ import 
org.apache.pinot.segment.spi.index.metadata.SegmentMetadataImpl;
 import org.apache.pinot.segment.spi.memory.PinotBufferFactory;
 import org.apache.pinot.segment.spi.memory.PinotDataBuffer;
 import org.apache.pinot.segment.spi.store.ColumnIndexUtils;
+import org.apache.pinot.segment.spi.store.SegmentDirectory;
 import org.apache.pinot.segment.spi.store.SegmentDirectoryPaths;
 import org.apache.pinot.spi.config.instance.InstanceDataManagerConfig;
 import org.apache.pinot.spi.config.table.FieldConfig;
@@ -89,6 +90,8 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
@@ -495,6 +498,50 @@ public class SegmentMetadataImplTest {
     assertEquals(PinotDataBuffer.getMmapBufferCount(), mappings);
   }
 
+  @Test
+  public void testMappedMetadataSubclass()
+      throws Exception {
+    new SegmentV1V2ToV3FormatConverter().convert(_segmentDirectory);
+    SegmentMetadataImpl eager = new SegmentMetadataImpl(_segmentDirectory);
+    long mappings = PinotDataBuffer.getMmapBufferCount();
+    try (MetadataOnlySegmentMetadata mapped = new 
MetadataOnlySegmentMetadata(_segmentDirectory)) {
+      assertTrue(mapped.isMapped());
+      assertNull(mapped.getIndexDir());
+      assertEquals(mapped.getName(), eager.getName());
+      assertEquals(mapped.getTotalDocs(), eager.getTotalDocs());
+      assertEquals(mapped.getPhysicalColumnNames(), 
eager.getSchema().getPhysicalColumnNames());
+      for (String column : mapped.getPhysicalColumnNames()) {
+        ColumnMetadata actual = mapped.getColumnMetadataFor(column);
+        assertTrue(actual instanceof MappedColumnMetadata, column);
+        assertEquals(((MappedColumnMetadata) actual).getIndexSizeMap(),
+            ((ColumnMetadataImpl) 
eager.getColumnMetadataFor(column)).getIndexSizeMap(), column);
+      }
+      SegmentMetadataImpl detached = mapped.detachedCopy();
+      assertFalse(detached.isMapped());
+      assertFalse(detached instanceof MetadataOnlySegmentMetadata);
+      assertNull(detached.getIndexDir());
+      assertEquals(detached.getTotalDocs(), eager.getTotalDocs());
+    }
+    assertEquals(PinotDataBuffer.getMmapBufferCount(), mappings);
+  }
+
+  @Test
+  public void testServingLoadSwitchesAnySegmentDirectoryToMappedMetadata()
+      throws Exception {
+    InstanceDataManagerConfig instanceConfig = 
mock(InstanceDataManagerConfig.class);
+    PinotConfiguration config = new PinotConfiguration();
+    when(instanceConfig.getConfig()).thenReturn(config);
+    for (boolean enabled : new boolean[]{true, false}) {
+      config.setProperty(IndexLoadingConfig.MAPPED_SEGMENT_METADATA_ENABLED, 
enabled);
+      // Not a SegmentLocalFSDirectory: the switch is part of the 
SegmentDirectory contract.
+      SegmentDirectory segmentDirectory = mock(SegmentDirectory.class);
+      when(segmentDirectory.getSegmentMetadata()).thenReturn(
+          new SegmentMetadataImpl("testTable", "emptySegment", new Schema(), 
System.currentTimeMillis()));
+      ImmutableSegmentLoader.load(segmentDirectory, new 
IndexLoadingConfig(instanceConfig, null, null)).destroy();
+      verify(segmentDirectory, times(enabled ? 1 : 0)).loadMappedMetadata();
+    }
+  }
+
   @Test
   public void testMappedMetadataIgnoresStaleEagerSnapshot()
       throws Exception {
@@ -881,4 +928,17 @@ public class SegmentMetadataImplTest {
     driver.build();
     return driver.getOutputDirectory();
   }
+
+  /// Reports no index directory, as metadata staged apart from a segment's 
index files would.
+  private static final class MetadataOnlySegmentMetadata extends 
SegmentMetadataImpl {
+    MetadataOnlySegmentMetadata(File metadataDir)
+        throws IOException, ConfigurationException {
+      super(metadataDir, ReadMode.mmap, null);
+    }
+
+    @Override
+    public File getIndexDir() {
+      return null;
+    }
+  }
 }
diff --git 
a/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/metadata/SegmentMetadataImpl.java
 
b/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/metadata/SegmentMetadataImpl.java
index f781aebacd4..46391140d21 100644
--- 
a/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/metadata/SegmentMetadataImpl.java
+++ 
b/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/index/metadata/SegmentMetadataImpl.java
@@ -207,7 +207,8 @@ public class SegmentMetadataImpl implements 
SegmentMetadata, Closeable {
   }
 
   private SegmentMetadataImpl(SegmentMetadataImpl mapped) {
-    _indexDir = mapped._indexDir;
+    // A subclass may report a different index directory than the one its 
mapping reads.
+    _indexDir = mapped.getIndexDir();
     _columnMetadataMap = new TreeMap<>();
     _schema = new Schema();
     _segmentName = mapped._segmentName;
@@ -263,7 +264,12 @@ public class SegmentMetadataImpl implements 
SegmentMetadata, Closeable {
     return MappedSegmentMetadataStore.isTempFileName(fileName);
   }
 
-  private SegmentMetadataImpl(File indexDir, ReadMode readMode, @Nullable 
EagerSnapshot eagerSnapshot)
+  /// Mapped constructor for segment directory implementations that subclass 
[SegmentMetadataImpl] to carry their own
+  /// metadata, for example one that stages a segment's metadata files locally 
while its index files live elsewhere.
+  /// It reads `indexDir` exactly as [#loadMapped(File, ReadMode, 
EagerSnapshot)] does, so the caller owns the mapping
+  /// and must [#close()] it. A subclass may override [#getIndexDir()]; 
[#detachedCopy()] keeps what it reports, but
+  /// returns a plain [SegmentMetadataImpl].
+  protected SegmentMetadataImpl(File indexDir, ReadMode readMode, @Nullable 
EagerSnapshot eagerSnapshot)
       throws IOException, ConfigurationException {
     _indexDir = indexDir;
     _columnMetadataMap = null;
diff --git 
a/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/store/SegmentDirectory.java
 
b/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/store/SegmentDirectory.java
index 2cda5a5bea3..277699cc873 100644
--- 
a/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/store/SegmentDirectory.java
+++ 
b/pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/store/SegmentDirectory.java
@@ -101,6 +101,13 @@ public abstract class SegmentDirectory implements 
Closeable {
   public abstract void reloadMetadata()
       throws Exception;
 
+  /// Switches to mapped segment metadata, for a directory that supports it, 
after preprocessing and before the segment
+  /// is queryable. The serving load calls this only when mapped segment 
metadata is enabled. The default keeps the
+  /// current metadata.
+  public void loadMappedMetadata()
+      throws IOException {
+  }
+
   /// Get the path/URL for the directory
   public abstract Path getPath();
 


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

Reply via email to