Skip to content

Commit f91124a

Browse files
authored
Fix default index options when dimensions are unset for legacy indices (#130540)
In #129825, we modified the dense_vector field type to delay setting index options until the field's dimensions are known. However, this introduced a discrepancy for indices created before that change, which would previously default to int8_hnsw even when dimensions were not set. This discrepancy leads to an assertion failure in mixed-version clusters, where the serialized mappings differ between nodes: ``` [2025-07-02T20:37:29,852][ERROR][o.e.b.ElasticsearchUncaughtExceptionHandler] [v9.0.4-2] fatal error in thread [elasticsearch[v9.0.4-2][clusterApplierService#updateTask][T#1]], exiting java.lang.AssertionError: provided source [{"_doc":{"properties":{"vector":{"type":"dense_vector","index":true,"similarity":"cosine"}}}}] differs from mapping [{"_doc":{"properties":{"vector":{"type":"dense_vector","index":true,"similarity":"cosine","index_options":{"type":"int8_hnsw","m":16,"ef_construction":100}}}}}] ``` This commit resolves the issue by ensuring that indices created before the change continue to default to int8_hnsw index options, even if dimensions remain unset.
1 parent e07f9fe commit f91124a

File tree

2 files changed

+65
-56
lines changed

2 files changed

+65
-56
lines changed

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/40_knn_search.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,40 @@ setup:
605605
- match: { hits.hits.0._score: $knn_score0 }
606606
- match: { hits.hits.1._score: $knn_score1 }
607607
- match: { hits.hits.2._score: $knn_score2 }
608+
609+
---
610+
"Dimensions are dynamically set":
611+
- do:
612+
indices.create:
613+
index: test_index
614+
body:
615+
mappings:
616+
properties:
617+
embedding:
618+
type: dense_vector
619+
620+
- do:
621+
index:
622+
index: test_index
623+
id: "0"
624+
body:
625+
embedding: [ 0.5, 111.3, -13.0, 14.8, -156.0 ]
626+
627+
- do:
628+
indices.get_mapping:
629+
index: test_index
630+
631+
- match: { test_index.mappings.properties.embedding.type: dense_vector }
632+
- match: { test_index.mappings.properties.embedding.dims: 5 }
633+
634+
- do:
635+
catch: bad_request
636+
index:
637+
index: test_index
638+
id: "0"
639+
body:
640+
embedding: [ 0.5, 111.3 ]
641+
608642
---
609643
"Updating dim to null is not allowed":
610644
- requires:

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

Lines changed: 31 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,9 @@ public Builder(String name, IndexVersion indexVersionCreated) {
342342
|| previous.updatableTo(current)
343343
);
344344
if (defaultInt8Hnsw || defaultBBQ8Hnsw) {
345-
this.indexOptions.alwaysSerialize();
345+
if (defaultBBQ8Hnsw == false || (dims != null && dims.isConfigured())) {
346+
this.indexOptions.alwaysSerialize();
347+
}
346348
}
347349
this.indexed.addValidator(v -> {
348350
if (v) {
@@ -365,21 +367,31 @@ public Builder(String name, IndexVersion indexVersionCreated) {
365367
}
366368

367369
private DenseVectorIndexOptions defaultIndexOptions(boolean defaultInt8Hnsw, boolean defaultBBQHnsw) {
368-
if (this.dims != null && this.dims.isConfigured() && elementType.getValue() == ElementType.FLOAT && this.indexed.getValue()) {
369-
if (defaultBBQHnsw && this.dims.getValue() >= BBQ_DIMS_DEFAULT_THRESHOLD) {
370-
return new BBQHnswIndexOptions(
371-
Lucene99HnswVectorsFormat.DEFAULT_MAX_CONN,
372-
Lucene99HnswVectorsFormat.DEFAULT_BEAM_WIDTH,
373-
new RescoreVector(DEFAULT_OVERSAMPLE)
374-
);
375-
} else if (defaultInt8Hnsw) {
376-
return new Int8HnswIndexOptions(
377-
Lucene99HnswVectorsFormat.DEFAULT_MAX_CONN,
378-
Lucene99HnswVectorsFormat.DEFAULT_BEAM_WIDTH,
379-
null,
380-
null
381-
);
382-
}
370+
if (elementType.getValue() != ElementType.FLOAT || indexed.getValue() == false) {
371+
return null;
372+
}
373+
374+
boolean dimIsConfigured = dims != null && dims.isConfigured();
375+
if (defaultBBQHnsw && dimIsConfigured == false) {
376+
// Delay selecting the default index options until dimensions are configured.
377+
// This applies only to indices that are eligible to use BBQ as the default,
378+
// since prior to this change, the default was selected eagerly.
379+
return null;
380+
}
381+
382+
if (defaultBBQHnsw && dimIsConfigured && dims.getValue() >= BBQ_DIMS_DEFAULT_THRESHOLD) {
383+
return new BBQHnswIndexOptions(
384+
Lucene99HnswVectorsFormat.DEFAULT_MAX_CONN,
385+
Lucene99HnswVectorsFormat.DEFAULT_BEAM_WIDTH,
386+
new RescoreVector(DEFAULT_OVERSAMPLE)
387+
);
388+
} else if (defaultInt8Hnsw) {
389+
return new Int8HnswIndexOptions(
390+
Lucene99HnswVectorsFormat.DEFAULT_MAX_CONN,
391+
Lucene99HnswVectorsFormat.DEFAULT_BEAM_WIDTH,
392+
null,
393+
null
394+
);
383395
}
384396
return null;
385397
}
@@ -2853,46 +2865,9 @@ public void parse(DocumentParserContext context) throws IOException {
28532865
}
28542866
if (fieldType().dims == null) {
28552867
int dims = fieldType().elementType.parseDimensionCount(context);
2856-
final boolean defaultInt8Hnsw = indexCreatedVersion.onOrAfter(IndexVersions.DEFAULT_DENSE_VECTOR_TO_INT8_HNSW);
2857-
final boolean defaultBBQ8Hnsw = indexCreatedVersion.onOrAfter(IndexVersions.DEFAULT_DENSE_VECTOR_TO_BBQ_HNSW);
2858-
DenseVectorIndexOptions denseVectorIndexOptions = fieldType().indexOptions;
2859-
if (denseVectorIndexOptions == null && fieldType().getElementType() == ElementType.FLOAT && fieldType().isIndexed()) {
2860-
if (defaultBBQ8Hnsw && dims >= BBQ_DIMS_DEFAULT_THRESHOLD) {
2861-
denseVectorIndexOptions = new BBQHnswIndexOptions(
2862-
Lucene99HnswVectorsFormat.DEFAULT_MAX_CONN,
2863-
Lucene99HnswVectorsFormat.DEFAULT_BEAM_WIDTH,
2864-
new RescoreVector(DEFAULT_OVERSAMPLE)
2865-
);
2866-
} else if (defaultInt8Hnsw) {
2867-
denseVectorIndexOptions = new Int8HnswIndexOptions(
2868-
Lucene99HnswVectorsFormat.DEFAULT_MAX_CONN,
2869-
Lucene99HnswVectorsFormat.DEFAULT_BEAM_WIDTH,
2870-
null,
2871-
null
2872-
);
2873-
}
2874-
}
2875-
if (denseVectorIndexOptions != null) {
2876-
denseVectorIndexOptions.validateDimension(dims);
2877-
}
2878-
DenseVectorFieldType updatedDenseVectorFieldType = new DenseVectorFieldType(
2879-
fieldType().name(),
2880-
indexCreatedVersion,
2881-
fieldType().elementType,
2882-
dims,
2883-
fieldType().indexed,
2884-
fieldType().similarity,
2885-
denseVectorIndexOptions,
2886-
fieldType().meta(),
2887-
fieldType().isSyntheticSource
2888-
);
2889-
Mapper update = new DenseVectorFieldMapper(
2890-
leafName(),
2891-
updatedDenseVectorFieldType,
2892-
builderParams,
2893-
denseVectorIndexOptions,
2894-
indexCreatedVersion
2895-
);
2868+
DenseVectorFieldMapper.Builder builder = (Builder) getMergeBuilder();
2869+
builder.dimensions(dims);
2870+
Mapper update = builder.build(context.createDynamicMapperBuilderContext());
28962871
context.addDynamicMapper(update);
28972872
return;
28982873
}

0 commit comments

Comments
 (0)