From e90fb0c8f70a0e1b9d0ef969d9cf9aae02068255 Mon Sep 17 00:00:00 2001 From: Armin Braun Date: Mon, 24 Feb 2025 12:00:29 +0100 Subject: [PATCH] Speedup SearchResponse serialization (#123211) No need to have a nested concat here. There's obviously lots and lots of room for optimization on this one, but just flattening out one obvious step here outright halves the number of method calls required when serializing a search response. Given that method calls can consume up to half the serialization cost this change might massively speed up some usecases. --- .../action/search/SearchResponse.java | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/search/SearchResponse.java b/server/src/main/java/org/elasticsearch/action/search/SearchResponse.java index d67d5d2faf47e..8a94f0fd15bd8 100644 --- a/server/src/main/java/org/elasticsearch/action/search/SearchResponse.java +++ b/server/src/main/java/org/elasticsearch/action/search/SearchResponse.java @@ -14,13 +14,14 @@ import org.elasticsearch.action.OriginalIndices; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.collect.Iterators; import org.elasticsearch.common.io.stream.DelayableWriteable; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.lucene.Lucene; import org.elasticsearch.common.util.concurrent.ConcurrentCollections; -import org.elasticsearch.common.xcontent.ChunkedToXContent; +import org.elasticsearch.common.xcontent.ChunkedToXContentHelper; import org.elasticsearch.common.xcontent.ChunkedToXContentObject; import org.elasticsearch.core.Nullable; import org.elasticsearch.core.RefCounted; @@ -391,17 +392,24 @@ public Clusters getClusters() { @Override public Iterator toXContentChunked(ToXContent.Params params) { assert hasReferences(); - return ChunkedToXContent.builder(params).xContentObject(innerToXContentChunked(params)); + return getToXContentIterator(true, params); } public Iterator innerToXContentChunked(ToXContent.Params params) { - return ChunkedToXContent.builder(params) - .append(SearchResponse.this::headerToXContent) - .append(clusters) - .append(hits) - .appendIfPresent(aggregations) - .appendIfPresent(suggest) - .appendIfPresent(profileResults); + return getToXContentIterator(false, params); + } + + private Iterator getToXContentIterator(boolean wrapInObject, ToXContent.Params params) { + return Iterators.concat( + wrapInObject ? ChunkedToXContentHelper.startObject() : Collections.emptyIterator(), + ChunkedToXContentHelper.singleChunk(SearchResponse.this::headerToXContent), + Iterators.single(clusters), + hits.toXContentChunked(params), + aggregations == null ? Collections.emptyIterator() : ChunkedToXContentHelper.singleChunk(aggregations), + suggest == null ? Collections.emptyIterator() : ChunkedToXContentHelper.singleChunk(suggest), + profileResults == null ? Collections.emptyIterator() : ChunkedToXContentHelper.singleChunk(profileResults), + wrapInObject ? ChunkedToXContentHelper.endObject() : Collections.emptyIterator() + ); } public XContentBuilder headerToXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {