Skip to content

Commit cd60953

Browse files
Fix duplicate strings in SearchHit serialization (#127180)
The map key is always the field name. We exploited this fact in the get results but not in search hits, leading to a lot of duplicate strings in many heap dumps. We could do much better here since the names are generally coming out of a know limited set of names, but as a first step lets at least align the get- and search-responses and non-trivial amount of bytes in a number of use-cases. Plus, having a single string instance is faster on lookup etc. and saves on CPU also.
1 parent 03fab28 commit cd60953

File tree

17 files changed

+65
-51
lines changed

17 files changed

+65
-51
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public void process(HitContext hitContext) throws IOException {
100100
IntStream slots = convertTopDocsToSlots(topDocs, pc.rootDocsBySlot);
101101
// _percolator_document_slot fields are document fields and should be under "fields" section in a hit
102102
List<Object> docSlots = slots.boxed().collect(Collectors.toList());
103-
hitContext.hit().setDocumentField(fieldName, new DocumentField(fieldName, docSlots));
103+
hitContext.hit().setDocumentField(new DocumentField(fieldName, docSlots));
104104

105105
// Add info what sub-queries of percolator query matched this each percolated document
106106
if (fetchContext.getSearchExecutionContext().hasNamedQueries()) {
@@ -120,7 +120,7 @@ public void process(HitContext hitContext) throws IOException {
120120
matchedQueries.add(match.getName());
121121
}
122122
String matchedFieldName = fieldName + "_" + docSlots.get(i) + "_matched_queries";
123-
hitContext.hit().setDocumentField(matchedFieldName, new DocumentField(matchedFieldName, matchedQueries));
123+
hitContext.hit().setDocumentField(new DocumentField(matchedFieldName, matchedQueries));
124124
}
125125
}
126126
}

