Skip to content

Commit 2ebfe7d

Browse files
authored
Bust the request cache when the mapping changes (backport of #66295) (#66795)
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 #62033
1 parent a4e60a7 commit 2ebfe7d

File tree

53 files changed

+1267
-563
lines changed

Some content is hidden

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

53 files changed

+1267
-563
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

modules/percolator/src/main/java/org/elasticsearch/percolator/PercolateQueryBuilder.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -544,8 +544,7 @@ protected Query doToQuery(QueryShardContext context) throws IOException {
544544
}
545545
}
546546
for (BytesReference document : documents) {
547-
docs.add(context.parseDocument(type,
548-
new SourceToParse(context.index().getName(), type, "_temp_id", document, documentXContentType)));
547+
docs.add(context.parseDocument(new SourceToParse(context.index().getName(), type, "_temp_id", document, documentXContentType)));
549548
}
550549

551550
// We need this custom analyzer because the default index analyzer is strict and the percolator sometimes isn't when

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.11.99"
281+
reason: cache fixed in 7.12.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 }
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
type query:
2+
- do:
3+
index:
4+
index: test1
5+
id: 1
6+
type: cat
7+
refresh: true
8+
body:
9+
foo: bar
10+
11+
- do:
12+
index:
13+
index: test2
14+
id: 1
15+
type: _doc
16+
refresh: true
17+
body:
18+
foo: bar
19+
20+
- do:
21+
search:
22+
rest_total_hits_as_int: true
23+
index: test1
24+
body:
25+
query:
26+
type:
27+
value: cat
28+
- match: { hits.total: 1 }
29+
30+
- do:
31+
search:
32+
rest_total_hits_as_int: true
33+
index: test1
34+
body:
35+
query:
36+
type:
37+
value: dog
38+
- match: { hits.total: 0 }
39+
40+
- do:
41+
search:
42+
rest_total_hits_as_int: true
43+
index: test1
44+
body:
45+
query:
46+
type:
47+
value: _doc
48+
- match: { hits.total: 0 }
49+
50+
- do:
51+
search:
52+
rest_total_hits_as_int: true
53+
index: test1
54+
body:
55+
query:
56+
type:
57+
value: _default_
58+
- match: { hits.total: 0 }
59+
60+
- do:
61+
search:
62+
rest_total_hits_as_int: true
63+
index: test2
64+
body:
65+
query:
66+
type:
67+
value: _doc
68+
- match: { hits.total: 1 }
69+
70+
- do:
71+
search:
72+
rest_total_hits_as_int: true
73+
index: test2
74+
body:
75+
query:
76+
type:
77+
value: dog
78+
- match: { hits.total: 0 }
79+
80+
- do:
81+
search:
82+
rest_total_hits_as_int: true
83+
index: test2
84+
body:
85+
query:
86+
type:
87+
value: _default_
88+
- match: { hits.total: 0 }

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,7 @@ public QueryShardContext newQueryShardContext(
614614
indexCache.bitsetFilterCache(),
615615
indexFieldData::getForField,
616616
mapperService(),
617+
mapperService().mappingLookup(),
617618
similarityService(),
618619
scriptService,
619620
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
@@ -116,7 +116,7 @@ private DocumentMapper(IndexSettings indexSettings,
116116
this.documentParser = documentParser;
117117
this.indexSettings = indexSettings;
118118
this.indexAnalyzers = indexAnalyzers;
119-
this.fieldMappers = MappingLookup.fromMapping(this.mapping);
119+
this.fieldMappers = MappingLookup.fromMapping(mapping, this::parse);
120120

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

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

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

2222
import org.elasticsearch.common.regex.Regex;
23+
import org.elasticsearch.index.mapper.TypeFieldMapper.TypeFieldType;
2324

2425
import java.util.Collection;
26+
import java.util.Collections;
2527
import java.util.HashMap;
2628
import java.util.HashSet;
2729
import java.util.Map;
@@ -43,11 +45,16 @@ final class FieldTypeLookup {
4345
* For convenience, the set of copied fields includes the field itself.
4446
*/
4547
private final Map<String, Set<String>> fieldToCopiedFields = new HashMap<>();
48+
private final String type;
4649
private final DynamicKeyFieldTypeLookup dynamicKeyLookup;
4750

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

5360
for (FieldMapper fieldMapper : fieldMappers) {
@@ -89,6 +96,10 @@ final class FieldTypeLookup {
8996
* Returns the mapped field type for the given field name.
9097
*/
9198
MappedFieldType get(String field) {
99+
if (field.equals(TypeFieldMapper.NAME)) {
100+
return new TypeFieldType(type);
101+
}
102+
92103
MappedFieldType fieldType = fullNameToFieldType.get(field);
93104
if (fieldType != null) {
94105
return fieldType;
@@ -103,6 +114,10 @@ MappedFieldType get(String field) {
103114
* Returns a list of the full names of a simple match regex like pattern against full name and index name.
104115
*/
105116
Set<String> simpleMatchToFullName(String pattern) {
117+
if (Regex.isSimpleMatchPattern(pattern) == false) {
118+
// no wildcards
119+
return Collections.singleton(pattern);
120+
}
106121
Set<String> fields = new HashSet<>();
107122
for (String field : fullNameToFieldType.keySet()) {
108123
if (Regex.simpleMatch(pattern, field)) {
@@ -125,6 +140,9 @@ Set<String> simpleMatchToFullName(String pattern) {
125140
* @return A set of paths in the _source that contain the field's values.
126141
*/
127142
Set<String> sourcePaths(String field) {
143+
if (fullNameToFieldType.isEmpty()) {
144+
return org.elasticsearch.common.collect.Set.of();
145+
}
128146
String resolvedField = field;
129147
int lastDotIndex = field.lastIndexOf('.');
130148
if (lastDotIndex > 0) {
@@ -149,4 +167,8 @@ Iterable<MappedFieldType> filter(Predicate<MappedFieldType> predicate) {
149167
return () -> Stream.concat(fullNameToFieldType.values().stream(), dynamicKeyLookup.fieldTypes())
150168
.distinct().filter(predicate).iterator();
151169
}
170+
171+
public String getType() {
172+
return type;
173+
}
152174
}

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

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.elasticsearch.index.mapper;
2121

2222
import com.carrotsearch.hppc.cursors.ObjectCursor;
23+
2324
import org.apache.logging.log4j.message.ParameterizedMessage;
2425
import org.elasticsearch.Assertions;
2526
import org.elasticsearch.Version;
@@ -29,7 +30,6 @@
2930
import org.elasticsearch.common.Strings;
3031
import org.elasticsearch.common.compress.CompressedXContent;
3132
import org.elasticsearch.common.logging.DeprecationLogger;
32-
import org.elasticsearch.common.regex.Regex;
3333
import org.elasticsearch.common.settings.Setting;
3434
import org.elasticsearch.common.settings.Setting.Property;
3535
import org.elasticsearch.common.settings.Settings;
@@ -165,7 +165,7 @@ public MapperService(IndexSettings indexSettings, IndexAnalyzers indexAnalyzers,
165165
}
166166

167167
public boolean hasNested() {
168-
return this.mapper != null && this.mapper.hasNestedObjects();
168+
return mappingLookup().hasNested();
169169
}
170170

171171
public IndexAnalyzers getIndexAnalyzers() {
@@ -598,31 +598,23 @@ public DocumentMapperForType documentMapperWithAutoCreate(String type) {
598598
* Given the full name of a field, returns its {@link MappedFieldType}.
599599
*/
600600
public MappedFieldType fieldType(String fullName) {
601-
if (fullName.equals(TypeFieldMapper.NAME)) {
602-
String type = mapper == null ? null : mapper.type();
603-
return new TypeFieldMapper.TypeFieldType(type);
604-
}
605-
return this.mapper == null ? null : this.mapper.mappers().fieldTypes().get(fullName);
601+
return mappingLookup().fieldTypes().get(fullName);
606602
}
607603

608604
/**
609605
* Returns all the fields that match the given pattern. If the pattern is prefixed with a type
610606
* then the fields will be returned with a type prefix.
611607
*/
612608
public Set<String> simpleMatchToFullName(String pattern) {
613-
if (Regex.isSimpleMatchPattern(pattern) == false) {
614-
// no wildcards
615-
return Collections.singleton(pattern);
616-
}
617-
return this.mapper == null ? Collections.emptySet() : this.mapper.mappers().fieldTypes().simpleMatchToFullName(pattern);
609+
return mappingLookup().simpleMatchToFullName(pattern);
618610
}
619611

620612
/**
621-
* Given a field name, returns its possible paths in the _source. For example,
622-
* the 'source path' for a multi-field is the path to its parent field.
613+
* {@code volatile} read a (mostly) immutable snapshot current mapping.
623614
*/
624-
public Set<String> sourcePath(String fullName) {
625-
return this.mapper == null ? Collections.emptySet() : this.mapper.mappers().fieldTypes().sourcePaths(fullName);
615+
public MappingLookup mappingLookup() {
616+
DocumentMapper mapper = this.mapper;
617+
return mapper == null ? MappingLookup.EMPTY : mapper.mappers();
626618
}
627619

628620
/**
@@ -644,18 +636,7 @@ public ObjectMapper getObjectMapper(String name) {
644636
* directly associated index-time analyzer
645637
*/
646638
public NamedAnalyzer indexAnalyzer(String field, Function<String, NamedAnalyzer> unindexedFieldAnalyzer) {
647-
if (this.mapper == null) {
648-
return unindexedFieldAnalyzer.apply(field);
649-
}
650-
return this.mapper.mappers().indexAnalyzer(field, unindexedFieldAnalyzer);
651-
}
652-
653-
public boolean containsBrokenAnalysis(String field) {
654-
NamedAnalyzer a = indexAnalyzer(field, f -> null);
655-
if (a == null) {
656-
return false;
657-
}
658-
return a.containsBrokenAnalysis();
639+
return mappingLookup().indexAnalyzer(field, unindexedFieldAnalyzer);
659640
}
660641

661642
@Override
@@ -688,6 +669,7 @@ public synchronized List<String> reloadSearchAnalyzers(AnalysisRegistry registry
688669
reloadedAnalyzers.add(analyzerName);
689670
}
690671
}
672+
// TODO this should bust the cache somehow. Tracked in https://github.com/elastic/elasticsearch/issues/66722
691673
return reloadedAnalyzers;
692674
}
693675
}

0 commit comments

Comments
 (0)