Skip to content

Commit 5577029

Browse files
authored
Fix vector rehydration from _source during concurrent query phase (elastic#134747)
This change ensures that a separate patch loader is created for each leaf when rehydrating vectors from _source. This is required for concurrent searches across multiple segments, since the patch loader maintains state that must be preserved between the high-level object and the leaf object. Note: this bug only affects serverless, so it is marked as a non-issue.
1 parent f1a0d1c commit 5577029

File tree

4 files changed

+28
-16
lines changed

4 files changed

+28
-16
lines changed

server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2874,8 +2874,11 @@ public String toString() {
28742874
@Override
28752875
public SourceLoader.SyntheticVectorsLoader syntheticVectorsLoader() {
28762876
if (isExcludeSourceVectors) {
2877-
var syntheticField = new IndexedSyntheticFieldLoader(indexCreatedVersion, fieldType().similarity);
2878-
return new SyntheticVectorsPatchFieldLoader(syntheticField, syntheticField::copyVectorAsList);
2877+
return new SyntheticVectorsPatchFieldLoader<>(
2878+
// Recreate the object for each leaf so that different segments can be searched concurrently.
2879+
() -> new IndexedSyntheticFieldLoader(indexCreatedVersion, fieldType().similarity),
2880+
IndexedSyntheticFieldLoader::copyVectorAsList
2881+
);
28792882
}
28802883
return null;
28812884
}
@@ -2900,10 +2903,12 @@ private class IndexedSyntheticFieldLoader extends SourceLoader.DocValuesBasedSyn
29002903

29012904
private final IndexVersion indexCreatedVersion;
29022905
private final VectorSimilarity vectorSimilarity;
2906+
private final Thread creationThread;
29032907

29042908
private IndexedSyntheticFieldLoader(IndexVersion indexCreatedVersion, VectorSimilarity vectorSimilarity) {
29052909
this.indexCreatedVersion = indexCreatedVersion;
29062910
this.vectorSimilarity = vectorSimilarity;
2911+
this.creationThread = Thread.currentThread();
29072912
}
29082913

29092914
@Override
@@ -2930,6 +2935,8 @@ private boolean shouldNormalize() {
29302935

29312936
private DocValuesLoader createLoader(KnnVectorValues.DocIndexIterator iterator, boolean checkMagnitude) {
29322937
return docId -> {
2938+
assert creationThread == Thread.currentThread()
2939+
: "Thread mismatch: created by [" + creationThread + "], but accessed by [" + Thread.currentThread() + "]";
29332940
if (iterator.docID() > docId) {
29342941
return hasValue = false;
29352942
}

server/src/main/java/org/elasticsearch/index/mapper/vectors/SparseVectorFieldMapper.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,8 +333,11 @@ protected SyntheticSourceSupport syntheticSourceSupport() {
333333
@Override
334334
public SourceLoader.SyntheticVectorsLoader syntheticVectorsLoader() {
335335
if (isExcludeSourceVectors) {
336-
var syntheticField = new SparseVectorSyntheticFieldLoader(fullPath(), leafName());
337-
return new SyntheticVectorsPatchFieldLoader(syntheticField, syntheticField::copyAsMap);
336+
return new SyntheticVectorsPatchFieldLoader<>(
337+
// Recreate the object for each leaf so that different segments can be searched concurrently.
338+
() -> new SparseVectorSyntheticFieldLoader(fullPath(), leafName()),
339+
SparseVectorSyntheticFieldLoader::copyAsMap
340+
);
338341
}
339342
return null;
340343
}

server/src/main/java/org/elasticsearch/index/mapper/vectors/SyntheticVectorsPatchFieldLoader.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,30 @@
1010
package org.elasticsearch.index.mapper.vectors;
1111

1212
import org.apache.lucene.index.LeafReaderContext;
13-
import org.elasticsearch.core.CheckedSupplier;
13+
import org.elasticsearch.core.CheckedFunction;
1414
import org.elasticsearch.index.mapper.SourceLoader;
1515

1616
import java.io.IOException;
17+
import java.util.function.Supplier;
1718

18-
public class SyntheticVectorsPatchFieldLoader implements SourceLoader.SyntheticVectorsLoader {
19-
private final SourceLoader.SyntheticFieldLoader syntheticLoader;
20-
private final CheckedSupplier<Object, IOException> copyObject;
19+
public class SyntheticVectorsPatchFieldLoader<T extends SourceLoader.SyntheticFieldLoader> implements SourceLoader.SyntheticVectorsLoader {
20+
private final Supplier<T> syntheticLoaderSupplier;
21+
private final CheckedFunction<T, Object, IOException> copyObject;
2122

22-
public SyntheticVectorsPatchFieldLoader(
23-
SourceLoader.SyntheticFieldLoader syntheticLoader,
24-
CheckedSupplier<Object, IOException> copyObject
25-
) {
26-
this.syntheticLoader = syntheticLoader;
23+
public SyntheticVectorsPatchFieldLoader(Supplier<T> syntheticLoaderSupplier, CheckedFunction<T, Object, IOException> copyObject) {
24+
this.syntheticLoaderSupplier = syntheticLoaderSupplier;
2725
this.copyObject = copyObject;
2826
}
2927

3028
public SourceLoader.SyntheticVectorsLoader.Leaf leaf(LeafReaderContext context) throws IOException {
29+
var syntheticLoader = syntheticLoaderSupplier.get();
3130
var dvLoader = syntheticLoader.docValuesLoader(context.reader(), null);
3231
return (doc, acc) -> {
3332
if (dvLoader == null) {
3433
return;
3534
}
3635
if (dvLoader.advanceToDoc(doc) && syntheticLoader.hasValue()) {
37-
acc.add(new SourceLoader.LeafSyntheticVectorPath(syntheticLoader.fieldName(), copyObject.get()));
36+
acc.add(new SourceLoader.LeafSyntheticVectorPath(syntheticLoader.fieldName(), copyObject.apply(syntheticLoader)));
3837
}
3938
};
4039
}

x-pack/plugin/rank-vectors/src/main/java/org/elasticsearch/xpack/rank/vectors/mapper/RankVectorsFieldMapper.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,8 +409,11 @@ protected SyntheticSourceSupport syntheticSourceSupport() {
409409
@Override
410410
public SourceLoader.SyntheticVectorsLoader syntheticVectorsLoader() {
411411
if (isExcludeSourceVectors) {
412-
var syntheticField = new DocValuesSyntheticFieldLoader();
413-
return new SyntheticVectorsPatchFieldLoader(syntheticField, syntheticField::copyVectorsAsList);
412+
return new SyntheticVectorsPatchFieldLoader<>(
413+
// Recreate the object for each leaf so that different segments can be searched concurrently.
414+
DocValuesSyntheticFieldLoader::new,
415+
DocValuesSyntheticFieldLoader::copyVectorsAsList
416+
);
414417
}
415418
return null;
416419
}

0 commit comments

Comments
 (0)