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]