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 b6c3ca686af Fix vector index load failure on non-local segment 
directories (storeInSegmentFile=true) (#18930)
b6c3ca686af is described below

commit b6c3ca686af50988a39e762151dcec3646466744
Author: RAGHVENDRA KUMAR YADAV <[email protected]>
AuthorDate: Tue Jul 7 15:04:32 2026 -0700

    Fix vector index load failure on non-local segment directories 
(storeInSegmentFile=true) (#18930)
---
 .../segment/index/vector/VectorIndexType.java      | 16 +++--
 .../segment/index/vector/VectorIndexTypeTest.java  | 78 ++++++++++++++++++++++
 2 files changed, 90 insertions(+), 4 deletions(-)

diff --git 
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/vector/VectorIndexType.java
 
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/vector/VectorIndexType.java
index eb27c0e63a2..206f25c7c73 100644
--- 
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/vector/VectorIndexType.java
+++ 
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/vector/VectorIndexType.java
@@ -222,9 +222,13 @@ public class VectorIndexType extends 
AbstractIndexType<VectorIndexConfig, Vector
         // (not a regular file), killing the load before any fallback could 
run. This keeps
         // storeInSegmentFile=true rolling-upgrade-safe for existing HNSW 
segments. The probed
         // buffer is owned by the segment directory — this reader must not 
close it.
+        // Probe only when the segment directory is a local directory: under a 
remote segment
+        // directory (e.g. tiered storage on S3) getPath() is not a local 
filesystem path, there
+        // can be no legacy sidecar on disk, and findFormatFile would reject 
the path outright.
         if (indexConfig.isStoreInSegmentFile()) {
-          File onDiskHnsw = 
SegmentDirectoryPaths.findVectorIndexIndexFile(segmentDir, column,
-              VectorBackendType.HNSW);
+          File onDiskHnsw = segmentDir.isDirectory()
+              ? SegmentDirectoryPaths.findVectorIndexIndexFile(segmentDir, 
column, VectorBackendType.HNSW)
+              : null;
           if (onDiskHnsw == null || !onDiskHnsw.isDirectory()) {
             PinotDataBuffer buffer;
             try {
@@ -271,8 +275,12 @@ public class VectorIndexType extends 
AbstractIndexType<VectorIndexConfig, Vector
         // Fall back to the on-disk artifact. For storeInSegmentFile=true this 
keeps the index usable
         // while a legacy sidecar still awaits absorption (mirrors the HNSW 
path above, which falls
         // back to its Lucene directory) instead of silently disabling the 
index and forcing an exact
-        // scan until migration completes.
-        File configuredIndexFile = 
SegmentDirectoryPaths.findVectorIndexIndexFile(segmentDir, column, indexConfig);
+        // scan until migration completes. Skip the probe when the segment 
directory is not a local
+        // directory (e.g. tiered storage on S3) — no sidecar can exist there 
and findFormatFile
+        // rejects non-local paths.
+        File configuredIndexFile = segmentDir.isDirectory()
+            ? SegmentDirectoryPaths.findVectorIndexIndexFile(segmentDir, 
column, indexConfig)
+            : null;
         if (configuredIndexFile == null || !configuredIndexFile.exists()) {
           LOGGER.warn("Skipping vector index reader for column: {} because 
backend {} has neither a consolidated "
               + "columns.psf entry nor a matching on-disk artifact in segment: 
{}", column, backendType, segmentDir);
diff --git 
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/vector/VectorIndexTypeTest.java
 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/vector/VectorIndexTypeTest.java
index b0e57f15f88..e913122f0eb 100644
--- 
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/vector/VectorIndexTypeTest.java
+++ 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/vector/VectorIndexTypeTest.java
@@ -19,16 +19,19 @@
 package org.apache.pinot.segment.local.segment.index.vector;
 
 import java.io.File;
+import java.nio.ByteOrder;
 import java.util.HashMap;
 import java.util.Map;
 import org.apache.commons.io.FileUtils;
 import 
org.apache.pinot.segment.local.segment.creator.impl.vector.HnswVectorIndexCreator;
+import 
org.apache.pinot.segment.local.segment.creator.impl.vector.lucene99.HnswVectorIndexCombined;
 import org.apache.pinot.segment.spi.ColumnMetadata;
 import org.apache.pinot.segment.spi.V1Constants;
 import org.apache.pinot.segment.spi.index.FieldIndexConfigs;
 import org.apache.pinot.segment.spi.index.StandardIndexes;
 import org.apache.pinot.segment.spi.index.creator.VectorIndexConfig;
 import org.apache.pinot.segment.spi.index.reader.VectorIndexReader;
+import org.apache.pinot.segment.spi.memory.PinotDataBuffer;
 import org.apache.pinot.segment.spi.store.SegmentDirectory;
 import org.apache.pinot.segment.spi.store.SegmentDirectoryPaths;
 import org.apache.pinot.spi.data.DimensionFieldSpec;
@@ -299,4 +302,79 @@ public class VectorIndexTypeTest {
       FileUtils.deleteQuietly(indexDir);
     }
   }
+
+  /**
+   * Regression: when the segment directory path is not a local filesystem 
directory (e.g. a
+   * {@code SegmentDirectory} backed by remote storage), the legacy-directory 
probe must be skipped
+   * — {@code SegmentDirectoryPaths.findFormatFile} rejects non-directory 
paths with
+   * {@code IllegalArgumentException}, which used to fail the whole segment 
load. The factory must
+   * go straight to the consolidated columns.psf entry and return a working 
reader.
+   */
+  @Test
+  public void 
testReaderFactoryLoadsConsolidatedHnswWhenSegmentDirectoryIsNotLocal()
+      throws Exception {
+    File buildDir = new File(FileUtils.getTempDirectory(), 
"vector-index-type-nonlocal-" + System.nanoTime());
+    FileUtils.deleteQuietly(buildDir);
+    PinotDataBuffer buffer = null;
+    try {
+      Assert.assertTrue(buildDir.mkdirs());
+      int dimension = 4;
+      int numDocs = 8;
+      // Build a legacy HNSW directory, then pack it into a combined buffer 
standing in for the
+      // consolidated columns.psf entry.
+      Map<String, String> creatorProps = new HashMap<>();
+      creatorProps.put("commit", "true");
+      creatorProps.put("useCompoundFile", "false");
+      VectorIndexConfig creatorConfig = new VectorIndexConfig(false, "HNSW", 
dimension, 1,
+          VectorIndexConfig.VectorDistanceFunction.EUCLIDEAN, creatorProps);
+      try (HnswVectorIndexCreator creator = new 
HnswVectorIndexCreator("embedding", buildDir, creatorConfig)) {
+        for (int i = 0; i < numDocs; i++) {
+          creator.add(new float[] {i, i + 1, i + 2, i + 3});
+        }
+        creator.seal();
+      }
+      File hnswDir = new File(buildDir, "embedding" + 
V1Constants.Indexes.VECTOR_V912_HNSW_INDEX_FILE_EXTENSION);
+      File combinedFile =
+          new File(buildDir, "embedding" + 
V1Constants.Indexes.VECTOR_HNSW_COMBINED_INDEX_FILE_EXTENSION);
+      HnswVectorIndexCombined.combineHnswIndexFiles(hnswDir, 
combinedFile.getAbsolutePath(), null, null);
+      // BIG_ENDIAN mirrors how columns.psf entries are mapped in production.
+      buffer = PinotDataBuffer.mapFile(combinedFile, /* readOnly */ true, 0, 
combinedFile.length(),
+          ByteOrder.BIG_ENDIAN, "vector-index-type-nonlocal-test");
+
+      SegmentDirectory segmentDirectory = Mockito.mock(SegmentDirectory.class);
+      SegmentDirectory.Reader segmentReader = 
Mockito.mock(SegmentDirectory.Reader.class);
+      // A path that exists nowhere on the local filesystem, as getPath() 
yields for remote-backed
+      // segment directories.
+      Mockito.when(segmentDirectory.getPath())
+          .thenReturn(new File("/segments/vectorTest/nonexistent-" + 
System.nanoTime()).toPath());
+      
Mockito.when(segmentReader.toSegmentDirectory()).thenReturn(segmentDirectory);
+      Mockito.when(segmentReader.getIndexFor("embedding", 
StandardIndexes.vector())).thenReturn(buffer);
+
+      ColumnMetadata metadata = Mockito.mock(ColumnMetadata.class);
+      Mockito.when(metadata.getColumnName()).thenReturn("embedding");
+      
Mockito.when(metadata.getDataType()).thenReturn(FieldSpec.DataType.FLOAT);
+      Mockito.when(metadata.getTotalDocs()).thenReturn(numDocs);
+      Mockito.when(metadata.getFieldSpec())
+          .thenReturn(new DimensionFieldSpec("embedding", 
FieldSpec.DataType.FLOAT, false));
+
+      Map<String, String> readerProps = new HashMap<>(creatorProps);
+      readerProps.put(VectorIndexConfig.STORE_IN_SEGMENT_FILE, "true");
+      VectorIndexConfig readerConfig = new VectorIndexConfig(false, "HNSW", 
dimension, 1,
+          VectorIndexConfig.VectorDistanceFunction.EUCLIDEAN, readerProps);
+      FieldIndexConfigs fieldIndexConfigs =
+          new FieldIndexConfigs.Builder().add(StandardIndexes.vector(), 
readerConfig).build();
+
+      VectorIndexReader reader = StandardIndexes.vector().getReaderFactory()
+          .createIndexReader(segmentReader, fieldIndexConfigs, metadata);
+      Assert.assertNotNull(reader,
+          "consolidated HNSW entry must load when the segment directory is not 
a local path");
+      // The buffer is owned by the segment directory; closing the reader must 
not close it.
+      reader.close();
+    } finally {
+      if (buffer != null) {
+        buffer.close();
+      }
+      FileUtils.deleteQuietly(buildDir);
+    }
+  }
 }


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

Reply via email to