This is an automated email from the ASF dual-hosted git repository. dsmiley pushed a commit to branch branch_9x in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/branch_9x by this push: new a9e7506c618 SOLR-17381: Fix CloudSolrClient.getClusterState when HTTP CSP (not ZK) (#2853) a9e7506c618 is described below commit a9e7506c618e392d357dd5cb636a0c8772e79fc5 Author: aparnasuresh85 <aparna.sur...@salesforce.com> AuthorDate: Mon Dec 9 11:30:41 2024 -0500 SOLR-17381: Fix CloudSolrClient.getClusterState when HTTP CSP (not ZK) (#2853) When using the HTTP ClusterStateProvider (not ZK), getClusterState() wasn't working correctly; a regression from the first PR for this JIRA issue (not released). Also, * Optimization: fix O(N^2) algorithm to be O(N) for the number of collections when calling getClusterState * liveNodes is now immutable, and probably a non-sorted ordering * removed "health" and some other keys from DocCollection that aren't present when using ZK CSP Minor details: * clearly differentiate internal ClusterState processing from getting one DocCollection * Use GenericSolrRequest not QueryRequest * test the both CSPs better --------- Co-authored-by: David Smiley <dsmi...@salesforce.com> (cherry picked from commit d2045f679e55e15cf4e974947b9417c4465fccd2) --- solr/CHANGES.txt | 4 +- .../solrj/impl/BaseHttpClusterStateProvider.java | 160 ++++++++++----------- .../solrj/impl/ClusterStateProviderTest.java | 80 ++++++++++- 3 files changed, 156 insertions(+), 88 deletions(-) diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index e332efe7bea..d948bb5801a 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -64,7 +64,9 @@ Optimizations that which consumes almost no memory, saving 1MB of memory per SolrCore. (David Smiley) * SOLR-17381: Make CLUSTERSTATUS request configurable to improve performance by allowing retrieval of specific information, - reducing unnecessary data fetching. (Aparna Suresh, David Smiley) + reducing unnecessary data fetching. Enhanced CloudSolrClient's HTTP ClusterStateProvider to use it, and to scale to + more collections better as well. + (Aparna Suresh, David Smiley) * SOLR-17396: Reduce thread contention in ZkStateReader.getCollectionProperties(). (Aparna Suresh, David Smiley, Paul McArthur) diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/BaseHttpClusterStateProvider.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/BaseHttpClusterStateProvider.java index 75c50167d2b..ed77397145d 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/BaseHttpClusterStateProvider.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/BaseHttpClusterStateProvider.java @@ -23,22 +23,23 @@ import java.io.IOException; import java.lang.invoke.MethodHandles; import java.time.Instant; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.TimeUnit; import org.apache.solr.client.solrj.SolrClient; +import org.apache.solr.client.solrj.SolrRequest.METHOD; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.request.CollectionAdminRequest; -import org.apache.solr.client.solrj.request.QueryRequest; +import org.apache.solr.client.solrj.request.GenericSolrRequest; import org.apache.solr.client.solrj.response.CollectionAdminResponse; import org.apache.solr.common.cloud.Aliases; import org.apache.solr.common.cloud.ClusterState; import org.apache.solr.common.cloud.DocCollection; import org.apache.solr.common.cloud.PerReplicaStates; import org.apache.solr.common.params.ModifiableSolrParams; +import org.apache.solr.common.util.CollectionUtil; import org.apache.solr.common.util.EnvUtils; import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.SimpleOrderedMap; @@ -98,8 +99,8 @@ public abstract class BaseHttpClusterStateProvider implements ClusterStateProvid for (String nodeName : liveNodes) { String baseUrl = Utils.getBaseUrlForNodeName(nodeName, urlScheme); try (SolrClient client = getSolrClient(baseUrl)) { - ClusterState cs = fetchClusterState(client, collection, null); - return cs.getCollectionRef(collection); + DocCollection docCollection = fetchCollectionState(client, collection); + return new ClusterState.CollectionRef(docCollection); } catch (SolrServerException | IOException e) { log.warn( "Attempt to fetch cluster state from {} failed.", @@ -128,90 +129,43 @@ public abstract class BaseHttpClusterStateProvider implements ClusterStateProvid } @SuppressWarnings("unchecked") - private ClusterState fetchClusterState( - SolrClient client, String collection, Map<String, Object> clusterProperties) + private ClusterState fetchClusterState(SolrClient client) throws SolrServerException, IOException, NotACollectionException { SimpleOrderedMap<?> cluster = - submitClusterStateRequest(client, collection, ClusterStateRequestType.FETCH_COLLECTION); - - Map<String, Object> collectionsMap; - if (collection != null) { - collectionsMap = - Collections.singletonMap( - collection, ((NamedList<?>) cluster.get("collections")).get(collection)); - } else { - collectionsMap = ((NamedList<?>) cluster.get("collections")).asMap(10); - } - int znodeVersion; - Map<String, Object> collFromStatus = (Map<String, Object>) (collectionsMap).get(collection); - if (collection != null && collFromStatus == null) { - throw new NotACollectionException(); // probably an alias - } - if (collection != null) { // can be null if alias - znodeVersion = (int) collFromStatus.get("znodeVersion"); - } else { - znodeVersion = -1; - } + submitClusterStateRequest(client, null, ClusterStateRequestType.FETCH_CLUSTER_STATE); - ClusterState cs = new ClusterState(this.liveNodes, new HashMap<>()); List<String> liveNodesList = (List<String>) cluster.get("live_nodes"); if (liveNodesList != null) { - Set<String> liveNodes = new HashSet<>(liveNodesList); - this.liveNodes = liveNodes; + this.liveNodes = Set.copyOf(liveNodesList); liveNodesTimestamp = System.nanoTime(); - cs = new ClusterState(liveNodes, new HashMap<>()); } - for (Map.Entry<String, Object> e : collectionsMap.entrySet()) { - @SuppressWarnings("rawtypes") - Map m = (Map) e.getValue(); - Long creationTimeMillisFromClusterStatus = (Long) m.get("creationTimeMillis"); - Instant creationTime = - creationTimeMillisFromClusterStatus == null - ? Instant.EPOCH - : Instant.ofEpochMilli(creationTimeMillisFromClusterStatus); - cs = cs.copyWith(e.getKey(), fillPrs(znodeVersion, e, creationTime, m)); - } + var collectionsNl = (NamedList<Map<String, Object>>) cluster.get("collections"); - if (clusterProperties != null) { - Map<String, Object> properties = (Map<String, Object>) cluster.get("properties"); - if (properties != null) { - clusterProperties.putAll(properties); - } + Map<String, DocCollection> collStateByName = + CollectionUtil.newLinkedHashMap(collectionsNl.size()); + for (Entry<String, Map<String, Object>> entry : collectionsNl) { + collStateByName.put( + entry.getKey(), getDocCollectionFromObjects(entry.getKey(), entry.getValue())); } - return cs; - } - private SimpleOrderedMap<?> submitClusterStateRequest( - SolrClient client, String collection, ClusterStateRequestType requestType) - throws SolrServerException, IOException { + return new ClusterState(this.liveNodes, collStateByName); + } - ModifiableSolrParams params = new ModifiableSolrParams(); - params.set("action", "CLUSTERSTATUS"); + @SuppressWarnings("unchecked") + private DocCollection getDocCollectionFromObjects( + String collectionName, Map<String, Object> collStateMap) { + collStateMap.remove("health"); - if (requestType == ClusterStateRequestType.FETCH_COLLECTION && collection != null) { - params.set("collection", collection); - } else if (requestType == ClusterStateRequestType.FETCH_LIVE_NODES) { - params.set("liveNodes", "true"); - } else if (requestType == ClusterStateRequestType.FETCH_CLUSTER_PROP) { - params.set("clusterProperties", "true"); - } else if (requestType == ClusterStateRequestType.FETCH_NODE_ROLES) { - params.set("roles", "true"); - } + int zNodeVersion = (int) collStateMap.remove("znodeVersion"); - params.set("includeAll", "false"); - params.set("prs", "true"); - QueryRequest request = new QueryRequest(params); - request.setPath("/admin/collections"); - return (SimpleOrderedMap<?>) client.request(request).get("cluster"); - } + Long creationTimeMillis = (Long) collStateMap.remove("creationTimeMillis"); + Instant creationTime = + creationTimeMillis == null ? Instant.EPOCH : Instant.ofEpochMilli(creationTimeMillis); - @SuppressWarnings({"rawtypes", "unchecked"}) - private DocCollection fillPrs( - int znodeVersion, Map.Entry<String, Object> e, Instant creationTime, Map m) { DocCollection.PrsSupplier prsSupplier = null; - if (m.containsKey("PRS")) { - Map prs = (Map) m.remove("PRS"); + Map<String, Object> prs = (Map<String, Object>) collStateMap.remove("PRS"); + if (prs != null) { prsSupplier = () -> new PerReplicaStates( @@ -221,7 +175,51 @@ public abstract class BaseHttpClusterStateProvider implements ClusterStateProvid } return ClusterState.collectionFromObjects( - e.getKey(), m, znodeVersion, creationTime, prsSupplier); + collectionName, collStateMap, zNodeVersion, creationTime, prsSupplier); + } + + @SuppressWarnings("unchecked") + private DocCollection fetchCollectionState(SolrClient client, String collection) + throws SolrServerException, IOException, NotACollectionException { + + SimpleOrderedMap<?> cluster = + submitClusterStateRequest(client, collection, ClusterStateRequestType.FETCH_COLLECTION); + + var collStateMap = (Map<String, Object>) cluster.findRecursive("collections", collection); + if (collStateMap == null) { + throw new NotACollectionException(); // probably an alias + } + return getDocCollectionFromObjects(collection, collStateMap); + } + + private SimpleOrderedMap<?> submitClusterStateRequest( + SolrClient client, String collection, ClusterStateRequestType requestType) + throws SolrServerException, IOException { + + ModifiableSolrParams params = new ModifiableSolrParams(); + params.set("action", "CLUSTERSTATUS"); + + params.set("includeAll", false); // will flip flor CLUSTER_STATE + switch (requestType) { + case FETCH_CLUSTER_STATE: + params.set("includeAll", true); + break; + case FETCH_COLLECTION: + if (collection != null) params.set("collection", collection); + break; + case FETCH_LIVE_NODES: + params.set("liveNodes", true); + break; + case FETCH_CLUSTER_PROP: + params.set("clusterProperties", true); + break; + case FETCH_NODE_ROLES: + params.set("roles", true); + break; + } + params.set("prs", true); + var request = new GenericSolrRequest(METHOD.GET, "/admin/collections", params); + return (SimpleOrderedMap<?>) client.request(request).get("cluster"); } @Override @@ -260,12 +258,11 @@ public abstract class BaseHttpClusterStateProvider implements ClusterStateProvid } } - @SuppressWarnings({"rawtypes", "unchecked"}) + @SuppressWarnings({"unchecked"}) private Set<String> fetchLiveNodes(SolrClient client) throws Exception { - SimpleOrderedMap<?> cluster = submitClusterStateRequest(client, null, ClusterStateRequestType.FETCH_LIVE_NODES); - return (Set<String>) new HashSet((List<String>) (cluster.get("live_nodes"))); + return Set.copyOf((List<String>) cluster.get("live_nodes")); } @Override @@ -346,8 +343,8 @@ public abstract class BaseHttpClusterStateProvider implements ClusterStateProvid for (String nodeName : liveNodes) { String baseUrl = Utils.getBaseUrlForNodeName(nodeName, urlScheme); try (SolrClient client = getSolrClient(baseUrl)) { - return fetchClusterState(client, null, null); - } catch (SolrServerException | BaseHttpSolrClient.RemoteSolrException | IOException e) { + return fetchClusterState(client); + } catch (SolrServerException | RemoteSolrException | IOException e) { log.warn("Attempt to fetch cluster state from {} failed.", baseUrl, e); } catch (NotACollectionException e) { // not possible! (we passed in null for collection, so it can't be an alias) @@ -376,7 +373,7 @@ public abstract class BaseHttpClusterStateProvider implements ClusterStateProvid SimpleOrderedMap<?> cluster = submitClusterStateRequest(client, null, ClusterStateRequestType.FETCH_CLUSTER_PROP); return (Map<String, Object>) cluster.get("properties"); - } catch (SolrServerException | BaseHttpSolrClient.RemoteSolrException | IOException e) { + } catch (SolrServerException | RemoteSolrException | IOException e) { log.warn("Attempt to fetch cluster state from {} failed.", baseUrl, e); } } @@ -428,6 +425,7 @@ public abstract class BaseHttpClusterStateProvider implements ClusterStateProvid FETCH_LIVE_NODES, FETCH_CLUSTER_PROP, FETCH_NODE_ROLES, - FETCH_COLLECTION + FETCH_COLLECTION, + FETCH_CLUSTER_STATE } } diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/ClusterStateProviderTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/ClusterStateProviderTest.java index c181a59e7da..4c7741c8ee4 100644 --- a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/ClusterStateProviderTest.java +++ b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/ClusterStateProviderTest.java @@ -17,10 +17,17 @@ package org.apache.solr.client.solrj.impl; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; + +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.time.Instant; import java.util.List; import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Collectors; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.request.CollectionAdminRequest; import org.apache.solr.client.solrj.response.CollectionAdminResponse; @@ -28,6 +35,7 @@ import org.apache.solr.cloud.SolrCloudTestCase; import org.apache.solr.common.cloud.ClusterState; import org.apache.solr.common.cloud.DocCollection; import org.apache.solr.common.util.NamedList; +import org.hamcrest.Matchers; import org.junit.BeforeClass; import org.junit.Test; @@ -47,19 +55,44 @@ public class ClusterStateProviderTest extends SolrCloudTestCase { .configure(); } - private ClusterStateProvider createClusterStateProvider() throws Exception { - return !usually() ? http2ClusterStateProvider() : zkClientClusterStateProvider(); + @ParametersFactory + public static Iterable<String[]> parameters() throws NoSuchMethodException { + return List.of( + new String[] {"http2ClusterStateProvider"}, new String[] {"zkClientClusterStateProvider"}); } - private ClusterStateProvider http2ClusterStateProvider() throws Exception { - return new Http2ClusterStateProvider( - List.of(cluster.getJettySolrRunner(0).getBaseUrl().toString()), null); + private static ClusterStateProvider http2ClusterStateProvider() { + try { + return new Http2ClusterStateProvider( + List.of(cluster.getJettySolrRunner(0).getBaseUrl().toString()), null); + } catch (Exception e) { + throw new RuntimeException(e); + } } - private ClusterStateProvider zkClientClusterStateProvider() { + private static ClusterStateProvider zkClientClusterStateProvider() { return new ZkClientClusterStateProvider(cluster.getZkStateReader()); } + private final Supplier<ClusterStateProvider> cspSupplier; + + public ClusterStateProviderTest(String method) throws Exception { + this.cspSupplier = + () -> { + try { + return (ClusterStateProvider) getClass().getDeclaredMethod(method).invoke(getClass()); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + }; + + cluster.deleteAllCollections(); + } + + private ClusterStateProvider createClusterStateProvider() { + return cspSupplier.get(); + } + @Test public void testGetClusterState() throws Exception { @@ -117,4 +150,39 @@ public class ClusterStateProviderTest extends SolrCloudTestCase { Map<String, Object> collection = (Map<String, Object>) collections.get(collectionName); return Instant.ofEpochMilli((long) collection.get("creationTimeMillis")); } + + @Test + public void testClusterStateProvider() throws SolrServerException, IOException { + // we'll test equivalency of the two cluster state providers + + CollectionAdminRequest.setClusterProperty("ext.foo", "bar").process(cluster.getSolrClient()); + + createCollection("col1"); + createCollection("col2"); + + try (var cspZk = zkClientClusterStateProvider(); + var cspHttp = http2ClusterStateProvider()) { + + assertThat(cspZk.getClusterProperties(), Matchers.hasEntry("ext.foo", "bar")); + assertThat( + cspZk.getClusterProperties().entrySet(), + containsInAnyOrder(cspHttp.getClusterProperties().entrySet().toArray())); + + assertThat(cspHttp.getCollection("col1"), equalTo(cspZk.getCollection("col1"))); + + final var clusterStateZk = cspZk.getClusterState(); + final var clusterStateHttp = cspHttp.getClusterState(); + assertThat( + clusterStateHttp.getLiveNodes(), + containsInAnyOrder(clusterStateHttp.getLiveNodes().toArray())); + assertEquals(2, clusterStateZk.size()); + assertEquals(clusterStateZk.size(), clusterStateHttp.size()); + assertThat( + clusterStateHttp.collectionStream().collect(Collectors.toList()), + containsInAnyOrder(clusterStateHttp.collectionStream().toArray())); + + assertThat( + clusterStateZk.getCollection("col2"), equalTo(clusterStateHttp.getCollection("col2"))); + } + } }