Skip to content

Commit 6e60c6e

Browse files
committed
Add unit test for the tracking memory in the fetch phase
1 parent 41b74cb commit 6e60c6e

File tree

2 files changed

+78
-2
lines changed

2 files changed

+78
-2
lines changed

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,7 @@ protected SearchHit nextDoc(int doc) throws IOException {
197197

198198
BytesReference sourceRef = hit.hit().getSourceRef();
199199
if (sourceRef != null) {
200-
int sourceLength = sourceRef.length();
201-
this.accumulatedBytesInLeaf += sourceLength;
200+
this.accumulatedBytesInLeaf += sourceRef.length();
202201
}
203202
success = true;
204203
return hit.hit();

server/src/test/java/org/elasticsearch/action/search/FetchSearchPhaseTests.java

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
package org.elasticsearch.action.search;
1010

1111
import org.apache.lucene.document.Document;
12+
import org.apache.lucene.document.Field;
13+
import org.apache.lucene.document.StoredField;
14+
import org.apache.lucene.document.StringField;
1215
import org.apache.lucene.index.IndexReader;
1316
import org.apache.lucene.index.LeafReaderContext;
1417
import org.apache.lucene.search.Query;
@@ -20,16 +23,19 @@
2023
import org.apache.lucene.tests.index.RandomIndexWriter;
2124
import org.apache.lucene.tests.store.MockDirectoryWrapper;
2225
import org.apache.lucene.util.Accountable;
26+
import org.apache.lucene.util.BytesRef;
2327
import org.elasticsearch.action.ActionListener;
2428
import org.elasticsearch.action.OriginalIndices;
2529
import org.elasticsearch.cluster.metadata.IndexMetadata;
2630
import org.elasticsearch.common.UUIDs;
2731
import org.elasticsearch.common.breaker.CircuitBreaker;
32+
import org.elasticsearch.common.breaker.CircuitBreakingException;
2833
import org.elasticsearch.common.breaker.NoopCircuitBreaker;
2934
import org.elasticsearch.common.lucene.search.TopDocsAndMaxScore;
3035
import org.elasticsearch.common.settings.Settings;
3136
import org.elasticsearch.common.util.concurrent.AtomicArray;
3237
import org.elasticsearch.common.util.concurrent.EsExecutors;
38+
import org.elasticsearch.core.Nullable;
3339
import org.elasticsearch.index.IndexSettings;
3440
import org.elasticsearch.index.IndexVersion;
3541
import org.elasticsearch.index.cache.bitset.BitsetFilterCache;
@@ -55,6 +61,7 @@
5561
import org.elasticsearch.search.internal.SearchContext;
5662
import org.elasticsearch.search.internal.ShardSearchContextId;
5763
import org.elasticsearch.search.internal.ShardSearchRequest;
64+
import org.elasticsearch.search.lookup.Source;
5865
import org.elasticsearch.search.profile.ProfileResult;
5966
import org.elasticsearch.search.profile.SearchProfileQueryPhaseResult;
6067
import org.elasticsearch.search.profile.SearchProfileShardResult;
@@ -72,10 +79,12 @@
7279
import java.util.concurrent.CountDownLatch;
7380
import java.util.concurrent.atomic.AtomicInteger;
7481
import java.util.function.BiFunction;
82+
import java.util.stream.IntStream;
7583

7684
import static org.hamcrest.Matchers.arrayWithSize;
7785
import static org.hamcrest.Matchers.equalTo;
7886
import static org.hamcrest.Matchers.hasSize;
87+
import static org.hamcrest.Matchers.is;
7988
import static org.hamcrest.Matchers.nullValue;
8089

8190
public class FetchSearchPhaseTests extends ESTestCase {
@@ -820,6 +829,57 @@ public void testFetchTimeoutNoPartialResults() throws IOException {
820829
}
821830
}
822831

832+
public void testFetchPhaseChecksMemoryBreaker() throws IOException {
833+
Directory dir = newDirectory();
834+
RandomIndexWriter w = new RandomIndexWriter(random(), dir);
835+
836+
// we're indexing 100 documents with a field that is 48KB long so the fetch phase should check the memory breaker 5 times
837+
// (every 22 documents that accumulate 1MiB in source sizes, and then a final time when we finished processing the one segment)
838+
839+
String body = "{ \"thefield\": \" " + randomAlphaOfLength(48_000) + "\" }";
840+
for (int i = 0; i < 100; i++) {
841+
Document document = new Document();
842+
document.add(new StringField("id", Integer.toString(i), Field.Store.YES));
843+
document.add(new StoredField("_source", new BytesRef(body)));
844+
w.addDocument(document);
845+
}
846+
w.forceMerge(1);
847+
IndexReader r = w.getReader();
848+
w.close();
849+
ContextIndexSearcher contextIndexSearcher = createSearcher(r);
850+
AtomicInteger breakerCalledCount = new AtomicInteger(0);
851+
NoopCircuitBreaker breakingCircuitBreaker = new NoopCircuitBreaker(CircuitBreaker.REQUEST) {
852+
@Override
853+
public void addEstimateBytesAndMaybeBreak(long bytes, String label) throws CircuitBreakingException {
854+
breakerCalledCount.incrementAndGet();
855+
}
856+
};
857+
try (SearchContext searchContext = createSearchContext(contextIndexSearcher, true, breakingCircuitBreaker)) {
858+
FetchPhase fetchPhase = new FetchPhase(List.of(fetchContext -> new FetchSubPhaseProcessor() {
859+
@Override
860+
public void setNextReader(LeafReaderContext readerContext) throws IOException {
861+
862+
}
863+
864+
@Override
865+
public void process(FetchSubPhase.HitContext hitContext) throws IOException {
866+
Source source = hitContext.source();
867+
hitContext.hit().sourceRef(source.internalSourceRef());
868+
}
869+
870+
@Override
871+
public StoredFieldsSpec storedFieldsSpec() {
872+
return StoredFieldsSpec.NEEDS_SOURCE;
873+
}
874+
}));
875+
fetchPhase.execute(searchContext, IntStream.range(0, 100).toArray(), null);
876+
assertThat(breakerCalledCount.get(), is(5));
877+
} finally {
878+
r.close();
879+
dir.close();
880+
}
881+
}
882+
823883
private static ContextIndexSearcher createSearcher(IndexReader reader) throws IOException {
824884
return new ContextIndexSearcher(reader, null, null, new QueryCachingPolicy() {
825885
@Override
@@ -857,6 +917,14 @@ public StoredFieldsSpec storedFieldsSpec() {
857917
}
858918

859919
private static SearchContext createSearchContext(ContextIndexSearcher contextIndexSearcher, boolean allowPartialResults) {
920+
return createSearchContext(contextIndexSearcher, allowPartialResults, null);
921+
}
922+
923+
private static SearchContext createSearchContext(
924+
ContextIndexSearcher contextIndexSearcher,
925+
boolean allowPartialResults,
926+
@Nullable CircuitBreaker circuitBreaker
927+
) {
860928
IndexSettings indexSettings = new IndexSettings(
861929
IndexMetadata.builder("index")
862930
.settings(Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, IndexVersion.current()))
@@ -929,6 +997,15 @@ public FetchSearchResult fetchResult() {
929997
public ShardSearchRequest request() {
930998
return request;
931999
}
1000+
1001+
@Override
1002+
public CircuitBreaker circuitBreaker() {
1003+
if (circuitBreaker != null) {
1004+
return circuitBreaker;
1005+
} else {
1006+
return super.circuitBreaker();
1007+
}
1008+
}
9321009
};
9331010
searchContext.addReleasable(searchContext.fetchResult()::decRef);
9341011
searchContext.setTask(new SearchShardTask(-1, "type", "action", "description", null, Collections.emptyMap()));

0 commit comments

Comments
 (0)