-
Notifications
You must be signed in to change notification settings - Fork 24.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix aggregation memory leak for CCS (#78404)
When a CCS search is proxied, the memory for the aggregations on the proxy node would not be freed. Now only use the non-copying byte referencing version on the coordinating node, which itself ensures that memory is freed by calling `consumeAggs`.
- Loading branch information
1 parent
abdf8fe
commit 80792a1
Showing
7 changed files
with
205 additions
and
17 deletions.
There are no files selected for viewing
146 changes: 146 additions & 0 deletions
146
...r/src/internalClusterTest/java/org/elasticsearch/search/ccs/CrossClusterSearchLeakIT.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
package org.elasticsearch.search.ccs; | ||
|
||
import org.elasticsearch.action.ActionFuture; | ||
import org.elasticsearch.action.search.ClearScrollRequest; | ||
import org.elasticsearch.action.search.SearchRequest; | ||
import org.elasticsearch.action.search.SearchResponse; | ||
import org.elasticsearch.client.Client; | ||
import org.elasticsearch.cluster.metadata.IndexMetadata; | ||
import org.elasticsearch.cluster.node.DiscoveryNode; | ||
import org.elasticsearch.common.settings.Settings; | ||
import org.elasticsearch.core.TimeValue; | ||
import org.elasticsearch.index.query.MatchAllQueryBuilder; | ||
import org.elasticsearch.search.aggregations.bucket.terms.Terms; | ||
import org.elasticsearch.search.builder.SearchSourceBuilder; | ||
import org.elasticsearch.test.AbstractMultiClustersTestCase; | ||
import org.elasticsearch.test.InternalTestCluster; | ||
import org.elasticsearch.transport.TransportService; | ||
import org.hamcrest.Matchers; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.StreamSupport; | ||
|
||
import static org.elasticsearch.search.aggregations.AggregationBuilders.terms; | ||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; | ||
import static org.hamcrest.Matchers.equalTo; | ||
|
||
public class CrossClusterSearchLeakIT extends AbstractMultiClustersTestCase { | ||
|
||
@Override | ||
protected Collection<String> remoteClusterAlias() { | ||
return List.of("cluster_a"); | ||
} | ||
|
||
@Override | ||
protected boolean reuseClusters() { | ||
return false; | ||
} | ||
|
||
private int indexDocs(Client client, String field, String index) { | ||
int numDocs = between(1, 200); | ||
for (int i = 0; i < numDocs; i++) { | ||
client.prepareIndex(index).setSource(field, "v" + i).get(); | ||
} | ||
client.admin().indices().prepareRefresh(index).get(); | ||
return numDocs; | ||
} | ||
|
||
/** | ||
* This test validates that we do not leak any memory when running CCS in various modes, actual validation is done by test framework | ||
* (leak detection) | ||
* <ul> | ||
* <li>proxy vs non-proxy</li> | ||
* <li>single-phase query-fetch or multi-phase</li> | ||
* <li>minimize roundtrip vs not</li> | ||
* <li>scroll vs no scroll</li> | ||
* </ul> | ||
*/ | ||
public void testSearch() throws Exception { | ||
assertAcked(client(LOCAL_CLUSTER).admin().indices().prepareCreate("demo") | ||
.setMapping("f", "type=keyword") | ||
.setSettings(Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, between(1, 3)))); | ||
indexDocs(client(LOCAL_CLUSTER), "ignored", "demo"); | ||
final InternalTestCluster remoteCluster = cluster("cluster_a"); | ||
int minRemotes = between(2, 5); | ||
remoteCluster.ensureAtLeastNumDataNodes(minRemotes); | ||
List<String> remoteDataNodes = StreamSupport.stream(remoteCluster.clusterService().state().nodes().spliterator(), false) | ||
.filter(DiscoveryNode::canContainData) | ||
.map(DiscoveryNode::getName) | ||
.collect(Collectors.toList()); | ||
assertThat(remoteDataNodes.size(), Matchers.greaterThanOrEqualTo(minRemotes)); | ||
List<String> seedNodes = randomSubsetOf(between(1, remoteDataNodes.size() - 1), remoteDataNodes); | ||
disconnectFromRemoteClusters(); | ||
configureRemoteCluster("cluster_a", seedNodes); | ||
final Settings.Builder allocationFilter = Settings.builder(); | ||
if (rarely()) { | ||
allocationFilter.put("index.routing.allocation.include._name", String.join(",", seedNodes)); | ||
} else { | ||
// Provoke using proxy connections | ||
allocationFilter.put("index.routing.allocation.exclude._name", String.join(",", seedNodes)); | ||
} | ||
assertAcked(client("cluster_a").admin().indices().prepareCreate("prod") | ||
.setMapping("f", "type=keyword") | ||
.setSettings(Settings.builder().put(allocationFilter.build()) | ||
.put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0).put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, between(1, 3)))); | ||
assertFalse(client("cluster_a").admin().cluster().prepareHealth("prod") | ||
.setWaitForYellowStatus().setTimeout(TimeValue.timeValueSeconds(10)).get().isTimedOut()); | ||
int docs = indexDocs(client("cluster_a"), "f", "prod"); | ||
|
||
List<ActionFuture<SearchResponse>> futures = new ArrayList<>(); | ||
for (int i = 0; i < 10; ++i) { | ||
String[] indices = randomBoolean() ? new String[] { "demo", "cluster_a:prod" } : new String[] { "cluster_a:prod" }; | ||
final SearchRequest searchRequest = new SearchRequest(indices); | ||
searchRequest.allowPartialSearchResults(false); | ||
boolean scroll = randomBoolean(); | ||
searchRequest.source(new SearchSourceBuilder().query(new MatchAllQueryBuilder()) | ||
.aggregation(terms("f").field("f").size(docs + between(scroll ? 1 : 0, 10))).size(between(0, 1000))); | ||
if (scroll) { | ||
searchRequest.scroll("30s"); | ||
} | ||
searchRequest.setCcsMinimizeRoundtrips(rarely()); | ||
futures.add(client(LOCAL_CLUSTER).search(searchRequest)); | ||
} | ||
|
||
for (ActionFuture<SearchResponse> future : futures) { | ||
SearchResponse searchResponse = future.get(); | ||
if (searchResponse.getScrollId() != null) { | ||
ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); | ||
clearScrollRequest.scrollIds(List.of(searchResponse.getScrollId())); | ||
client(LOCAL_CLUSTER).clearScroll(clearScrollRequest).get(); | ||
} | ||
|
||
Terms terms = searchResponse.getAggregations().get("f"); | ||
assertThat(terms.getBuckets().size(), equalTo(docs)); | ||
for (Terms.Bucket bucket : terms.getBuckets()) { | ||
assertThat(bucket.getDocCount(), equalTo(1L)); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
protected void configureRemoteCluster(String clusterAlias, Collection<String> seedNodes) throws Exception { | ||
if (rarely()) { | ||
super.configureRemoteCluster(clusterAlias, seedNodes); | ||
} else { | ||
final Settings.Builder settings = Settings.builder(); | ||
final String seedNode = randomFrom(seedNodes); | ||
final TransportService transportService = cluster(clusterAlias).getInstance(TransportService.class, seedNode); | ||
final String seedAddress = transportService.boundAddress().publishAddress().toString(); | ||
|
||
settings.put("cluster.remote." + clusterAlias + ".mode", "proxy"); | ||
settings.put("cluster.remote." + clusterAlias + ".proxy_address", seedAddress); | ||
client().admin().cluster().prepareUpdateSettings().setPersistentSettings(settings).get(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters