Skip to content

Commit f535c27

Browse files
committed
Make mapping updates more robust.
This changes a couple of things: Mappings are truly immutable. Before, each field mapper stored a MappedFieldTypeReference that was shared across fields that have the same name across types. This means that a mapping update could have the side-effect of changing the field type in other types when updateAllTypes is true. This works differently now: after a mapping update, a new copy of the mappings is created in such a way that fields across different types have the same MappedFieldType. See the new Mapper.updateFieldType API which replaces MappedFieldTypeReference. DocumentMapper is now immutable and MapperService.merge has been refactored in such a way that if an exception is thrown while eg. lookup structures are being updated, then the whole mapping update will be aborted. As a consequence, FieldTypeLookup's checkCompatibility has been folded into copyAndAddAll. Synchronization was simplified: given that mappings are truly immutable, we don't need the read/write lock so that no documents can be parsed while a mapping update is being processed. Document parsing is not performed under a lock anymore, and mapping merging uses a simple synchronized block.
1 parent f9a601c commit f535c27

File tree

47 files changed

+691
-662
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+691
-662
lines changed

core/src/main/java/org/elasticsearch/cluster/metadata/MetaDataMappingService.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -259,9 +259,8 @@ private ClusterState applyRequest(ClusterState currentState, PutMappingClusterSt
259259
} else {
260260
newMapper = indexService.mapperService().parse(request.type(), mappingUpdateSource, existingMapper == null);
261261
if (existingMapper != null) {
262-
// first, simulate
263-
// this will just throw exceptions in case of problems
264-
existingMapper.merge(newMapper.mapping(), true, request.updateAllTypes());
262+
// first, simulate: just call merge and ignore the result
263+
existingMapper.merge(newMapper.mapping(), request.updateAllTypes());
265264
} else {
266265
// TODO: can we find a better place for this validation?
267266
// The reason this validation is here is that the mapper service doesn't learn about

core/src/main/java/org/elasticsearch/index/analysis/FieldNameAnalyzer.java

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,36 +23,24 @@
2323
import org.apache.lucene.analysis.DelegatingAnalyzerWrapper;
2424
import org.elasticsearch.common.collect.CopyOnWriteHashMap;
2525

26-
import java.util.AbstractMap;
2726
import java.util.Map;
28-
import java.util.stream.Stream;
2927

3028
/**
3129
*
3230
*/
3331
public final class FieldNameAnalyzer extends DelegatingAnalyzerWrapper {
3432

35-
private final CopyOnWriteHashMap<String, Analyzer> analyzers;
36-
private final Analyzer defaultAnalyzer;
33+
private final Map<String, Analyzer> analyzers;
3734

38-
public FieldNameAnalyzer(Analyzer defaultAnalyzer) {
39-
this(new CopyOnWriteHashMap<>(), defaultAnalyzer);
40-
}
41-
42-
public FieldNameAnalyzer(Map<String, Analyzer> analyzers, Analyzer defaultAnalyzer) {
35+
public FieldNameAnalyzer(Map<String, Analyzer> analyzers) {
4336
super(Analyzer.PER_FIELD_REUSE_STRATEGY);
4437
this.analyzers = CopyOnWriteHashMap.copyOf(analyzers);
45-
this.defaultAnalyzer = defaultAnalyzer;
4638
}
4739

4840
public Map<String, Analyzer> analyzers() {
4941
return analyzers;
5042
}
5143

52-
public Analyzer defaultAnalyzer() {
53-
return defaultAnalyzer;
54-
}
55-
5644
@Override
5745
protected Analyzer getWrappedAnalyzer(String fieldName) {
5846
Analyzer analyzer = analyzers.get(fieldName);
@@ -63,18 +51,4 @@ protected Analyzer getWrappedAnalyzer(String fieldName) {
6351
// Fields need to be explicitly added
6452
throw new IllegalArgumentException("Field [" + fieldName + "] has no associated analyzer");
6553
}
66-
67-
/**
68-
* Return a new instance that contains the union of this and of the provided analyzers.
69-
*/
70-
public FieldNameAnalyzer copyAndAddAll(Stream<? extends Map.Entry<String, Analyzer>> mappers) {
71-
CopyOnWriteHashMap<String, Analyzer> result = analyzers.copyAndPutAll(mappers.map((e) -> {
72-
if (e.getValue() == null) {
73-
return new AbstractMap.SimpleImmutableEntry<>(e.getKey(), defaultAnalyzer);
74-
}
75-
return e;
76-
}));
77-
return new FieldNameAnalyzer(result, defaultAnalyzer);
78-
}
79-
8054
}

core/src/main/java/org/elasticsearch/index/mapper/DocumentFieldMappers.java

Lines changed: 25 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@
2020
package org.elasticsearch.index.mapper;
2121

2222
import org.apache.lucene.analysis.Analyzer;
23-
import org.elasticsearch.common.collect.CopyOnWriteHashMap;
2423
import org.elasticsearch.common.regex.Regex;
25-
import org.elasticsearch.index.analysis.AnalysisService;
2624
import org.elasticsearch.index.analysis.FieldNameAnalyzer;
2725

28-
import java.util.AbstractMap;
2926
import java.util.Collection;
27+
import java.util.Collections;
28+
import java.util.HashMap;
3029
import java.util.HashSet;
3130
import java.util.Iterator;
31+
import java.util.Map;
3232
import java.util.Set;
3333

3434
/**
@@ -37,44 +37,38 @@
3737
public final class DocumentFieldMappers implements Iterable<FieldMapper> {
3838

3939
/** Full field name to mapper */
40-
private final CopyOnWriteHashMap<String, FieldMapper> fieldMappers;
40+
private final Map<String, FieldMapper> fieldMappers;
4141

4242
private final FieldNameAnalyzer indexAnalyzer;
4343
private final FieldNameAnalyzer searchAnalyzer;
4444
private final FieldNameAnalyzer searchQuoteAnalyzer;
4545

46-
public DocumentFieldMappers(AnalysisService analysisService) {
47-
this(new CopyOnWriteHashMap<String, FieldMapper>(),
48-
new FieldNameAnalyzer(analysisService.defaultIndexAnalyzer()),
49-
new FieldNameAnalyzer(analysisService.defaultSearchAnalyzer()),
50-
new FieldNameAnalyzer(analysisService.defaultSearchQuoteAnalyzer()));
51-
}
52-
53-
private DocumentFieldMappers(CopyOnWriteHashMap<String, FieldMapper> fieldMappers, FieldNameAnalyzer indexAnalyzer, FieldNameAnalyzer searchAnalyzer, FieldNameAnalyzer searchQuoteAnalyzer) {
54-
this.fieldMappers = fieldMappers;
55-
this.indexAnalyzer = indexAnalyzer;
56-
this.searchAnalyzer = searchAnalyzer;
57-
this.searchQuoteAnalyzer = searchQuoteAnalyzer;
46+
private static void put(Map<String, Analyzer> analyzers, String key, Analyzer value, Analyzer defaultValue) {
47+
if (value == null) {
48+
value = defaultValue;
49+
}
50+
analyzers.put(key, value);
5851
}
5952

60-
public DocumentFieldMappers copyAndAllAll(Collection<FieldMapper> newMappers) {
61-
CopyOnWriteHashMap<String, FieldMapper> map = this.fieldMappers;
62-
for (FieldMapper fieldMapper : newMappers) {
63-
map = map.copyAndPut(fieldMapper.fieldType().names().fullName(), fieldMapper);
53+
public DocumentFieldMappers(Collection<FieldMapper> mappers, Analyzer defaultIndex, Analyzer defaultSearch, Analyzer defaultSearchQuote) {
54+
Map<String, FieldMapper> fieldMappers = new HashMap<>();
55+
Map<String, Analyzer> indexAnalyzers = new HashMap<>();
56+
Map<String, Analyzer> searchAnalyzers = new HashMap<>();
57+
Map<String, Analyzer> searchQuoteAnalyzers = new HashMap<>();
58+
for (FieldMapper mapper : mappers) {
59+
fieldMappers.put(mapper.name(), mapper);
60+
MappedFieldType fieldType = mapper.fieldType();
61+
put(indexAnalyzers, fieldType.names().indexName(), fieldType.indexAnalyzer(), defaultIndex);
62+
put(searchAnalyzers, fieldType.names().indexName(), fieldType.searchAnalyzer(), defaultSearch);
63+
put(searchQuoteAnalyzers, fieldType.names().indexName(), fieldType.searchQuoteAnalyzer(), defaultSearchQuote);
6464
}
65-
FieldNameAnalyzer indexAnalyzer = this.indexAnalyzer.copyAndAddAll(newMappers.stream().map((input) ->
66-
new AbstractMap.SimpleImmutableEntry<>(input.fieldType().names().indexName(), (Analyzer)input.fieldType().indexAnalyzer())
67-
));
68-
FieldNameAnalyzer searchAnalyzer = this.searchAnalyzer.copyAndAddAll(newMappers.stream().map((input) ->
69-
new AbstractMap.SimpleImmutableEntry<>(input.fieldType().names().indexName(), (Analyzer)input.fieldType().searchAnalyzer())
70-
));
71-
FieldNameAnalyzer searchQuoteAnalyzer = this.searchQuoteAnalyzer.copyAndAddAll(newMappers.stream().map((input) ->
72-
new AbstractMap.SimpleImmutableEntry<>(input.fieldType().names().indexName(), (Analyzer) input.fieldType().searchQuoteAnalyzer())
73-
));
74-
return new DocumentFieldMappers(map,indexAnalyzer,searchAnalyzer,searchQuoteAnalyzer);
65+
this.fieldMappers = Collections.unmodifiableMap(fieldMappers);
66+
this.indexAnalyzer = new FieldNameAnalyzer(indexAnalyzers);
67+
this.searchAnalyzer = new FieldNameAnalyzer(searchAnalyzers);
68+
this.searchQuoteAnalyzer = new FieldNameAnalyzer(searchQuoteAnalyzers);
7569
}
7670

77-
/** Returns the mapper for the given field */
71+
/** Returns the mapper for the given field */
7872
public FieldMapper getMapper(String field) {
7973
return fieldMappers.get(field);
8074
}
@@ -112,14 +106,6 @@ public Analyzer indexAnalyzer() {
112106
return this.indexAnalyzer;
113107
}
114108

115-
/**
116-
* A smart analyzer used for indexing that takes into account specific analyzers configured
117-
* per {@link FieldMapper} with a custom default analyzer for no explicit field analyzer.
118-
*/
119-
public Analyzer indexAnalyzer(Analyzer defaultAnalyzer) {
120-
return new FieldNameAnalyzer(indexAnalyzer.analyzers(), defaultAnalyzer);
121-
}
122-
123109
/**
124110
* A smart analyzer used for searching that takes into account specific analyzers configured
125111
* per {@link FieldMapper}.

core/src/main/java/org/elasticsearch/index/mapper/DocumentMapper.java

Lines changed: 42 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,15 @@
2424
import org.apache.lucene.search.Scorer;
2525
import org.apache.lucene.search.Weight;
2626
import org.elasticsearch.ElasticsearchGenerationException;
27-
import org.elasticsearch.Version;
28-
import org.elasticsearch.common.Nullable;
2927
import org.elasticsearch.common.bytes.BytesReference;
3028
import org.elasticsearch.common.compress.CompressedXContent;
3129
import org.elasticsearch.common.settings.Settings;
3230
import org.elasticsearch.common.text.Text;
33-
import org.elasticsearch.common.util.concurrent.ReleasableLock;
3431
import org.elasticsearch.common.xcontent.ToXContent;
3532
import org.elasticsearch.common.xcontent.XContentBuilder;
3633
import org.elasticsearch.common.xcontent.XContentType;
34+
import org.elasticsearch.index.IndexSettings;
35+
import org.elasticsearch.index.analysis.AnalysisService;
3736
import org.elasticsearch.index.mapper.MetadataFieldMapper.TypeParser;
3837
import org.elasticsearch.index.mapper.internal.AllFieldMapper;
3938
import org.elasticsearch.index.mapper.internal.IdFieldMapper;
@@ -51,15 +50,12 @@
5150

5251
import java.io.IOException;
5352
import java.util.ArrayList;
54-
import java.util.Arrays;
55-
import java.util.Collection;
5653
import java.util.Collections;
5754
import java.util.HashMap;
5855
import java.util.LinkedHashMap;
5956
import java.util.List;
6057
import java.util.Map;
6158
import java.util.Objects;
62-
import java.util.concurrent.locks.ReentrantReadWriteLock;
6359

6460
import static java.util.Collections.emptyMap;
6561

@@ -72,16 +68,14 @@ public static class Builder {
7268

7369
private Map<Class<? extends MetadataFieldMapper>, MetadataFieldMapper> metadataMappers = new LinkedHashMap<>();
7470

75-
private final Settings indexSettings;
76-
7771
private final RootObjectMapper rootObjectMapper;
7872

7973
private Map<String, Object> meta = emptyMap();
8074

8175
private final Mapper.BuilderContext builderContext;
8276

83-
public Builder(Settings indexSettings, RootObjectMapper.Builder builder, MapperService mapperService) {
84-
this.indexSettings = indexSettings;
77+
public Builder(RootObjectMapper.Builder builder, MapperService mapperService) {
78+
final Settings indexSettings = mapperService.getIndexSettings().getSettings();
8579
this.builderContext = new Mapper.BuilderContext(indexSettings, new ContentPath(1));
8680
this.rootObjectMapper = builder.build(builderContext);
8781

@@ -104,9 +98,14 @@ public Builder put(MetadataFieldMapper.Builder<?, ?> mapper) {
10498
return this;
10599
}
106100

107-
public DocumentMapper build(MapperService mapperService, DocumentMapperParser docMapperParser) {
101+
public DocumentMapper build(MapperService mapperService) {
108102
Objects.requireNonNull(rootObjectMapper, "Mapper builder must have the root object mapper set");
109-
return new DocumentMapper(mapperService, indexSettings, docMapperParser, rootObjectMapper, meta, metadataMappers, mapperService.mappingLock);
103+
Mapping mapping = new Mapping(
104+
mapperService.getIndexSettings().getIndexVersionCreated(),
105+
rootObjectMapper,
106+
metadataMappers.values().toArray(new MetadataFieldMapper[metadataMappers.values().size()]),
107+
meta);
108+
return new DocumentMapper(mapperService, mapping);
110109
}
111110
}
112111

@@ -115,38 +114,25 @@ public DocumentMapper build(MapperService mapperService, DocumentMapperParser do
115114
private final String type;
116115
private final Text typeText;
117116

118-
private volatile CompressedXContent mappingSource;
117+
private final CompressedXContent mappingSource;
119118

120-
private volatile Mapping mapping;
119+
private final Mapping mapping;
121120

122121
private final DocumentParser documentParser;
123122

124-
private volatile DocumentFieldMappers fieldMappers;
125-
126-
private volatile Map<String, ObjectMapper> objectMappers = Collections.emptyMap();
123+
private final DocumentFieldMappers fieldMappers;
127124

128-
private boolean hasNestedObjects = false;
125+
private final Map<String, ObjectMapper> objectMappers;
129126

130-
private final ReleasableLock mappingWriteLock;
131-
private final ReentrantReadWriteLock mappingLock;
127+
private final boolean hasNestedObjects;
132128

133-
public DocumentMapper(MapperService mapperService, @Nullable Settings indexSettings, DocumentMapperParser docMapperParser,
134-
RootObjectMapper rootObjectMapper,
135-
Map<String, Object> meta,
136-
Map<Class<? extends MetadataFieldMapper>, MetadataFieldMapper> metadataMappers,
137-
ReentrantReadWriteLock mappingLock) {
129+
public DocumentMapper(MapperService mapperService, Mapping mapping) {
138130
this.mapperService = mapperService;
139-
this.type = rootObjectMapper.name();
131+
this.type = mapping.root().name();
140132
this.typeText = new Text(this.type);
141-
this.mapping = new Mapping(
142-
Version.indexCreated(indexSettings),
143-
rootObjectMapper,
144-
metadataMappers.values().toArray(new MetadataFieldMapper[metadataMappers.values().size()]),
145-
meta);
146-
this.documentParser = new DocumentParser(indexSettings, docMapperParser, this, new ReleasableLock(mappingLock.readLock()));
147-
148-
this.mappingWriteLock = new ReleasableLock(mappingLock.writeLock());
149-
this.mappingLock = mappingLock;
133+
final IndexSettings indexSettings = mapperService.getIndexSettings();
134+
this.mapping = mapping;
135+
this.documentParser = new DocumentParser(indexSettings, mapperService.documentMapperParser(), this);
150136

151137
if (metadataMapper(ParentFieldMapper.class).active()) {
152138
// mark the routing field mapper as required
@@ -163,7 +149,11 @@ public DocumentMapper(MapperService mapperService, @Nullable Settings indexSetti
163149
}
164150
MapperUtils.collect(this.mapping.root, newObjectMappers, newFieldMappers);
165151

166-
this.fieldMappers = new DocumentFieldMappers(docMapperParser.analysisService).copyAndAllAll(newFieldMappers);
152+
final AnalysisService analysisService = mapperService.analysisService();
153+
this.fieldMappers = new DocumentFieldMappers(newFieldMappers,
154+
analysisService.defaultIndexAnalyzer(),
155+
analysisService.defaultSearchAnalyzer(),
156+
analysisService.defaultSearchQuoteAnalyzer());
167157

168158
Map<String, ObjectMapper> builder = new HashMap<>();
169159
for (ObjectMapper objectMapper : newObjectMappers) {
@@ -173,14 +163,20 @@ public DocumentMapper(MapperService mapperService, @Nullable Settings indexSetti
173163
}
174164
}
175165

166+
boolean hasNestedObjects = false;
176167
this.objectMappers = Collections.unmodifiableMap(builder);
177168
for (ObjectMapper objectMapper : newObjectMappers) {
178169
if (objectMapper.nested().isNested()) {
179170
hasNestedObjects = true;
180171
}
181172
}
173+
this.hasNestedObjects = hasNestedObjects;
182174

183-
refreshSource();
175+
try {
176+
mappingSource = new CompressedXContent(this, XContentType.JSON, ToXContent.EMPTY_PARAMS);
177+
} catch (Exception e) {
178+
throw new ElasticsearchGenerationException("failed to serialize source for type [" + type + "]", e);
179+
}
184180
}
185181

186182
public Mapping mapping() {
@@ -334,46 +330,17 @@ public boolean isParent(String type) {
334330
return mapperService.getParentTypes().contains(type);
335331
}
336332

337-
private void addMappers(Collection<ObjectMapper> objectMappers, Collection<FieldMapper> fieldMappers, boolean updateAllTypes) {
338-
assert mappingLock.isWriteLockedByCurrentThread();
339-
340-
// update mappers for this document type
341-
Map<String, ObjectMapper> builder = new HashMap<>(this.objectMappers);
342-
for (ObjectMapper objectMapper : objectMappers) {
343-
builder.put(objectMapper.fullPath(), objectMapper);
344-
if (objectMapper.nested().isNested()) {
345-
hasNestedObjects = true;
346-
}
347-
}
348-
this.objectMappers = Collections.unmodifiableMap(builder);
349-
this.fieldMappers = this.fieldMappers.copyAndAllAll(fieldMappers);
350-
351-
// finally update for the entire index
352-
mapperService.addMappers(type, objectMappers, fieldMappers);
353-
}
354-
355-
public void merge(Mapping mapping, boolean simulate, boolean updateAllTypes) {
356-
try (ReleasableLock lock = mappingWriteLock.acquire()) {
357-
mapperService.checkMappersCompatibility(type, mapping, updateAllTypes);
358-
// do the merge even if simulate == false so that we get exceptions
359-
Mapping merged = this.mapping.merge(mapping, updateAllTypes);
360-
if (simulate == false) {
361-
this.mapping = merged;
362-
Collection<ObjectMapper> objectMappers = new ArrayList<>();
363-
Collection<FieldMapper> fieldMappers = new ArrayList<>(Arrays.asList(merged.metadataMappers));
364-
MapperUtils.collect(merged.root, objectMappers, fieldMappers);
365-
addMappers(objectMappers, fieldMappers, updateAllTypes);
366-
refreshSource();
367-
}
368-
}
333+
public DocumentMapper merge(Mapping mapping, boolean updateAllTypes) {
334+
Mapping merged = this.mapping.merge(mapping, updateAllTypes);
335+
return new DocumentMapper(mapperService, merged);
369336
}
370337

371-
private void refreshSource() throws ElasticsearchGenerationException {
372-
try {
373-
mappingSource = new CompressedXContent(this, XContentType.JSON, ToXContent.EMPTY_PARAMS);
374-
} catch (Exception e) {
375-
throw new ElasticsearchGenerationException("failed to serialize source for type [" + type + "]", e);
376-
}
338+
/**
339+
* Recursively update sub field types.
340+
*/
341+
public DocumentMapper updateFieldType(Map<String, MappedFieldType> fullNameToFieldType) {
342+
Mapping updated = this.mapping.updateFieldType(fullNameToFieldType);
343+
return new DocumentMapper(mapperService, updated);
377344
}
378345

379346
public void close() {

0 commit comments

Comments
 (0)