Skip to content

Commit 680d49c

Browse files
committed
For the fields fetch phase, avoid reloading stored fields. (#58196)
This PR updates FetchFieldsPhase to override hitExecute instead of hitsExecute (plural). This way, we can make sure that the stored fields (including _source) are only loaded once per hit as part of FetchPhase.
1 parent 88b8c4c commit 680d49c

File tree

5 files changed

+81
-32
lines changed

5 files changed

+81
-32
lines changed

rest-api-spec/src/main/resources/rest-api-spec/test/search/330_fetch_fields.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,48 @@ setup:
169169

170170
- match: { hits.hits.0.fields.integer.0: 42 }
171171
- is_false: hits.hits.1.fields.integer
172+
173+
---
174+
"Test disable _source loading":
175+
- do:
176+
indices.create:
177+
index: test
178+
body:
179+
settings:
180+
number_of_shards: 1
181+
mappings:
182+
properties:
183+
keyword:
184+
type: keyword
185+
integer:
186+
type: integer
187+
store: true
188+
189+
- do:
190+
index:
191+
index: test
192+
id: 1
193+
refresh: true
194+
body:
195+
keyword: "x"
196+
integer: 42
197+
198+
- do:
199+
search:
200+
index: test
201+
body:
202+
fields: [ keyword ]
203+
_source: false
204+
205+
- match: { hits.hits.0.fields.keyword.0: "x" }
206+
207+
- do:
208+
search:
209+
index: test
210+
body:
211+
fields: [ keyword ]
212+
stored_fields: [ integer ]
213+
_source: false
214+
215+
- match: { hits.hits.0.fields.keyword.0: "x" }
216+
- match: { hits.hits.0.fields.integer.0: 42 }

server/src/main/java/org/elasticsearch/search/SearchService.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -937,7 +937,10 @@ private void parseSource(DefaultSearchContext context, SearchSourceBuilder sourc
937937
context.docValuesContext(new FetchDocValuesContext(docValueFields));
938938
}
939939
if (source.fetchFields() != null) {
940-
context.fetchFieldsContext(new FetchFieldsContext(source.fetchFields()));
940+
String indexName = context.indexShard().shardId().getIndexName();
941+
FetchFieldsContext fetchFieldsContext = FetchFieldsContext.create(
942+
indexName, context.mapperService(), source.fetchFields());
943+
context.fetchFieldsContext(fetchFieldsContext);
941944
}
942945
if (source.highlighter() != null) {
943946
HighlightBuilder highlightBuilder = source.highlighter();

server/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ public void execute(SearchContext context) {
102102
if (!context.hasScriptFields() && !context.hasFetchSourceContext()) {
103103
context.fetchSourceContext(new FetchSourceContext(true));
104104
}
105-
fieldsVisitor = new FieldsVisitor(context.sourceRequested());
105+
boolean loadSource = context.sourceRequested() || context.fetchFieldsContext() != null;
106+
fieldsVisitor = new FieldsVisitor(loadSource);
106107
} else if (storedFieldsContext.fetchFields() == false) {
107108
// disable stored fields entirely
108109
fieldsVisitor = null;
@@ -131,7 +132,7 @@ public void execute(SearchContext context) {
131132
}
132133
}
133134
}
134-
boolean loadSource = context.sourceRequested();
135+
boolean loadSource = context.sourceRequested() || context.fetchFieldsContext() != null;
135136
if (storedToRequestedFields.isEmpty()) {
136137
// empty list specified, default to disable _source if no explicit indication
137138
fieldsVisitor = new FieldsVisitor(loadSource);

server/src/main/java/org/elasticsearch/search/fetch/subphase/FetchFieldsContext.java

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,36 @@
1818
*/
1919
package org.elasticsearch.search.fetch.subphase;
2020

21+
import org.elasticsearch.index.mapper.DocumentMapper;
22+
import org.elasticsearch.index.mapper.MapperService;
23+
2124
import java.util.List;
2225

2326
/**
2427
* The context needed to retrieve fields.
2528
*/
2629
public class FetchFieldsContext {
2730

28-
private final List<FieldAndFormat> fields;
31+
private FieldValueRetriever fieldValueRetriever;
32+
33+
public static FetchFieldsContext create(String indexName,
34+
MapperService mapperService,
35+
List<FieldAndFormat> fields) {
36+
DocumentMapper documentMapper = mapperService.documentMapper();
37+
if (documentMapper.sourceMapper().enabled() == false) {
38+
throw new IllegalArgumentException("Unable to retrieve the requested [fields] since _source is " +
39+
"disabled in the mappings for index [" + indexName + "]");
40+
}
41+
42+
FieldValueRetriever fieldValueRetriever = FieldValueRetriever.create(mapperService, fields);
43+
return new FetchFieldsContext(fieldValueRetriever);
44+
}
2945

30-
public FetchFieldsContext(List<FieldAndFormat> fields) {
31-
this.fields = fields;
46+
private FetchFieldsContext(FieldValueRetriever fieldValueRetriever) {
47+
this.fieldValueRetriever = fieldValueRetriever;
3248
}
3349

34-
public List<FieldAndFormat> fields() {
35-
return this.fields;
50+
public FieldValueRetriever fieldValueRetriever() {
51+
return fieldValueRetriever;
3652
}
3753
}

server/src/main/java/org/elasticsearch/search/fetch/subphase/FetchFieldsPhase.java

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,7 @@
1919

2020
package org.elasticsearch.search.fetch.subphase;
2121

22-
import org.apache.lucene.index.LeafReaderContext;
23-
import org.apache.lucene.index.ReaderUtil;
2422
import org.elasticsearch.common.document.DocumentField;
25-
import org.elasticsearch.index.mapper.DocumentMapper;
2623
import org.elasticsearch.index.mapper.IgnoredFieldMapper;
2724
import org.elasticsearch.search.SearchHit;
2825
import org.elasticsearch.search.fetch.FetchSubPhase;
@@ -40,33 +37,20 @@
4037
public final class FetchFieldsPhase implements FetchSubPhase {
4138

4239
@Override
43-
public void hitsExecute(SearchContext context, SearchHit[] hits) {
40+
public void hitExecute(SearchContext context, HitContext hitContext) {
4441
FetchFieldsContext fetchFieldsContext = context.fetchFieldsContext();
45-
if (fetchFieldsContext == null || fetchFieldsContext.fields().isEmpty()) {
42+
if (fetchFieldsContext == null) {
4643
return;
4744
}
4845

49-
DocumentMapper documentMapper = context.mapperService().documentMapper();
50-
if (documentMapper.sourceMapper().enabled() == false) {
51-
throw new IllegalArgumentException("Unable to retrieve the requested [fields] since _source is " +
52-
"disabled in the mappings for index [" + context.indexShard().shardId().getIndexName() + "]");
53-
}
54-
46+
SearchHit hit = hitContext.hit();
5547
SourceLookup sourceLookup = context.lookup().source();
56-
FieldValueRetriever fieldValueRetriever = FieldValueRetriever.create(
57-
context.mapperService(),
58-
fetchFieldsContext.fields());
59-
60-
for (SearchHit hit : hits) {
61-
int readerIndex = ReaderUtil.subIndex(hit.docId(), context.searcher().getIndexReader().leaves());
62-
LeafReaderContext readerContext = context.searcher().getIndexReader().leaves().get(readerIndex);
63-
sourceLookup.setSegmentAndDocument(readerContext, hit.docId());
48+
FieldValueRetriever fieldValueRetriever = fetchFieldsContext.fieldValueRetriever();
6449

65-
Set<String> ignoredFields = getIgnoredFields(hit);
66-
Map<String, DocumentField> documentFields = fieldValueRetriever.retrieve(sourceLookup, ignoredFields);
67-
for (Map.Entry<String, DocumentField> entry : documentFields.entrySet()) {
68-
hit.setDocumentField(entry.getKey(), entry.getValue());
69-
}
50+
Set<String> ignoredFields = getIgnoredFields(hit);
51+
Map<String, DocumentField> documentFields = fieldValueRetriever.retrieve(sourceLookup, ignoredFields);
52+
for (Map.Entry<String, DocumentField> entry : documentFields.entrySet()) {
53+
hit.setDocumentField(entry.getKey(), entry.getValue());
7054
}
7155
}
7256

0 commit comments

Comments
 (0)