diff --git a/docs/changelog/130893.yaml b/docs/changelog/130893.yaml new file mode 100644 index 0000000000000..3829641dd8455 --- /dev/null +++ b/docs/changelog/130893.yaml @@ -0,0 +1,5 @@ +pr: 130893 +summary: Add a direct IO option for rescoring to `bbq_hnsw` +area: Vector Search +type: feature +issues: [] diff --git a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/VectorSearchIT.java b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/VectorSearchIT.java index afee17cd82e2d..ca060c9790cda 100644 --- a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/VectorSearchIT.java +++ b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/VectorSearchIT.java @@ -16,6 +16,7 @@ import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.support.XContentMapValues; +import org.elasticsearch.index.mapper.MapperFeatures; import java.io.IOException; import java.util.List; @@ -35,6 +36,7 @@ public VectorSearchIT(@Name("upgradedNodes") int upgradedNodes) { private static final String BYTE_INDEX_NAME = "byte_vector_index"; private static final String QUANTIZED_INDEX_NAME = "quantized_vector_index"; private static final String BBQ_INDEX_NAME = "bbq_vector_index"; + private static final String BBQ_INDEX_NAME_RESCORE = "bbq_vector_index_rescore"; private static final String FLAT_QUANTIZED_INDEX_NAME = "flat_quantized_vector_index"; private static final String FLAT_BBQ_INDEX_NAME = "flat_bbq_vector_index"; @@ -507,6 +509,63 @@ public void testBBQVectorSearch() throws Exception { ); } + public void testBBQVectorSearchOffheapRescoring() throws Exception { + assumeTrue("Disabling off-heap rescoring is not supported", oldClusterHasFeature(MapperFeatures.BBQ_OFFHEAP_RESCORING)); + if (isOldCluster()) { + String mapping = """ + { + "properties": { + "vector": { + "type": "dense_vector", + "dims": 64, + "index": true, + "similarity": "cosine", + "index_options": { + "type": "bbq_hnsw", + "ef_construction": 100, + "m": 16, + "disable_offheap_cache_rescoring": true + } + } + } + } + """; + // create index and index 10 random floating point vectors + createIndex( + BBQ_INDEX_NAME_RESCORE, + Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1).put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0).build(), + mapping + ); + index64DimVectors(BBQ_INDEX_NAME_RESCORE); + // force merge the index + client().performRequest(new Request("POST", "/" + BBQ_INDEX_NAME_RESCORE + "/_forcemerge?max_num_segments=1")); + } + Request searchRequest = new Request("POST", "/" + BBQ_INDEX_NAME_RESCORE + "/_search"); + searchRequest.setJsonEntity(""" + { + "knn": { + "field": "vector", + "query_vector": [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], + "k": 2, + "num_candidates": 5, + "rescore_vector": { + "oversample": 2.0 + } + } + } + """); + Map response = search(searchRequest); + assertThat(extractValue(response, "hits.total.value"), equalTo(2)); + List> hits = extractValue(response, "hits.hits"); + assertThat("expected: 0 received" + hits.get(0).get("_id") + " hits: " + response, hits.get(0).get("_id"), equalTo("0")); + assertThat( + "expected_near: 0.99 received" + hits.get(0).get("_score") + "hits: " + response, + (double) hits.get(0).get("_score"), + closeTo(0.9934857, 0.005) + ); + } + public void testFlatBBQVectorSearch() throws Exception { assumeTrue("Quantized vector search is not supported on this version", oldClusterHasFeature(BBQ_VECTOR_SEARCH_TEST_FEATURE)); if (isOldCluster()) { diff --git a/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexTester.java b/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexTester.java index 421375f038475..d78f906923272 100644 --- a/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexTester.java +++ b/qa/vector/src/main/java/org/elasticsearch/test/knn/KnnIndexTester.java @@ -106,7 +106,11 @@ private static String formatIndexPath(CmdLineArgs args) { static Codec createCodec(CmdLineArgs args) { final KnnVectorsFormat format; if (args.indexType() == IndexType.IVF) { - format = new ES920DiskBBQVectorsFormat(args.ivfClusterSize(), ES920DiskBBQVectorsFormat.DEFAULT_CENTROIDS_PER_PARENT_CLUSTER); + format = new ES920DiskBBQVectorsFormat( + args.ivfClusterSize(), + ES920DiskBBQVectorsFormat.DEFAULT_CENTROIDS_PER_PARENT_CLUSTER, + false + ); } else { if (args.quantizeBits() == 1) { if (args.indexType() == IndexType.FLAT) { diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/41_knn_search_bbq_hnsw.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/41_knn_search_bbq_hnsw.yml index e3c1155ed2000..e4b89d669d91c 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/41_knn_search_bbq_hnsw.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/41_knn_search_bbq_hnsw.yml @@ -338,6 +338,69 @@ setup: - match: { hits.hits.1._score: $rescore_score1 } - match: { hits.hits.2._score: $rescore_score2 } --- +"Test index configured rescore vector with no off-heap scoring": + - requires: + cluster_features: ["mapper.vectors.bbq_offheap_rescoring"] + reason: Needs bbq_offheap_rescoring feature + - skip: + features: "headers" + - do: + indices.create: + index: bbq_rescore_hnsw + body: + settings: + index: + number_of_shards: 1 + mappings: + properties: + vector: + type: dense_vector + dims: 64 + index: true + similarity: max_inner_product + index_options: + type: bbq_hnsw + disable_offheap_cache_rescoring: true + rescore_vector: + oversample: 1.5 + + - do: + bulk: + index: bbq_rescore_hnsw + refresh: true + body: | + { "index": {"_id": "1"}} + { "vector": [0.077, 0.32 , -0.205, 0.63 , 0.032, 0.201, 0.167, -0.313, 0.176, 0.531, -0.375, 0.334, -0.046, 0.078, -0.349, 0.272, 0.307, -0.083, 0.504, 0.255, -0.404, 0.289, -0.226, -0.132, -0.216, 0.49 , 0.039, 0.507, -0.307, 0.107, 0.09 , -0.265, -0.285, 0.336, -0.272, 0.369, -0.282, 0.086, -0.132, 0.475, -0.224, 0.203, 0.439, 0.064, 0.246, -0.396, 0.297, 0.242, -0.028, 0.321, -0.022, -0.009, -0.001 , 0.031, -0.533, 0.45, -0.683, 1.331, 0.194, -0.157, -0.1 , -0.279, -0.098, -0.176] } + { "index": {"_id": "2"}} + { "vector": [0.196, 0.514, 0.039, 0.555, -0.042, 0.242, 0.463, -0.348, -0.08 , 0.442, -0.067, -0.05 , -0.001, 0.298, -0.377, 0.048, 0.307, 0.159, 0.278, 0.119, -0.057, 0.333, -0.289, -0.438, -0.014, 0.361, -0.169, 0.292, -0.229, 0.123, 0.031, -0.138, -0.139, 0.315, -0.216, 0.322, -0.445, -0.059, 0.071, 0.429, -0.602, -0.142, 0.11 , 0.192, 0.259, -0.241, 0.181, -0.166, 0.082, 0.107, -0.05 , 0.155, 0.011, 0.161, -0.486, 0.569, -0.489, 0.901, 0.208, 0.011, -0.209, -0.153, -0.27 , -0.013] } + { "index": {"_id": "3"}} + { "vector": [0.196, 0.514, 0.039, 0.555, -0.042, 0.242, 0.463, -0.348, -0.08 , 0.442, -0.067, -0.05 , -0.001, 0.298, -0.377, 0.048, 0.307, 0.159, 0.278, 0.119, -0.057, 0.333, -0.289, -0.438, -0.014, 0.361, -0.169, 0.292, -0.229, 0.123, 0.031, -0.138, -0.139, 0.315, -0.216, 0.322, -0.445, -0.059, 0.071, 0.429, -0.602, -0.142, 0.11 , 0.192, 0.259, -0.241, 0.181, -0.166, 0.082, 0.107, -0.05 , 0.155, 0.011, 0.161, -0.486, 0.569, -0.489, 0.901, 0.208, 0.011, -0.209, -0.153, -0.27 , -0.013] } + + - do: + headers: + Content-Type: application/json + search: + rest_total_hits_as_int: true + index: bbq_rescore_hnsw + body: + knn: + field: vector + query_vector: [0.128, 0.067, -0.08 , 0.395, -0.11 , -0.259, 0.473, -0.393, + 0.292, 0.571, -0.491, 0.444, -0.288, 0.198, -0.343, 0.015, + 0.232, 0.088, 0.228, 0.151, -0.136, 0.236, -0.273, -0.259, + -0.217, 0.359, -0.207, 0.352, -0.142, 0.192, -0.061, -0.17 , + -0.343, 0.189, -0.221, 0.32 , -0.301, -0.1 , 0.005, 0.232, + -0.344, 0.136, 0.252, 0.157, -0.13 , -0.244, 0.193, -0.034, + -0.12 , -0.193, -0.102, 0.252, -0.185, -0.167, -0.575, 0.582, + -0.426, 0.983, 0.212, 0.204, 0.03 , -0.276, -0.425, -0.158] + k: 3 + num_candidates: 3 + + - match: { hits.total: 3 } + - set: { hits.hits.0._score: rescore_score0 } + - set: { hits.hits.1._score: rescore_score1 } + - set: { hits.hits.2._score: rescore_score2 } +--- "Test index configured rescore vector updateable and settable to 0": - requires: cluster_features: ["mapper.dense_vector.rescore_zero_vector"] diff --git a/server/src/internalClusterTest/java/org/elasticsearch/index/store/DirectIOIT.java b/server/src/internalClusterTest/java/org/elasticsearch/index/store/DirectIOIT.java index 7e23161cd42cf..845c7df31fa27 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/index/store/DirectIOIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/index/store/DirectIOIT.java @@ -17,7 +17,6 @@ import org.apache.lucene.store.IndexOutput; import org.apache.lucene.tests.util.LuceneTestCase; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.index.codec.vectors.es818.ES818BinaryQuantizedVectorsFormat; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.search.vectors.KnnSearchBuilder; import org.elasticsearch.search.vectors.VectorData; @@ -50,8 +49,6 @@ public class DirectIOIT extends ESIntegTestCase { @BeforeClass public static void checkSupported() { - assumeTrue("Direct IO is not enabled", ES818BinaryQuantizedVectorsFormat.USE_DIRECT_IO); - Path path = createTempDir("directIOProbe"); try (Directory dir = open(path); IndexOutput out = dir.createOutput("out", IOContext.DEFAULT)) { out.writeString("test"); @@ -76,7 +73,7 @@ protected Collection> nodePlugins() { } private void indexVectors() { - String type = randomFrom("bbq_flat", "bbq_hnsw"); + String type = "bbq_hnsw"; assertAcked( prepareCreate("foo-vectors").setSettings(Settings.builder().put(InternalSettingsPlugin.USE_COMPOUND_FILE.getKey(), false)) .setMapping(""" @@ -89,7 +86,8 @@ private void indexVectors() { "index": true, "similarity": "l2_norm", "index_options": { - "type": "%type%" + "type": "%type%", + "disable_offheap_cache_rescoring": true } } } @@ -105,11 +103,12 @@ private void indexVectors() { assertBBQIndexType(type); // test assertion to ensure that the correct index type is being used } - @SuppressWarnings("unchecked") static void assertBBQIndexType(String type) { var response = indicesAdmin().prepareGetFieldMappings("foo-vectors").setFields("fooVector").get(); - var map = (Map) response.fieldMappings("foo-vectors", "fooVector").sourceAsMap().get("fooVector"); - assertThat((String) ((Map) map.get("index_options")).get("type"), is(equalTo(type))); + var map = (Map) response.fieldMappings("foo-vectors", "fooVector").sourceAsMap().get("fooVector"); + var options = (Map) map.get("index_options"); + assertThat(options.get("type"), is(equalTo(type))); + assertThat(options.get("disable_offheap_cache_rescoring"), is(true)); } @TestLogging(value = "org.elasticsearch.index.store.FsDirectoryFactory:DEBUG", reason = "to capture trace logging for direct IO") diff --git a/server/src/main/java/module-info.java b/server/src/main/java/module-info.java index cfe6345d7e590..6021871586961 100644 --- a/server/src/main/java/module-info.java +++ b/server/src/main/java/module-info.java @@ -7,7 +7,6 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import org.elasticsearch.index.codec.vectors.diskbbq.ES920DiskBBQVectorsFormat; import org.elasticsearch.plugins.internal.RestExtension; import org.elasticsearch.reservedstate.ReservedStateHandlerProvider; @@ -462,7 +461,7 @@ org.elasticsearch.index.codec.vectors.es816.ES816HnswBinaryQuantizedVectorsFormat, org.elasticsearch.index.codec.vectors.es818.ES818BinaryQuantizedVectorsFormat, org.elasticsearch.index.codec.vectors.es818.ES818HnswBinaryQuantizedVectorsFormat, - ES920DiskBBQVectorsFormat; + org.elasticsearch.index.codec.vectors.diskbbq.ES920DiskBBQVectorsFormat; provides org.apache.lucene.codecs.Codec with diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/AbstractFlatVectorsFormat.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/AbstractFlatVectorsFormat.java index 4bfdbe4c9273a..d5d34df4a0e40 100644 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/AbstractFlatVectorsFormat.java +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/AbstractFlatVectorsFormat.java @@ -11,21 +11,11 @@ import org.apache.lucene.codecs.hnsw.FlatVectorsFormat; import org.apache.lucene.codecs.hnsw.FlatVectorsScorer; -import org.elasticsearch.core.SuppressForbidden; import static org.elasticsearch.index.mapper.vectors.DenseVectorFieldMapper.MAX_DIMS_COUNT; public abstract class AbstractFlatVectorsFormat extends FlatVectorsFormat { - public static final boolean USE_DIRECT_IO = getUseDirectIO(); - - @SuppressForbidden( - reason = "TODO Deprecate any lenient usage of Boolean#parseBoolean https://github.com/elastic/elasticsearch/issues/128993" - ) - private static boolean getUseDirectIO() { - return Boolean.parseBoolean(System.getProperty("vector.rescoring.directio", "false")); - } - protected AbstractFlatVectorsFormat(String name) { super(name); } diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/diskbbq/ES920DiskBBQVectorsFormat.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/diskbbq/ES920DiskBBQVectorsFormat.java index 72f7f620d211a..af41d1148ee73 100644 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/diskbbq/ES920DiskBBQVectorsFormat.java +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/diskbbq/ES920DiskBBQVectorsFormat.java @@ -20,6 +20,7 @@ import org.apache.lucene.index.SegmentWriteState; import org.elasticsearch.common.util.Maps; import org.elasticsearch.index.codec.vectors.OptimizedScalarQuantizer; +import org.elasticsearch.index.codec.vectors.es818.DirectIOLucene99FlatVectorsFormat; import java.io.IOException; import java.util.Collections; @@ -63,7 +64,15 @@ public class ES920DiskBBQVectorsFormat extends KnnVectorsFormat { private static final FlatVectorsFormat rawVectorFormat = new Lucene99FlatVectorsFormat( FlatVectorScorerUtil.getLucene99FlatVectorsScorer() ); - private static final Map supportedFormats = Map.of(rawVectorFormat.getName(), rawVectorFormat); + private static final FlatVectorsFormat directIORawVectorFormat = new DirectIOLucene99FlatVectorsFormat( + FlatVectorScorerUtil.getLucene99FlatVectorsScorer() + ); + private static final Map supportedFormats = Map.of( + rawVectorFormat.getName(), + rawVectorFormat, + directIORawVectorFormat.getName(), + directIORawVectorFormat + ); // This dynamically sets the cluster probe based on the `k` requested and the number of clusters. // useful when searching with 'efSearch' type parameters instead of requiring a specific ratio. @@ -77,8 +86,9 @@ public class ES920DiskBBQVectorsFormat extends KnnVectorsFormat { private final int vectorPerCluster; private final int centroidsPerParentCluster; + private final boolean directRawDiskReads; - public ES920DiskBBQVectorsFormat(int vectorPerCluster, int centroidsPerParentCluster) { + public ES920DiskBBQVectorsFormat(int vectorPerCluster, int centroidsPerParentCluster, boolean directRawDiskReads) { super(NAME); if (vectorPerCluster < MIN_VECTORS_PER_CLUSTER || vectorPerCluster > MAX_VECTORS_PER_CLUSTER) { throw new IllegalArgumentException( @@ -102,19 +112,21 @@ public ES920DiskBBQVectorsFormat(int vectorPerCluster, int centroidsPerParentClu } this.vectorPerCluster = vectorPerCluster; this.centroidsPerParentCluster = centroidsPerParentCluster; + this.directRawDiskReads = directRawDiskReads; } /** Constructs a format using the given graph construction parameters and scalar quantization. */ public ES920DiskBBQVectorsFormat() { - this(DEFAULT_VECTORS_PER_CLUSTER, DEFAULT_CENTROIDS_PER_PARENT_CLUSTER); + this(DEFAULT_VECTORS_PER_CLUSTER, DEFAULT_CENTROIDS_PER_PARENT_CLUSTER, false); } @Override public KnnVectorsWriter fieldsWriter(SegmentWriteState state) throws IOException { + FlatVectorsFormat rawFormat = directRawDiskReads ? directIORawVectorFormat : rawVectorFormat; return new ES920DiskBBQVectorsWriter( - rawVectorFormat.getName(), + rawFormat.getName(), state, - rawVectorFormat.fieldsWriter(state), + rawFormat.fieldsWriter(state), vectorPerCluster, centroidsPerParentCluster ); diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/es818/DirectIOLucene99FlatVectorsFormat.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/es818/DirectIOLucene99FlatVectorsFormat.java index 620b9ec6637b6..f7b2f8613c42c 100644 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/es818/DirectIOLucene99FlatVectorsFormat.java +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/es818/DirectIOLucene99FlatVectorsFormat.java @@ -41,11 +41,10 @@ * Copied from Lucene99FlatVectorsFormat in Lucene 10.1 * * This is copied to change the implementation of {@link #fieldsReader} only. - * The codec format itself is not changed, so we keep the original {@link #NAME} */ public class DirectIOLucene99FlatVectorsFormat extends AbstractFlatVectorsFormat { - static final String NAME = "Lucene99FlatVectorsFormat"; + static final String NAME = "DirectIOLucene99FlatVectorsFormat"; public static final int VERSION_START = 0; public static final int VERSION_CURRENT = VERSION_START; @@ -69,7 +68,6 @@ public FlatVectorsWriter fieldsWriter(SegmentWriteState state) throws IOExceptio } static boolean shouldUseDirectIO(SegmentReadState state) { - assert USE_DIRECT_IO; return FsDirectoryFactory.isHybridFs(state.directory); } diff --git a/server/src/main/java/org/elasticsearch/index/codec/vectors/es818/ES818BinaryQuantizedVectorsFormat.java b/server/src/main/java/org/elasticsearch/index/codec/vectors/es818/ES818BinaryQuantizedVectorsFormat.java index a9601ffc408e8..cca69d6f14224 100644 --- a/server/src/main/java/org/elasticsearch/index/codec/vectors/es818/ES818BinaryQuantizedVectorsFormat.java +++ b/server/src/main/java/org/elasticsearch/index/codec/vectors/es818/ES818BinaryQuantizedVectorsFormat.java @@ -98,17 +98,20 @@ public class ES818BinaryQuantizedVectorsFormat extends AbstractFlatVectorsFormat static final String VECTOR_DATA_EXTENSION = "veb"; static final int DIRECT_MONOTONIC_BLOCK_SHIFT = 16; - private static final FlatVectorsFormat rawVectorFormat = USE_DIRECT_IO - ? new DirectIOLucene99FlatVectorsFormat(FlatVectorScorerUtil.getLucene99FlatVectorsScorer()) - : new Lucene99FlatVectorsFormat(FlatVectorScorerUtil.getLucene99FlatVectorsScorer()); - - private static final ES818BinaryFlatVectorsScorer scorer = new ES818BinaryFlatVectorsScorer( + static final ES818BinaryFlatVectorsScorer scorer = new ES818BinaryFlatVectorsScorer( FlatVectorScorerUtil.getLucene99FlatVectorsScorer() ); + private final FlatVectorsFormat rawVectorFormat; + /** Creates a new instance with the default number of vectors per cluster. */ public ES818BinaryQuantizedVectorsFormat() { - super(NAME); + this(NAME, new Lucene99FlatVectorsFormat(FlatVectorScorerUtil.getLucene99FlatVectorsScorer())); + } + + ES818BinaryQuantizedVectorsFormat(String name, FlatVectorsFormat rawVectorFormat) { + super(name); + this.rawVectorFormat = rawVectorFormat; } @Override diff --git a/server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java b/server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java index cc9f1c0e07db1..3f26999613b30 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java @@ -51,6 +51,7 @@ public class MapperFeatures implements FeatureSpecification { static final NodeFeature PATTERN_TEXT = new NodeFeature("mapper.patterned_text"); static final NodeFeature IGNORED_SOURCE_FIELDS_PER_ENTRY = new NodeFeature("mapper.ignored_source_fields_per_entry"); public static final NodeFeature MULTI_FIELD_UNICODE_OPTIMISATION_FIX = new NodeFeature("mapper.multi_field.unicode_optimisation_fix"); + public static final NodeFeature BBQ_OFFHEAP_RESCORING = new NodeFeature("mapper.vectors.bbq_offheap_rescoring"); @Override public Set getTestFeatures() { @@ -87,7 +88,8 @@ public Set getTestFeatures() { PATTERN_TEXT, IGNORED_SOURCE_FIELDS_PER_ENTRY, MULTI_FIELD_UNICODE_OPTIMISATION_FIX, - MATCH_ONLY_TEXT_BLOCK_LOADER_FIX + MATCH_ONLY_TEXT_BLOCK_LOADER_FIX, + BBQ_OFFHEAP_RESCORING ); } } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java index 74ef4a3d7b021..96cc33127472a 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldMapper.java @@ -388,7 +388,8 @@ private DenseVectorIndexOptions defaultIndexOptions(boolean defaultInt8Hnsw, boo return new BBQHnswIndexOptions( Lucene99HnswVectorsFormat.DEFAULT_MAX_CONN, Lucene99HnswVectorsFormat.DEFAULT_BEAM_WIDTH, - new RescoreVector(DEFAULT_OVERSAMPLE) + new RescoreVector(DEFAULT_OVERSAMPLE), + false ); } else if (defaultInt8Hnsw) { return new Int8HnswIndexOptions( @@ -1447,6 +1448,8 @@ public boolean supportsDimension(int dims) { public DenseVectorIndexOptions parseIndexOptions(String fieldName, Map indexOptionsMap, IndexVersion indexVersion) { Object mNode = indexOptionsMap.remove("m"); Object efConstructionNode = indexOptionsMap.remove("ef_construction"); + Object directRawVectorReadsNode = indexOptionsMap.remove("direct_raw_vector_reads"); + if (mNode == null) { mNode = Lucene99HnswVectorsFormat.DEFAULT_MAX_CONN; } @@ -1455,6 +1458,7 @@ public DenseVectorIndexOptions parseIndexOptions(String fieldName, Map new ES920DiskBBQVectorsFormat(MIN_VECTORS_PER_CLUSTER - 1, 16)); - expectThrows(IllegalArgumentException.class, () -> new ES920DiskBBQVectorsFormat(MAX_VECTORS_PER_CLUSTER + 1, 16)); - expectThrows(IllegalArgumentException.class, () -> new ES920DiskBBQVectorsFormat(128, MIN_CENTROIDS_PER_PARENT_CLUSTER - 1)); - expectThrows(IllegalArgumentException.class, () -> new ES920DiskBBQVectorsFormat(128, MAX_CENTROIDS_PER_PARENT_CLUSTER + 1)); + expectThrows(IllegalArgumentException.class, () -> new ES920DiskBBQVectorsFormat(MIN_VECTORS_PER_CLUSTER - 1, 16, false)); + expectThrows(IllegalArgumentException.class, () -> new ES920DiskBBQVectorsFormat(MAX_VECTORS_PER_CLUSTER + 1, 16, false)); + expectThrows(IllegalArgumentException.class, () -> new ES920DiskBBQVectorsFormat(128, MIN_CENTROIDS_PER_PARENT_CLUSTER - 1, false)); + expectThrows(IllegalArgumentException.class, () -> new ES920DiskBBQVectorsFormat(128, MAX_CENTROIDS_PER_PARENT_CLUSTER + 1, false)); } public void testSimpleOffHeapSize() throws IOException { diff --git a/server/src/test/java/org/elasticsearch/index/codec/vectors/es818/ES818BinaryQuantizedVectorsFormatTests.java b/server/src/test/java/org/elasticsearch/index/codec/vectors/es818/ES818BinaryQuantizedVectorsFormatTests.java index 540e9f1587cd1..095a85980e2f4 100644 --- a/server/src/test/java/org/elasticsearch/index/codec/vectors/es818/ES818BinaryQuantizedVectorsFormatTests.java +++ b/server/src/test/java/org/elasticsearch/index/codec/vectors/es818/ES818BinaryQuantizedVectorsFormatTests.java @@ -40,7 +40,6 @@ import org.apache.lucene.index.SoftDeletesRetentionMergePolicy; import org.apache.lucene.index.Term; import org.apache.lucene.index.VectorSimilarityFunction; -import org.apache.lucene.misc.store.DirectIODirectory; import org.apache.lucene.search.FieldExistsQuery; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.KnnFloatVectorQuery; @@ -54,32 +53,19 @@ import org.apache.lucene.search.join.DiversifyingChildrenFloatKnnVectorQuery; import org.apache.lucene.search.join.QueryBitSetProducer; import org.apache.lucene.store.Directory; -import org.apache.lucene.store.FSDirectory; -import org.apache.lucene.store.IOContext; -import org.apache.lucene.store.IndexOutput; import org.apache.lucene.store.MMapDirectory; import org.apache.lucene.tests.index.BaseKnnVectorsFormatTestCase; import org.apache.lucene.tests.store.MockDirectoryWrapper; import org.apache.lucene.tests.util.TestUtil; import org.elasticsearch.common.logging.LogConfigurator; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.index.IndexModule; -import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.codec.vectors.BQVectorUtils; import org.elasticsearch.index.codec.vectors.OptimizedScalarQuantizer; -import org.elasticsearch.index.shard.ShardId; -import org.elasticsearch.index.shard.ShardPath; -import org.elasticsearch.index.store.FsDirectoryFactory; -import org.elasticsearch.test.IndexSettingsModule; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; -import java.util.OptionalLong; import static java.lang.String.format; import static org.apache.lucene.index.VectorSimilarityFunction.DOT_PRODUCT; @@ -284,14 +270,6 @@ public void testSimpleOffHeapSize() throws IOException { } } - public void testSimpleOffHeapSizeFSDir() throws IOException { - checkDirectIOSupported(); - var config = newIndexWriterConfig().setUseCompoundFile(false); // avoid compound files to allow directIO - try (Directory dir = newFSDirectory()) { - testSimpleOffHeapSizeImpl(dir, config, false); - } - } - public void testSimpleOffHeapSizeMMapDir() throws IOException { try (Directory dir = newMMapDirectory()) { testSimpleOffHeapSizeImpl(dir, newIndexWriterConfig(), true); @@ -331,39 +309,4 @@ static Directory newMMapDirectory() throws IOException { } return dir; } - - private Directory newFSDirectory() throws IOException { - Settings settings = Settings.builder() - .put(IndexModule.INDEX_STORE_TYPE_SETTING.getKey(), IndexModule.Type.HYBRIDFS.name().toLowerCase(Locale.ROOT)) - .build(); - IndexSettings idxSettings = IndexSettingsModule.newIndexSettings("foo", settings); - Path tempDir = createTempDir().resolve(idxSettings.getUUID()).resolve("0"); - Files.createDirectories(tempDir); - ShardPath path = new ShardPath(false, tempDir, tempDir, new ShardId(idxSettings.getIndex(), 0)); - Directory dir = (new FsDirectoryFactory()).newDirectory(idxSettings, path); - if (random().nextBoolean()) { - dir = new MockDirectoryWrapper(random(), dir); - } - return dir; - } - - static void checkDirectIOSupported() { - assumeTrue("Direct IO is not enabled", ES818BinaryQuantizedVectorsFormat.USE_DIRECT_IO); - - Path path = createTempDir("directIOProbe"); - try (Directory dir = open(path); IndexOutput out = dir.createOutput("out", IOContext.DEFAULT)) { - out.writeString("test"); - } catch (IOException e) { - assumeNoException("test requires a filesystem that supports Direct IO", e); - } - } - - static DirectIODirectory open(Path path) throws IOException { - return new DirectIODirectory(FSDirectory.open(path)) { - @Override - protected boolean useDirectIO(String name, IOContext context, OptionalLong fileLength) { - return true; - } - }; - } } diff --git a/server/src/test/java/org/elasticsearch/index/codec/vectors/es818/ES818HnswBinaryQuantizedVectorsFormatTests.java b/server/src/test/java/org/elasticsearch/index/codec/vectors/es818/ES818HnswBinaryQuantizedVectorsFormatTests.java index 7bc78186fa533..268e359597039 100644 --- a/server/src/test/java/org/elasticsearch/index/codec/vectors/es818/ES818HnswBinaryQuantizedVectorsFormatTests.java +++ b/server/src/test/java/org/elasticsearch/index/codec/vectors/es818/ES818HnswBinaryQuantizedVectorsFormatTests.java @@ -36,13 +36,9 @@ import org.apache.lucene.index.KnnVectorValues; import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.VectorSimilarityFunction; -import org.apache.lucene.misc.store.DirectIODirectory; import org.apache.lucene.search.AcceptDocs; import org.apache.lucene.search.TopDocs; import org.apache.lucene.store.Directory; -import org.apache.lucene.store.FSDirectory; -import org.apache.lucene.store.IOContext; -import org.apache.lucene.store.IndexOutput; import org.apache.lucene.store.MMapDirectory; import org.apache.lucene.tests.index.BaseKnnVectorsFormatTestCase; import org.apache.lucene.tests.store.MockDirectoryWrapper; @@ -50,20 +46,10 @@ import org.apache.lucene.util.SameThreadExecutorService; import org.apache.lucene.util.VectorUtil; import org.elasticsearch.common.logging.LogConfigurator; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.index.IndexModule; -import org.elasticsearch.index.IndexSettings; -import org.elasticsearch.index.shard.ShardId; -import org.elasticsearch.index.shard.ShardPath; -import org.elasticsearch.index.store.FsDirectoryFactory; -import org.elasticsearch.test.IndexSettingsModule; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.Arrays; import java.util.Locale; -import java.util.OptionalLong; import static java.lang.String.format; import static org.apache.lucene.index.VectorSimilarityFunction.DOT_PRODUCT; @@ -169,14 +155,6 @@ public void testSimpleOffHeapSize() throws IOException { } } - public void testSimpleOffHeapSizeFSDir() throws IOException { - checkDirectIOSupported(); - var config = newIndexWriterConfig().setUseCompoundFile(false); // avoid compound files to allow directIO - try (Directory dir = newFSDirectory()) { - testSimpleOffHeapSizeImpl(dir, config, false); - } - } - public void testSimpleOffHeapSizeMMapDir() throws IOException { try (Directory dir = newMMapDirectory()) { testSimpleOffHeapSizeImpl(dir, newIndexWriterConfig(), true); @@ -217,39 +195,4 @@ static Directory newMMapDirectory() throws IOException { } return dir; } - - private Directory newFSDirectory() throws IOException { - Settings settings = Settings.builder() - .put(IndexModule.INDEX_STORE_TYPE_SETTING.getKey(), IndexModule.Type.HYBRIDFS.name().toLowerCase(Locale.ROOT)) - .build(); - IndexSettings idxSettings = IndexSettingsModule.newIndexSettings("foo", settings); - Path tempDir = createTempDir().resolve(idxSettings.getUUID()).resolve("0"); - Files.createDirectories(tempDir); - ShardPath path = new ShardPath(false, tempDir, tempDir, new ShardId(idxSettings.getIndex(), 0)); - Directory dir = (new FsDirectoryFactory()).newDirectory(idxSettings, path); - if (random().nextBoolean()) { - dir = new MockDirectoryWrapper(random(), dir); - } - return dir; - } - - static void checkDirectIOSupported() { - assumeTrue("Direct IO is not enabled", ES818BinaryQuantizedVectorsFormat.USE_DIRECT_IO); - - Path path = createTempDir("directIOProbe"); - try (Directory dir = open(path); IndexOutput out = dir.createOutput("out", IOContext.DEFAULT)) { - out.writeString("test"); - } catch (IOException e) { - assumeNoException("test requires a filesystem that supports Direct IO", e); - } - } - - static DirectIODirectory open(Path path) throws IOException { - return new DirectIODirectory(FSDirectory.open(path)) { - @Override - protected boolean useDirectIO(String name, IOContext context, OptionalLong fileLength) { - return true; - } - }; - } } diff --git a/server/src/test/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldTypeTests.java b/server/src/test/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldTypeTests.java index 82ac6a76f5adf..ea78028e1d47a 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldTypeTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/vectors/DenseVectorFieldTypeTests.java @@ -100,7 +100,8 @@ public static DenseVectorFieldMapper.DenseVectorIndexOptions randomIndexOptionsA new DenseVectorFieldMapper.BBQHnswIndexOptions( randomIntBetween(1, 100), randomIntBetween(1, 10_000), - randomFrom((DenseVectorFieldMapper.RescoreVector) null, randomRescoreVector()) + randomFrom((DenseVectorFieldMapper.RescoreVector) null, randomRescoreVector()), + randomBoolean() ), new DenseVectorFieldMapper.BBQFlatIndexOptions( randomFrom((DenseVectorFieldMapper.RescoreVector) null, randomRescoreVector()) @@ -113,7 +114,8 @@ public static DenseVectorFieldMapper.DenseVectorIndexOptions randomIndexOptionsA new DenseVectorFieldMapper.BBQIVFIndexOptions( randomIntBetween(MIN_VECTORS_PER_CLUSTER, MAX_VECTORS_PER_CLUSTER), randomFloatBetween(0.0f, 100.0f, true), - randomFrom((DenseVectorFieldMapper.RescoreVector) null, randomRescoreVector()) + randomFrom((DenseVectorFieldMapper.RescoreVector) null, randomRescoreVector()), + randomBoolean() ) ); } @@ -141,7 +143,12 @@ private DenseVectorFieldMapper.DenseVectorIndexOptions randomIndexOptionsHnswQua randomFrom((Float) null, 0f, (float) randomDoubleBetween(0.9, 1.0, true)), rescoreVector ), - new DenseVectorFieldMapper.BBQHnswIndexOptions(randomIntBetween(1, 100), randomIntBetween(1, 10_000), rescoreVector) + new DenseVectorFieldMapper.BBQHnswIndexOptions( + randomIntBetween(1, 100), + randomIntBetween(1, 10_000), + rescoreVector, + randomBoolean() + ) ); } diff --git a/server/src/test/java/org/elasticsearch/search/vectors/AbstractDiversifyingChildrenIVFKnnVectorQueryTestCase.java b/server/src/test/java/org/elasticsearch/search/vectors/AbstractDiversifyingChildrenIVFKnnVectorQueryTestCase.java index 8e27cca564a40..0446b6485e099 100644 --- a/server/src/test/java/org/elasticsearch/search/vectors/AbstractDiversifyingChildrenIVFKnnVectorQueryTestCase.java +++ b/server/src/test/java/org/elasticsearch/search/vectors/AbstractDiversifyingChildrenIVFKnnVectorQueryTestCase.java @@ -98,7 +98,8 @@ public void setUp() throws Exception { random().nextInt( ES920DiskBBQVectorsFormat.MIN_CENTROIDS_PER_PARENT_CLUSTER, ES920DiskBBQVectorsFormat.MAX_CENTROIDS_PER_PARENT_CLUSTER - ) + ), + random().nextBoolean() ); } diff --git a/server/src/test/java/org/elasticsearch/search/vectors/AbstractIVFKnnVectorQueryTestCase.java b/server/src/test/java/org/elasticsearch/search/vectors/AbstractIVFKnnVectorQueryTestCase.java index e94f0dc915802..1aabd772a5e42 100644 --- a/server/src/test/java/org/elasticsearch/search/vectors/AbstractIVFKnnVectorQueryTestCase.java +++ b/server/src/test/java/org/elasticsearch/search/vectors/AbstractIVFKnnVectorQueryTestCase.java @@ -98,7 +98,7 @@ abstract class AbstractIVFKnnVectorQueryTestCase extends LuceneTestCase { @Before public void setUp() throws Exception { super.setUp(); - format = new ES920DiskBBQVectorsFormat(128, 4); + format = new ES920DiskBBQVectorsFormat(128, 4, false); } abstract AbstractIVFKnnVectorQuery getKnnVectorQuery(String field, float[] query, int k, Query queryFilter, float visitRatio); diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapper.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapper.java index b7c01ce817b32..aad9586dae0c5 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapper.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapper.java @@ -1362,7 +1362,7 @@ public static DenseVectorFieldMapper.DenseVectorIndexOptions defaultBbqHnswDense int m = Lucene99HnswVectorsFormat.DEFAULT_MAX_CONN; int efConstruction = Lucene99HnswVectorsFormat.DEFAULT_BEAM_WIDTH; DenseVectorFieldMapper.RescoreVector rescoreVector = new DenseVectorFieldMapper.RescoreVector(DEFAULT_RESCORE_OVERSAMPLE); - return new DenseVectorFieldMapper.BBQHnswIndexOptions(m, efConstruction, rescoreVector); + return new DenseVectorFieldMapper.BBQHnswIndexOptions(m, efConstruction, rescoreVector, false); } static SemanticTextIndexOptions defaultIndexOptions(IndexVersion indexVersionCreated, MinimalServiceSettings modelSettings) { diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapperTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapperTests.java index 838e4576716ff..b5a03b8c5787f 100644 --- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapperTests.java +++ b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/mapper/SemanticTextFieldMapperTests.java @@ -1369,7 +1369,7 @@ private static DenseVectorFieldMapper.DenseVectorIndexOptions defaultBbqHnswDens int m = Lucene99HnswVectorsFormat.DEFAULT_MAX_CONN; int efConstruction = Lucene99HnswVectorsFormat.DEFAULT_BEAM_WIDTH; DenseVectorFieldMapper.RescoreVector rescoreVector = new DenseVectorFieldMapper.RescoreVector(DEFAULT_RESCORE_OVERSAMPLE); - return new DenseVectorFieldMapper.BBQHnswIndexOptions(m, efConstruction, rescoreVector); + return new DenseVectorFieldMapper.BBQHnswIndexOptions(m, efConstruction, rescoreVector, false); } private static SemanticTextIndexOptions defaultBbqHnswSemanticTextIndexOptions() {