server/src/internalClusterTest/java/org/elasticsearch/search/aggregations/metrics/TopHitsIT.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1327,8 +1327,7 @@ public void setNextReader(LeafReaderContext ctx) {
13271327
public void process(FetchSubPhase.HitContext hitContext) {
13281328
leafSearchLookup.setDocument(hitContext.docId());
13291329
FieldLookup fieldLookup = leafSearchLookup.fields().get("text");
1330-
hitContext.hit()
1331-
.setDocumentField("text_stored_lookup", new DocumentField("text_stored_lookup", fieldLookup.getValues()));
1330+
hitContext.hit().setDocumentField(new DocumentField("text_stored_lookup", fieldLookup.getValues()));
13321331
}
13331332

13341333
@Override

server/src/internalClusterTest/java/org/elasticsearch/search/fetch/FetchSubPhasePluginIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ private void hitExecute(FetchContext context, HitContext hitContext) throws IOEx
137137
DocumentField hitField = hitContext.hit().getFields().get(NAME);
138138
if (hitField == null) {
139139
hitField = new DocumentField(NAME, new ArrayList<>(1));
140-
hitContext.hit().setDocumentField(NAME, hitField);
140+
hitContext.hit().setDocumentField(hitField);
141141
}
142142
Terms terms = hitContext.reader().termVectors().get(hitContext.docId(), field);
143143
if (terms != null) {

server/src/main/java/org/elasticsearch/TransportVersions.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ static TransportVersion def(int id) {
233233
public static final TransportVersion SEARCH_INCREMENTAL_TOP_DOCS_NULL = def(9_058_0_00);
234234
public static final TransportVersion COMPRESS_DELAYABLE_WRITEABLE = def(9_059_0_00);
235235
public static final TransportVersion SYNONYMS_REFRESH_PARAM = def(9_060_0_00);
236+
public static final TransportVersion DOC_FIELDS_AS_LIST = def(9_061_0_00);
236237

237238
/*
238239
* STOP! READ THIS FIRST! No, really,

server/src/main/java/org/elasticsearch/common/document/DocumentField.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Collections;
2525
import java.util.Iterator;
2626
import java.util.List;
27+
import java.util.Map;
2728
import java.util.Objects;
2829

2930
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
@@ -70,6 +71,15 @@ public DocumentField(String name, List<Object> values, List<Object> ignoredValue
7071
: "DocumentField can't have both lookup fields and values";
7172
}
7273

74+
/**
75+
* Read map of document fields written via {@link StreamOutput#writeMapValues(Map)}.
76+
* @param in stream input
77+
* @return map of {@link DocumentField} keyed by {@link DocumentField#getName()}
78+
*/
79+
public static Map<String, DocumentField> readFieldsFromMapValues(StreamInput in) throws IOException {
80+
return in.readMapValues(DocumentField::new, DocumentField::getName);
81+
}
82+
7383
/**
7484
* The name of the field.
7585
*/

server/src/main/java/org/elasticsearch/index/get/GetResult.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ public GetResult(StreamInput in) throws IOException {
7474
if (source.length() == 0) {
7575
source = null;
7676
}
77-
documentFields = in.readMapValues(DocumentField::new, DocumentField::getName);
78-
metaFields = in.readMapValues(DocumentField::new, DocumentField::getName);
77+
documentFields = DocumentField.readFieldsFromMapValues(in);
78+
metaFields = DocumentField.readFieldsFromMapValues(in);
7979
} else {
8080
metaFields = Collections.emptyMap();
8181
documentFields = Collections.emptyMap();

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

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,15 @@ public static SearchHit readFrom(StreamInput in, boolean pooled) throws IOExcept
216216
if (in.readBoolean()) {
217217
explanation = readExplanation(in);
218218
}
219-
final Map<String, DocumentField> documentFields = in.readMap(DocumentField::new);
220-
final Map<String, DocumentField> metaFields = in.readMap(DocumentField::new);
219+
final Map<String, DocumentField> documentFields;
220+
final Map<String, DocumentField> metaFields;
221+
if (in.getTransportVersion().onOrAfter(TransportVersions.DOC_FIELDS_AS_LIST)) {
222+
documentFields = DocumentField.readFieldsFromMapValues(in);
223+
metaFields = DocumentField.readFieldsFromMapValues(in);
224+
} else {
225+
documentFields = in.readMap(DocumentField::new);
226+
metaFields = in.readMap(DocumentField::new);
227+
}
221228
Map<String, HighlightField> highlightFields = in.readMapValues(HighlightField::new, HighlightField::name);
222229
highlightFields = highlightFields.isEmpty() ? null : unmodifiableMap(highlightFields);
223230

@@ -322,8 +329,13 @@ public void writeTo(StreamOutput out) throws IOException {
322329
out.writeBoolean(true);
323330
writeExplanation(out, explanation);
324331
}
325-
out.writeMap(documentFields, StreamOutput::writeWriteable);
326-
out.writeMap(metaFields, StreamOutput::writeWriteable);
332+
if (out.getTransportVersion().onOrAfter(TransportVersions.DOC_FIELDS_AS_LIST)) {
333+
out.writeMapValues(documentFields);
334+
out.writeMapValues(metaFields);
335+
} else {
336+
out.writeMap(documentFields, StreamOutput::writeWriteable);
337+
out.writeMap(metaFields, StreamOutput::writeWriteable);
338+
}
327339
if (highlightFields == null) {
328340
out.writeVInt(0);
329341
} else {
@@ -502,9 +514,9 @@ public DocumentField field(String fieldName) {
502514
/*
503515
* Adds a new DocumentField to the map in case both parameters are not null.
504516
* */
505-
public void setDocumentField(String fieldName, DocumentField field) {
506-
if (fieldName == null || field == null) return;
507-
this.documentFields.put(fieldName, field);
517+
public void setDocumentField(DocumentField field) {
518+
if (field == null) return;
519+
this.documentFields.put(field.getName(), field);
508520
}
509521

510522
public void addDocumentFields(Map<String, DocumentField> docFields, Map<String, DocumentField> metaFields) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public void process(HitContext hit) throws IOException {
7676
hitField = new DocumentField(f.field, new ArrayList<>(2));
7777
// even if we request a doc values of a meta-field (e.g. _routing),
7878
// docValues fields will still be document fields, and put under "fields" section of a hit.
79-
hit.hit().setDocumentField(f.field, hitField);
79+
hit.hit().setDocumentField(hitField);
8080
}
8181
List<Object> ignoredValues = new ArrayList<>();
8282
hitField.getValues().addAll(f.fetcher.fetchValues(hit.source(), hit.docId(), ignoredValues));

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public void process(HitContext hitContext) {
7575
}
7676
hitField = new DocumentField(scriptFieldName, values);
7777
// script fields are never meta-fields
78-
hitContext.hit().setDocumentField(scriptFieldName, hitField);
78+
hitContext.hit().setDocumentField(hitField);
7979
}
8080
}
8181
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,7 @@ public void process(HitContext hitContext) {
9595
Map<String, List<Object>> loadedFields = hitContext.loadedFields();
9696
for (StoredField storedField : storedFields) {
9797
if (storedField.hasValue(loadedFields)) {
98-
hitContext.hit()
99-
.setDocumentField(storedField.name, new DocumentField(storedField.name, storedField.process(loadedFields)));
98+
hitContext.hit().setDocumentField(new DocumentField(storedField.name, storedField.process(loadedFields)));
10099
}
101100
}
102101
}

0 commit comments

Comments
 (0)