Skip to content

Commit ae14bc7

Browse files
committed
Ignore conflicting fields during dynamic mapping update
This fixes a bug when concurrently executing index requests that have different types for the same field.
1 parent 0c69de1 commit ae14bc7

File tree

2 files changed

+48
-1
lines changed

2 files changed

+48
-1
lines changed

server/src/internalClusterTest/java/org/elasticsearch/index/mapper/DynamicMappingIT.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@
6363
import static org.hamcrest.Matchers.equalTo;
6464
import static org.hamcrest.Matchers.hasKey;
6565
import static org.hamcrest.Matchers.instanceOf;
66+
import static org.hamcrest.Matchers.is;
67+
import static org.hamcrest.Matchers.oneOf;
6668

6769
public class DynamicMappingIT extends ESIntegTestCase {
6870

@@ -190,6 +192,35 @@ private Map<String, Object> indexConcurrently(int numberOfFieldsToCreate, Settin
190192
return properties;
191193
}
192194

195+
public void testConcurrentDynamicMappingsWithConflictingType() throws Throwable {
196+
int numberOfDocsToCreate = 16;
197+
indicesAdmin().prepareCreate("index").setSettings(Settings.builder()).get();
198+
ensureGreen("index");
199+
final AtomicReference<Throwable> error = new AtomicReference<>();
200+
startInParallel(numberOfDocsToCreate, i -> {
201+
try {
202+
assertEquals(
203+
DocWriteResponse.Result.CREATED,
204+
prepareIndex("index").setId(Integer.toString(i)).setSource("field" + i, 0, "field" + (i + 1), 0.1).get().getResult()
205+
);
206+
} catch (Exception e) {
207+
error.compareAndSet(null, e);
208+
}
209+
});
210+
if (error.get() != null) {
211+
throw error.get();
212+
}
213+
client().admin().indices().prepareRefresh("index").get();
214+
for (int i = 0; i < numberOfDocsToCreate; ++i) {
215+
assertTrue(client().prepareGet("index", Integer.toString(i)).get().isExists());
216+
}
217+
Map<String, Object> index = indicesAdmin().prepareGetMappings("index").get().getMappings().get("index").getSourceAsMap();
218+
for (int i = 0, j = 1; i < numberOfDocsToCreate; i++, j++) {
219+
assertThat(new WriteField("properties.field" + i + ".type", () -> index).get(null), is(oneOf("long", "float")));
220+
assertThat(new WriteField("properties.field" + j + ".type", () -> index).get(null), is(oneOf("long", "float")));
221+
}
222+
}
223+
193224
public void testPreflightCheckAvoidsMaster() throws InterruptedException, IOException {
194225
// can't use INDEX_MAPPING_TOTAL_FIELDS_LIMIT_SETTING nor INDEX_MAPPING_DEPTH_LIMIT_SETTING as a check here, as that is already
195226
// checked at parse time, see testTotalFieldsLimitForDynamicMappingsUpdateCheckedAtDocumentParseTime

server/src/main/java/org/elasticsearch/index/mapper/ObjectMapper.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,14 +677,30 @@ private static Map<String, Mapper> buildMergedMappers(
677677
// replaces an existing one.
678678
if (objectMergeContext.getMapperBuilderContext().getMergeReason() == MergeReason.INDEX_TEMPLATE) {
679679
putMergedMapper(mergedMappers, mergeWithMapper);
680-
} else {
680+
} else if (isConflictingDynamicMapping(objectMergeContext, mergeWithMapper, mergeIntoMapper) == false) {
681681
putMergedMapper(mergedMappers, mergeIntoMapper.merge(mergeWithMapper, objectMergeContext));
682682
}
683683
}
684684
}
685685
return Map.copyOf(mergedMappers);
686686
}
687687

688+
/*
689+
* We're ignoring the field if a dynamic mapping update tries to define a conflicting field type (dynamic mappings update)
690+
* This is caused by another index request with a different value racing to update the mappings.
691+
* After ignoring the update the index request will be re-tried and sees the updated mappings for this field.
692+
* The updated mappings will be taken into account when parsing the document
693+
* (for example by coercing the value, ignore_malformed values, or failing the index request due to a type conflict).
694+
*/
695+
private static boolean isConflictingDynamicMapping(
696+
MapperMergeContext objectMergeContext,
697+
Mapper mergeWithMapper,
698+
Mapper mergeIntoMapper
699+
) {
700+
return objectMergeContext.getMapperBuilderContext().getMergeReason().isAutoUpdate()
701+
&& mergeIntoMapper.typeName().equals(mergeWithMapper.typeName()) == false;
702+
}
703+
688704
private static void putMergedMapper(Map<String, Mapper> mergedMappers, @Nullable Mapper merged) {
689705
if (merged != null) {
690706
mergedMappers.put(merged.leafName(), merged);

0 commit comments

Comments
 (0)