diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 26729df99791..ad733266f16e 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -178,6 +178,9 @@ Optimizations Bug Fixes --------------------- +* GITHUB#16367: HNSW graph construction now periodically checks whether the surrounding merge has + been aborted, so IndexWriter#rollback and abortMerges no longer block until the entire graph is + built. (Jeho Jeong) * GITHUB#14049: Randomize KNN codec params in RandomCodec. Fixes scalar quantization div-by-zero when all values are identical. (Mike Sokolov) diff --git a/lucene/core/src/java/org/apache/lucene/codecs/lucene99/Lucene99HnswVectorsWriter.java b/lucene/core/src/java/org/apache/lucene/codecs/lucene99/Lucene99HnswVectorsWriter.java index 1e330fee21b9..0b0bf5bda506 100644 --- a/lucene/core/src/java/org/apache/lucene/codecs/lucene99/Lucene99HnswVectorsWriter.java +++ b/lucene/core/src/java/org/apache/lucene/codecs/lucene99/Lucene99HnswVectorsWriter.java @@ -473,7 +473,8 @@ private void buildAndWriteGraph( mergeState.intraMergeTaskExecutor == null ? null : new TaskExecutor(mergeState.intraMergeTaskExecutor), - numMergeWorkers); + numMergeWorkers, + mergeState::checkAborted); for (int i = 0; i < mergeState.liveDocs.length; i++) { if (hasVectorValues(mergeState.fieldInfos[i], fieldInfo.name)) { merger.addReader( @@ -626,10 +627,11 @@ private HnswGraphMerger createGraphMerger( FieldInfo fieldInfo, RandomVectorScorerSupplier scorerSupplier, TaskExecutor parallelMergeTaskExecutor, - int numParallelMergeWorkers) { + int numParallelMergeWorkers, + IORunnable abortCheck) { if (mergeExec != null) { return new ConcurrentHnswMerger( - fieldInfo, scorerSupplier, M, beamWidth, mergeExec, numMergeWorkers); + fieldInfo, scorerSupplier, M, beamWidth, mergeExec, numMergeWorkers, abortCheck); } if (parallelMergeTaskExecutor != null && numParallelMergeWorkers > 1) { return new ConcurrentHnswMerger( @@ -638,9 +640,10 @@ private HnswGraphMerger createGraphMerger( M, beamWidth, parallelMergeTaskExecutor, - numParallelMergeWorkers); + numParallelMergeWorkers, + abortCheck); } - return new IncrementalHnswGraphMerger(fieldInfo, scorerSupplier, M, beamWidth); + return new IncrementalHnswGraphMerger(fieldInfo, scorerSupplier, M, beamWidth, abortCheck); } @Override diff --git a/lucene/core/src/java/org/apache/lucene/util/hnsw/ConcurrentHnswMerger.java b/lucene/core/src/java/org/apache/lucene/util/hnsw/ConcurrentHnswMerger.java index 4b6244c18522..0803c1389ed2 100644 --- a/lucene/core/src/java/org/apache/lucene/util/hnsw/ConcurrentHnswMerger.java +++ b/lucene/core/src/java/org/apache/lucene/util/hnsw/ConcurrentHnswMerger.java @@ -29,6 +29,7 @@ import org.apache.lucene.search.TaskExecutor; import org.apache.lucene.util.BitSet; import org.apache.lucene.util.FixedBitSet; +import org.apache.lucene.util.IORunnable; /** This merger merges graph in a concurrent manner, by using {@link HnswConcurrentMergeBuilder} */ public class ConcurrentHnswMerger extends IncrementalHnswGraphMerger { @@ -46,7 +47,24 @@ public ConcurrentHnswMerger( int beamWidth, TaskExecutor taskExecutor, int numWorker) { - super(fieldInfo, scorerSupplier, M, beamWidth); + this(fieldInfo, scorerSupplier, M, beamWidth, taskExecutor, numWorker, null); + } + + /** + * @param fieldInfo FieldInfo for the field being merged + * @param abortCheck optional check invoked before every node insertion during graph construction; + * may throw {@link org.apache.lucene.index.MergePolicy.MergeAbortedException} to abort the + * build when the surrounding merge has been aborted, or null + */ + public ConcurrentHnswMerger( + FieldInfo fieldInfo, + RandomVectorScorerSupplier scorerSupplier, + int M, + int beamWidth, + TaskExecutor taskExecutor, + int numWorker, + IORunnable abortCheck) { + super(fieldInfo, scorerSupplier, M, beamWidth, abortCheck); this.taskExecutor = taskExecutor; this.numWorker = numWorker; } diff --git a/lucene/core/src/java/org/apache/lucene/util/hnsw/HnswBuilder.java b/lucene/core/src/java/org/apache/lucene/util/hnsw/HnswBuilder.java index 38109c9c95e2..43cef2cde8ee 100644 --- a/lucene/core/src/java/org/apache/lucene/util/hnsw/HnswBuilder.java +++ b/lucene/core/src/java/org/apache/lucene/util/hnsw/HnswBuilder.java @@ -19,6 +19,7 @@ import java.io.IOException; import org.apache.lucene.internal.hppc.IntHashSet; +import org.apache.lucene.util.IORunnable; import org.apache.lucene.util.InfoStream; /** @@ -46,6 +47,18 @@ public interface HnswBuilder { /** Set info-stream to output debugging information */ void setInfoStream(InfoStream infoStream); + /** + * Sets a check that is invoked before every node insertion during graph construction. The check + * may throw an exception to abort the build promptly, e.g. {@link + * org.apache.lucene.index.MergePolicy.MergeAbortedException} when the merge that triggered the + * build has been aborted. + * + *
The check must be non-null and can be set at most once.
+ *
+ * @throws IllegalStateException if the check was already set
+ */
+ void setAbortCheck(IORunnable abortCheck);
+
OnHeapHnswGraph getGraph();
/**
diff --git a/lucene/core/src/java/org/apache/lucene/util/hnsw/HnswConcurrentMergeBuilder.java b/lucene/core/src/java/org/apache/lucene/util/hnsw/HnswConcurrentMergeBuilder.java
index f1a4666fd7e6..26fb7e420998 100644
--- a/lucene/core/src/java/org/apache/lucene/util/hnsw/HnswConcurrentMergeBuilder.java
+++ b/lucene/core/src/java/org/apache/lucene/util/hnsw/HnswConcurrentMergeBuilder.java
@@ -31,6 +31,7 @@
import org.apache.lucene.search.TaskExecutor;
import org.apache.lucene.util.BitSet;
import org.apache.lucene.util.FixedBitSet;
+import org.apache.lucene.util.IORunnable;
import org.apache.lucene.util.InfoStream;
/**
@@ -134,6 +135,13 @@ public void setInfoStream(InfoStream infoStream) {
}
}
+ @Override
+ public void setAbortCheck(IORunnable abortCheck) {
+ for (HnswBuilder worker : workers) {
+ worker.setAbortCheck(abortCheck);
+ }
+ }
+
@Override
public OnHeapHnswGraph getCompletedGraph() throws IOException {
if (frozen == false) {
diff --git a/lucene/core/src/java/org/apache/lucene/util/hnsw/HnswGraphBuilder.java b/lucene/core/src/java/org/apache/lucene/util/hnsw/HnswGraphBuilder.java
index e42b69ddb278..dbd92163f8d6 100644
--- a/lucene/core/src/java/org/apache/lucene/util/hnsw/HnswGraphBuilder.java
+++ b/lucene/core/src/java/org/apache/lucene/util/hnsw/HnswGraphBuilder.java
@@ -33,6 +33,7 @@
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.knn.KnnSearchStrategy;
import org.apache.lucene.util.FixedBitSet;
+import org.apache.lucene.util.IORunnable;
import org.apache.lucene.util.InfoStream;
import org.apache.lucene.util.hnsw.HnswUtil.Component;
@@ -83,6 +84,7 @@ public class HnswGraphBuilder implements HnswBuilder {
protected final HnswLock hnswLock;
protected InfoStream infoStream = InfoStream.getDefault();
+ private IORunnable abortCheck;
protected boolean frozen;
/**
@@ -209,6 +211,15 @@ public void setInfoStream(InfoStream infoStream) {
this.infoStream = infoStream;
}
+ @Override
+ public void setAbortCheck(IORunnable abortCheck) {
+ Objects.requireNonNull(abortCheck);
+ if (this.abortCheck != null) {
+ throw new IllegalStateException("abort check was already set");
+ }
+ this.abortCheck = abortCheck;
+ }
+
@Override
public OnHeapHnswGraph getCompletedGraph() throws IOException {
if (!frozen) {
@@ -280,6 +291,9 @@ private void addGraphNodeInternal(int node, UpdateableRandomVectorScorer scorer,
if (frozen) {
throw new IllegalStateException("Graph builder is already frozen");
}
+ if (abortCheck != null) {
+ abortCheck.run();
+ }
final int nodeLevel = getRandomGraphLevel(ml, random);
// first add nodes to all levels
for (int level = nodeLevel; level >= 0; level--) {
diff --git a/lucene/core/src/java/org/apache/lucene/util/hnsw/IncrementalHnswGraphMerger.java b/lucene/core/src/java/org/apache/lucene/util/hnsw/IncrementalHnswGraphMerger.java
index 3caa9600ed1a..d9dc4144472e 100644
--- a/lucene/core/src/java/org/apache/lucene/util/hnsw/IncrementalHnswGraphMerger.java
+++ b/lucene/core/src/java/org/apache/lucene/util/hnsw/IncrementalHnswGraphMerger.java
@@ -33,6 +33,7 @@
import org.apache.lucene.util.BitSet;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.FixedBitSet;
+import org.apache.lucene.util.IORunnable;
import org.apache.lucene.util.InfoStream;
/**
@@ -46,6 +47,7 @@ public class IncrementalHnswGraphMerger implements HnswGraphMerger {
protected final RandomVectorScorerSupplier scorerSupplier;
protected final int M;
protected final int beamWidth;
+ protected final IORunnable abortCheck;
protected List