Skip to content

Commit 3e31524

Browse files
authored
Bust the request cache when the mapping changes (elastic#66295)
This makes sure that we only serve a hit from the request cache if it was build using the same mapping and that the same mapping is used for the entire "query phase" of the search. Closes elastic#62033
1 parent 11153fc commit 3e31524

File tree

47 files changed

+1136
-537
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

+1136
-537
lines changed

docs/reference/indices/apis/reload-analyzers.asciidoc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,17 @@ stream's backing indices.
1313
[source,console]
1414
--------------------------------------------------
1515
POST /my-index-000001/_reload_search_analyzers
16+
POST /my-index-000001/_cache/clear?request=true
1617
--------------------------------------------------
1718
// TEST[setup:my_index]
1819

20+
IMPORTANT: After reloading the search analyzers you should clear the request
21+
cache to make sure it doesn't contain responses derived from the
22+
previous versions of the analyzer.
23+
// the need for this is tracked in https://github.com/elastic/elasticsearch/issues/66722
24+
25+
26+
1927
[discrete]
2028
[[indices-reload-analyzers-api-request]]
2129
=== {api-request-title}

docs/reference/modules/indices/request_cache.asciidoc

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@ Scripted queries that use the API calls which are non-deterministic, such as
3232
The cache is smart -- it keeps the same _near real-time_ promise as uncached
3333
search.
3434

35-
Cached results are invalidated automatically whenever the shard refreshes, but
36-
only if the data in the shard has actually changed. In other words, you will
37-
always get the same results from the cache as you would for an uncached search
38-
request.
35+
Cached results are invalidated automatically whenever the shard refreshes to
36+
pick up changes to the documents or when you update the mapping. In other
37+
words you will always get the same results from the cache as you would for an
38+
uncached search request.
3939

4040
The longer the refresh interval, the longer that cached entries will remain
41-
valid. If the cache is full, the least recently used cache keys will be
42-
evicted.
41+
valid even if there are changes to the documents. If the cache is full, the
42+
least recently used cache keys will be evicted.
4343

4444
The cache can be expired manually with the <<indices-clearcache,`clear-cache` API>>:
4545

rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/220_filters_bucket.yml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,3 +272,72 @@ setup:
272272
the_filter:
273273
filters:
274274
filters: []
275+
276+
277+
---
278+
"cache":
279+
- skip:
280+
version: " - 7.99.99"
281+
reason: cache fixed in 8.0.0 to be backported to 7.11.0
282+
283+
- do:
284+
bulk:
285+
refresh: true
286+
body:
287+
- index:
288+
_index: test_1
289+
_id: 100
290+
- int_field: 1
291+
double_field: 1.0
292+
string_field: foo bar
293+
294+
- do:
295+
search:
296+
index: test_1
297+
body:
298+
size: 0
299+
aggs:
300+
f:
301+
filters:
302+
filters:
303+
foo:
304+
match:
305+
string_field: foo
306+
foo_bar:
307+
match:
308+
string_field: foo bar
309+
- match: { hits.total.value: 5 }
310+
- length: { aggregations.f.buckets: 2 }
311+
- match: { aggregations.f.buckets.foo.doc_count: 4 }
312+
- match: { aggregations.f.buckets.foo_bar.doc_count: 1 }
313+
314+
# Modify the mapping configuration that generates queries. This should bust the cache.
315+
- do:
316+
indices.put_mapping:
317+
index: test_1
318+
body:
319+
properties:
320+
string_field:
321+
type: keyword
322+
split_queries_on_whitespace: true
323+
324+
# This should be entirely fresh because updating the mapping busted the cache.
325+
- do:
326+
search:
327+
index: test_1
328+
body:
329+
size: 0
330+
aggs:
331+
f:
332+
filters:
333+
filters:
334+
foo:
335+
match:
336+
string_field: foo
337+
foo_bar:
338+
match:
339+
string_field: foo bar
340+
- match: { hits.total.value: 5 }
341+
- length: { aggregations.f.buckets: 2 }
342+
- match: { aggregations.f.buckets.foo.doc_count: 4 }
343+
- match: { aggregations.f.buckets.foo_bar.doc_count: 4 }

server/src/main/java/org/elasticsearch/index/IndexService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,7 @@ public QueryShardContext newQueryShardContext(
612612
indexCache.bitsetFilterCache(),
613613
indexFieldData::getForField,
614614
mapperService(),
615+
mapperService().mappingLookup(),
615616
similarityService(),
616617
scriptService,
617618
xContentRegistry,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ private DocumentMapper(IndexSettings indexSettings,
115115
this.documentParser = documentParser;
116116
this.indexSettings = indexSettings;
117117
this.indexAnalyzers = indexAnalyzers;
118-
this.fieldMappers = MappingLookup.fromMapping(this.mapping);
118+
this.fieldMappers = MappingLookup.fromMapping(mapping, this::parse);
119119

120120
try {
121121
mappingSource = new CompressedXContent(this, XContentType.JSON, ToXContent.EMPTY_PARAMS);

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

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.elasticsearch.common.regex.Regex;
2323

2424
import java.util.Collection;
25+
import java.util.Collections;
2526
import java.util.HashMap;
2627
import java.util.HashSet;
2728
import java.util.Map;
@@ -43,11 +44,16 @@ final class FieldTypeLookup {
4344
* For convenience, the set of copied fields includes the field itself.
4445
*/
4546
private final Map<String, Set<String>> fieldToCopiedFields = new HashMap<>();
47+
private final String type;
4648
private final DynamicKeyFieldTypeLookup dynamicKeyLookup;
4749

48-
FieldTypeLookup(Collection<FieldMapper> fieldMappers,
49-
Collection<FieldAliasMapper> fieldAliasMappers,
50-
Collection<RuntimeFieldType> runtimeFieldTypes) {
50+
FieldTypeLookup(
51+
String type,
52+
Collection<FieldMapper> fieldMappers,
53+
Collection<FieldAliasMapper> fieldAliasMappers,
54+
Collection<RuntimeFieldType> runtimeFieldTypes
55+
) {
56+
this.type = type;
5157
Map<String, DynamicKeyFieldMapper> dynamicKeyMappers = new HashMap<>();
5258

5359
for (FieldMapper fieldMapper : fieldMappers) {
@@ -89,6 +95,10 @@ final class FieldTypeLookup {
8995
* Returns the mapped field type for the given field name.
9096
*/
9197
MappedFieldType get(String field) {
98+
if (field.equals(TypeFieldType.NAME)) {
99+
return new TypeFieldType(type);
100+
}
101+
92102
MappedFieldType fieldType = fullNameToFieldType.get(field);
93103
if (fieldType != null) {
94104
return fieldType;
@@ -103,6 +113,10 @@ MappedFieldType get(String field) {
103113
* Returns a list of the full names of a simple match regex like pattern against full name and index name.
104114
*/
105115
Set<String> simpleMatchToFullName(String pattern) {
116+
if (Regex.isSimpleMatchPattern(pattern) == false) {
117+
// no wildcards
118+
return Collections.singleton(pattern);
119+
}
106120
Set<String> fields = new HashSet<>();
107121
for (String field : fullNameToFieldType.keySet()) {
108122
if (Regex.simpleMatch(pattern, field)) {
@@ -125,6 +139,9 @@ Set<String> simpleMatchToFullName(String pattern) {
125139
* @return A set of paths in the _source that contain the field's values.
126140
*/
127141
Set<String> sourcePaths(String field) {
142+
if (fullNameToFieldType.isEmpty()) {
143+
return Set.of();
144+
}
128145
String resolvedField = field;
129146
int lastDotIndex = field.lastIndexOf('.');
130147
if (lastDotIndex > 0) {

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

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import org.elasticsearch.cluster.metadata.MappingMetadata;
2727
import org.elasticsearch.common.Strings;
2828
import org.elasticsearch.common.compress.CompressedXContent;
29-
import org.elasticsearch.common.regex.Regex;
3029
import org.elasticsearch.common.settings.Setting;
3130
import org.elasticsearch.common.settings.Setting.Property;
3231
import org.elasticsearch.common.settings.Settings;
@@ -132,7 +131,7 @@ public MapperService(IndexSettings indexSettings, IndexAnalyzers indexAnalyzers,
132131
}
133132

134133
public boolean hasNested() {
135-
return this.mapper != null && this.mapper.hasNestedObjects();
134+
return mappingLookup().hasNested();
136135
}
137136

138137
public IndexAnalyzers getIndexAnalyzers() {
@@ -399,30 +398,23 @@ public DocumentMapperForType documentMapperWithAutoCreate() {
399398
* Given the full name of a field, returns its {@link MappedFieldType}.
400399
*/
401400
public MappedFieldType fieldType(String fullName) {
402-
if (fullName.equals(TypeFieldType.NAME)) {
403-
return new TypeFieldType(this.mapper == null ? "_doc" : this.mapper.type());
404-
}
405-
return this.mapper == null ? null : this.mapper.mappers().fieldTypes().get(fullName);
401+
return mappingLookup().fieldTypes().get(fullName);
406402
}
407403

408404
/**
409405
* Returns all the fields that match the given pattern. If the pattern is prefixed with a type
410406
* then the fields will be returned with a type prefix.
411407
*/
412408
public Set<String> simpleMatchToFullName(String pattern) {
413-
if (Regex.isSimpleMatchPattern(pattern) == false) {
414-
// no wildcards
415-
return Collections.singleton(pattern);
416-
}
417-
return this.mapper == null ? Collections.emptySet() : this.mapper.mappers().fieldTypes().simpleMatchToFullName(pattern);
409+
return mappingLookup().simpleMatchToFullName(pattern);
418410
}
419411

420412
/**
421-
* Given a field name, returns its possible paths in the _source. For example,
422-
* the 'source path' for a multi-field is the path to its parent field.
413+
* {@code volatile} read a (mostly) immutable snapshot current mapping.
423414
*/
424-
public Set<String> sourcePath(String fullName) {
425-
return this.mapper == null ? Collections.emptySet() : this.mapper.mappers().fieldTypes().sourcePaths(fullName);
415+
public MappingLookup mappingLookup() {
416+
DocumentMapper mapper = this.mapper;
417+
return mapper == null ? MappingLookup.EMPTY : mapper.mappers();
426418
}
427419

428420
/**
@@ -444,18 +436,7 @@ public ObjectMapper getObjectMapper(String name) {
444436
* directly associated index-time analyzer
445437
*/
446438
public NamedAnalyzer indexAnalyzer(String field, Function<String, NamedAnalyzer> unindexedFieldAnalyzer) {
447-
if (this.mapper == null) {
448-
return unindexedFieldAnalyzer.apply(field);
449-
}
450-
return this.mapper.mappers().indexAnalyzer(field, unindexedFieldAnalyzer);
451-
}
452-
453-
public boolean containsBrokenAnalysis(String field) {
454-
NamedAnalyzer a = indexAnalyzer(field, f -> null);
455-
if (a == null) {
456-
return false;
457-
}
458-
return a.containsBrokenAnalysis();
439+
return mappingLookup().indexAnalyzer(field, unindexedFieldAnalyzer);
459440
}
460441

461442
@Override
@@ -504,6 +485,7 @@ public synchronized List<String> reloadSearchAnalyzers(AnalysisRegistry registry
504485
reloadedAnalyzers.add(analyzerName);
505486
}
506487
}
488+
// TODO this should bust the cache somehow. Tracked in https://github.com/elastic/elasticsearch/issues/66722
507489
return reloadedAnalyzers;
508490
}
509491
}

0 commit comments

Comments
 (0)