This is an automated email from the ASF dual-hosted git repository.
Jackie-Jiang 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 88e4204cfe5 Fix resource leaks and cross-column cleanup in HNSW vector
index (#19175)
88e4204cfe5 is described below
commit 88e4204cfe5e5d2c80b457b79d71dbe4759e2e5e
Author: Xiaotian (Jackie) Jiang <[email protected]>
AuthorDate: Fri Aug 7 11:49:11 2026 -0700
Fix resource leaks and cross-column cleanup in HNSW vector index (#19175)
---
.../realtime/impl/vector/MutableVectorIndex.java | 55 ++++++++++++----------
.../impl/vector/HnswVectorIndexCreator.java | 40 ++++++++--------
.../impl/vector/MutableVectorIndexTest.java | 22 ++++++++-
3 files changed, 68 insertions(+), 49 deletions(-)
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/vector/MutableVectorIndex.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/vector/MutableVectorIndex.java
index 6f47afb24c4..27d77ad37cf 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/vector/MutableVectorIndex.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/vector/MutableVectorIndex.java
@@ -36,6 +36,7 @@ import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.KnnFloatVectorQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.util.IOUtils;
import
org.apache.pinot.segment.local.realtime.impl.invertedindex.RealtimeLuceneTextIndexSearcherPool;
import
org.apache.pinot.segment.local.segment.creator.impl.vector.XKnnFloatVectorField;
import
org.apache.pinot.segment.local.segment.index.readers.vector.LuceneHnswRuntimeControlUtils;
@@ -65,14 +66,13 @@ public class MutableVectorIndex implements
VectorIndexReader, MutableIndex, Vect
private final int _vectorDimension;
private final VectorIndexConfig _vectorIndexConfig;
private final VectorSimilarityFunction _vectorSimilarityFunction;
- private final IndexWriter _indexWriter;
private final String _vectorColumn;
private final String _segmentName;
private final long _commitIntervalMs;
private final long _commitDocs;
private final File _indexDir;
-
private final FSDirectory _indexDirectory;
+ private final IndexWriter _indexWriter;
private int _nextDocId;
private long _lastCommitTime;
@@ -90,40 +90,33 @@ public class MutableVectorIndex implements
VectorIndexReader, MutableIndex, Vect
_commitDocs = Long.parseLong(
vectorIndexConfig.getProperties().getOrDefault("commitDocs",
String.valueOf(DEFAULT_COMMIT_DOCS)));
_vectorSimilarityFunction =
VectorIndexUtils.toSimilarityFunction(vectorIndexConfig.getVectorDistanceFunction());
- // Use local variables so that resources opened before a failure can be
closed before rethrowing,
- // preventing file-descriptor and temp-directory leaks.
- File indexDir = new File(FileUtils.getTempDirectory(), segmentName);
+ // Each column of a segment gets its own directory, so that cleaning up
one column does not remove the index of
+ // another column of the same segment.
+ _indexDir = new File(new File(FileUtils.getTempDirectory(), segmentName),
+ _vectorColumn +
V1Constants.Indexes.VECTOR_V912_HNSW_INDEX_FILE_EXTENSION);
+
FSDirectory indexDirectory = null;
IndexWriter indexWriter = null;
try {
// segment generation is always in V1 and later we convert (as part of
post creation processing)
// to V3 if segmentVersion is set to V3 in SegmentGeneratorConfig.
- indexDirectory = FSDirectory.open(
- new File(indexDir, _vectorColumn +
V1Constants.Indexes.VECTOR_V912_HNSW_INDEX_FILE_EXTENSION).toPath());
+ indexDirectory = FSDirectory.open(_indexDir.toPath());
LOGGER.info("Creating mutable HNSW index for segment: {}, column: {} at
path: {} with {}", segmentName,
- vectorColumn, indexDir.getAbsolutePath(),
vectorIndexConfig.getProperties());
+ vectorColumn, _indexDir.getAbsolutePath(),
vectorIndexConfig.getProperties());
indexWriter = new IndexWriter(indexDirectory,
VectorIndexUtils.getIndexWriterConfig(vectorIndexConfig));
indexWriter.commit();
_lastCommitTime = System.currentTimeMillis();
} catch (Exception e) {
- if (indexWriter != null) {
- try {
- indexWriter.close();
- } catch (IOException closeEx) {
- e.addSuppressed(closeEx);
- }
- } else if (indexDirectory != null) {
- try {
- indexDirectory.close();
- } catch (IOException closeEx) {
- e.addSuppressed(closeEx);
- }
+ // IndexWriter does not close the Directory passed to it, so both need
to be closed.
+ try {
+ IOUtils.close(indexWriter, indexDirectory);
+ } catch (Exception closeEx) {
+ e.addSuppressed(closeEx);
}
- FileUtils.deleteQuietly(indexDir);
+ deleteIndexDir();
throw new RuntimeException(
"Caught exception while instantiating the LuceneTextIndexCreator for
column: " + vectorColumn, e);
}
- _indexDir = indexDir;
_indexDirectory = indexDirectory;
_indexWriter = indexWriter;
}
@@ -285,13 +278,23 @@ public class MutableVectorIndex implements
VectorIndexReader, MutableIndex, Vect
public void close() {
try {
_indexWriter.commit();
- _indexWriter.close();
- _indexDirectory.close();
+ // IndexWriter does not close the Directory passed to it, so both need
to be closed.
+ IOUtils.close(_indexWriter, _indexDirectory);
} catch (IOException e) {
+ // Both close() implementations are idempotent, so this is a no-op for
whatever was already closed above.
+ IOUtils.closeWhileHandlingException(_indexWriter, _indexDirectory);
throw new RuntimeException(e);
} finally {
- // Delete the temporary index directory.
- FileUtils.deleteQuietly(_indexDir);
+ deleteIndexDir();
}
}
+
+ /// Deletes the temporary index directory of this column, then the segment
directory holding it if this was the last
+ /// column with an index under it.
+ private void deleteIndexDir() {
+ FileUtils.deleteQuietly(_indexDir);
+ // Only succeeds when no other column of the same segment still has an
index directory under it.
+ //noinspection ResultOfMethodCallIgnored
+ _indexDir.getParentFile().delete();
+ }
}
diff --git
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/vector/HnswVectorIndexCreator.java
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/vector/HnswVectorIndexCreator.java
index 415e20602a1..9703ca7b5b9 100644
---
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/vector/HnswVectorIndexCreator.java
+++
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/vector/HnswVectorIndexCreator.java
@@ -28,6 +28,7 @@ import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.VectorSimilarityFunction;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.util.IOUtils;
import
org.apache.pinot.segment.local.segment.creator.impl.vector.lucene99.HnswVectorIndexCombined;
import org.apache.pinot.segment.local.segment.store.VectorIndexUtils;
import org.apache.pinot.segment.spi.V1Constants;
@@ -44,8 +45,6 @@ public class HnswVectorIndexCreator implements
VectorIndexCreator {
private static final Logger LOGGER =
LoggerFactory.getLogger(HnswVectorIndexCreator.class);
public static final String VECTOR_INDEX_DOC_ID_COLUMN_NAME = "DocID";
- private final Directory _indexDirectory;
- private final IndexWriter _indexWriter;
private final String _vectorColumn;
private final VectorSimilarityFunction _vectorSimilarityFunction;
private final int _vectorDimension;
@@ -53,6 +52,8 @@ public class HnswVectorIndexCreator implements
VectorIndexCreator {
private final boolean _storeInSegmentFile;
private final File _segmentIndexDir;
private final File _hnswIndexDir;
+ private final Directory _indexDirectory;
+ private final IndexWriter _indexWriter;
private int _nextDocId = 0;
@@ -62,27 +63,24 @@ public class HnswVectorIndexCreator implements
VectorIndexCreator {
_vectorSimilarityFunction =
VectorIndexUtils.toSimilarityFunction(vectorIndexConfig.getVectorDistanceFunction());
_storeInSegmentFile = vectorIndexConfig.isStoreInSegmentFile();
_segmentIndexDir = segmentIndexDir;
- // Use local variables so that, if IndexWriter construction fails after
FSDirectory is already
- // open, we can close the directory before rethrowing — preventing a
file-descriptor leak.
+ // Segment generation is always in V1 and later we convert (as part of
post creation processing) to V3 if
+ // segmentVersion is set to V3 in SegmentGeneratorConfig.
+ _hnswIndexDir =
+ new File(segmentIndexDir, _vectorColumn +
V1Constants.Indexes.VECTOR_V912_HNSW_INDEX_FILE_EXTENSION);
+
Directory indexDirectory = null;
- IndexWriter indexWriter;
+ IndexWriter indexWriter = null;
try {
- // segment generation is always in V1 and later we convert (as part of
post creation processing)
- // to V3 if segmentVersion is set to V3 in SegmentGeneratorConfig.
- File indexFile = new File(segmentIndexDir, _vectorColumn
- + V1Constants.Indexes.VECTOR_V912_HNSW_INDEX_FILE_EXTENSION);
- _hnswIndexDir = indexFile;
- indexDirectory = FSDirectory.open(indexFile.toPath());
+ indexDirectory = FSDirectory.open(_hnswIndexDir.toPath());
LOGGER.info("Creating HNSW index for column: {} at path: {} with {} for
segment: {}", column,
- indexFile.getAbsolutePath(), vectorIndexConfig.getProperties(),
segmentIndexDir.getAbsolutePath());
+ _hnswIndexDir.getAbsolutePath(), vectorIndexConfig.getProperties(),
segmentIndexDir.getAbsolutePath());
indexWriter = new IndexWriter(indexDirectory,
VectorIndexUtils.getIndexWriterConfig(vectorIndexConfig));
} catch (Exception e) {
- if (indexDirectory != null) {
- try {
- indexDirectory.close();
- } catch (IOException closeEx) {
- e.addSuppressed(closeEx);
- }
+ // IndexWriter does not close the Directory passed to it, so both need
to be closed.
+ try {
+ IOUtils.close(indexWriter, indexDirectory);
+ } catch (Exception closeEx) {
+ e.addSuppressed(closeEx);
}
throw new RuntimeException(
"Caught exception while instantiating the HnswVectorIndexCreator for
column: " + column, e);
@@ -129,9 +127,9 @@ public class HnswVectorIndexCreator implements
VectorIndexCreator {
public void close()
throws IOException {
try {
- // based on the commit flag set in IndexWriterConfig, this will decide
to commit or not
- _indexWriter.close();
- _indexDirectory.close();
+ // Based on the commit flag set in IndexWriterConfig, closing the writer
will decide to commit or not.
+ // IndexWriter does not close the Directory passed to it, so both need
to be closed.
+ IOUtils.close(_indexWriter, _indexDirectory);
} catch (Exception e) {
throw new RuntimeException("Caught exception while closing the HNSW
index for column: " + _vectorColumn, e);
}
diff --git
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/realtime/impl/vector/MutableVectorIndexTest.java
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/realtime/impl/vector/MutableVectorIndexTest.java
index 74eaae5b76a..16b18b8509d 100644
---
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/realtime/impl/vector/MutableVectorIndexTest.java
+++
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/realtime/impl/vector/MutableVectorIndexTest.java
@@ -29,6 +29,7 @@ import org.testng.annotations.Test;
public class MutableVectorIndexTest {
private static final String COLUMN_NAME = "embedding";
+ private static final String OTHER_COLUMN_NAME = "otherEmbedding";
@BeforeClass
public void setUpSearcherPool() {
@@ -85,9 +86,26 @@ public class MutableVectorIndexTest {
}
}
+ @Test
+ public void testCloseOnlyRemovesIndexOfClosedColumn() {
+ String segmentName = "mutableVectorIndexTest_" + System.nanoTime();
+ MutableVectorIndex index = createIndex(segmentName, COLUMN_NAME);
+ MutableVectorIndex otherIndex = createIndex(segmentName,
OTHER_COLUMN_NAME);
+ try {
+ index.close();
+ int[] matches = otherIndex.getDocIds(new float[]{5.0F, 42.0F, 54.33333F,
42.24F, 3413.4F}, 3).toArray();
+ Assert.assertEquals(matches.length, 3);
+ } finally {
+ otherIndex.close();
+ }
+ }
+
private static MutableVectorIndex createIndex() {
- MutableVectorIndex index =
- new MutableVectorIndex("mutableVectorIndexTest_" + System.nanoTime(),
COLUMN_NAME, createConfig());
+ return createIndex("mutableVectorIndexTest_" + System.nanoTime(),
COLUMN_NAME);
+ }
+
+ private static MutableVectorIndex createIndex(String segmentName, String
column) {
+ MutableVectorIndex index = new MutableVectorIndex(segmentName, column,
createConfig());
addVector(index, new float[]{5.0F, 42.0F, 54.33333F, 42.24F, 1001.045F},
0);
addVector(index, new float[]{42.0F, 23423.0F, 42431.32532F, 6785676.3242F,
42.3F}, 1);
addVector(index, new float[]{1.0F, 2.0F, 3.0F, 4.0F, 5.0F}, 2);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]