Skip to content

Commit 24fe191

Browse files
authored
SOLR-17535: Migrate ClusterState.getCollectionsMap callers to Stream (#2846)
Deprecated getCollectionsMap. A refactoring, albeit some additional points: * DeleteCollection: avoid NPE for non-existent collection (trivial change) * ClusterStateUtil (test-framework): optimize waiting to use ZkStateReader.waitForState for single-collection
1 parent 9f591aa commit 24fe191

File tree

18 files changed

+291
-404
lines changed

18 files changed

+291
-404
lines changed

solr/core/src/java/org/apache/solr/cloud/ConfigSetCmds.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import java.util.HashMap;
3232
import java.util.Map;
3333
import org.apache.solr.common.SolrException;
34-
import org.apache.solr.common.cloud.DocCollection;
3534
import org.apache.solr.common.cloud.ZkNodeProps;
3635
import org.apache.solr.common.cloud.ZkStateReader;
3736
import org.apache.solr.common.params.ConfigSetParams;
@@ -184,16 +183,18 @@ private static void deleteConfigSet(
184183
String configSetName, boolean force, CoreContainer coreContainer) throws IOException {
185184
ZkStateReader zkStateReader = coreContainer.getZkController().getZkStateReader();
186185

187-
for (Map.Entry<String, DocCollection> entry :
188-
zkStateReader.getClusterState().getCollectionsMap().entrySet()) {
189-
String configName = entry.getValue().getConfigName();
190-
if (configSetName.equals(configName))
191-
throw new SolrException(
192-
SolrException.ErrorCode.BAD_REQUEST,
193-
"Can not delete ConfigSet as it is currently being used by collection ["
194-
+ entry.getKey()
195-
+ "]");
196-
}
186+
zkStateReader
187+
.getClusterState()
188+
.forEachCollection(
189+
state -> {
190+
String configName = state.getConfigName();
191+
if (configSetName.equals(configName))
192+
throw new SolrException(
193+
SolrException.ErrorCode.BAD_REQUEST,
194+
"Can not delete ConfigSet as it is currently being used by collection ["
195+
+ state.getName()
196+
+ "]");
197+
});
197198

198199
String propertyPath = ConfigSetProperties.DEFAULT_FILENAME;
199200
NamedList<Object> properties =

solr/core/src/java/org/apache/solr/cloud/DistributedClusterStateUpdater.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -975,7 +975,7 @@ public void computeUpdates(ClusterState clusterState, SolrZkClient client) {
975975
final DocCollection docCollection = clusterState.getCollectionOrNull(collectionName);
976976
Optional<ZkWriteCommand> result =
977977
docCollection != null
978-
? NodeMutator.computeCollectionUpdate(nodeName, collectionName, docCollection, client)
978+
? NodeMutator.computeCollectionUpdate(nodeName, docCollection, client)
979979
: Optional.empty();
980980

981981
if (docCollection == null) {

solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteCollectionCmd.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -168,21 +168,19 @@ public void call(ClusterState state, ZkNodeProps message, NamedList<Object> resu
168168
}
169169

170170
// delete related config set iff: it is auto generated AND not related to any other collection
171-
String configSetName = coll.getConfigName();
171+
String configSetName = coll == null ? null : coll.getConfigName();
172172

173173
if (ConfigSetsHandler.isAutoGeneratedConfigSet(configSetName)) {
174174
boolean configSetIsUsedByOtherCollection = false;
175175

176176
// make sure the configSet is not shared with other collections
177177
// Similar to what happens in: ConfigSetCmds::deleteConfigSet
178-
for (Map.Entry<String, DocCollection> entry :
179-
zkStateReader.getClusterState().getCollectionsMap().entrySet()) {
180-
String otherConfigSetName = entry.getValue().getConfigName();
181-
if (configSetName.equals(otherConfigSetName)) {
182-
configSetIsUsedByOtherCollection = true;
183-
break;
184-
}
185-
}
178+
configSetIsUsedByOtherCollection =
179+
zkStateReader
180+
.getClusterState()
181+
.collectionStream()
182+
.map(DocCollection::getConfigName)
183+
.anyMatch(configSetName::equals);
186184

187185
if (!configSetIsUsedByOtherCollection) {
188186
// delete the config set

solr/core/src/java/org/apache/solr/cloud/api/collections/ReplicaMigrationUtils.java

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,7 @@
3434
import org.apache.solr.common.SolrCloseableLatch;
3535
import org.apache.solr.common.cloud.ClusterState;
3636
import org.apache.solr.common.cloud.CollectionStateWatcher;
37-
import org.apache.solr.common.cloud.DocCollection;
3837
import org.apache.solr.common.cloud.Replica;
39-
import org.apache.solr.common.cloud.Slice;
4038
import org.apache.solr.common.cloud.ZkNodeProps;
4139
import org.apache.solr.common.cloud.ZkStateReader;
4240
import org.apache.solr.common.params.CoreAdminParams;
@@ -305,30 +303,20 @@ static boolean cleanupReplicas(
305303
}
306304

307305
static List<Replica> getReplicasOfNodes(Collection<String> nodeNames, ClusterState state) {
308-
List<Replica> sourceReplicas = new ArrayList<>();
309-
for (Map.Entry<String, DocCollection> e : state.getCollectionsMap().entrySet()) {
310-
for (Slice slice : e.getValue().getSlices()) {
311-
for (Replica replica : slice.getReplicas()) {
312-
if (nodeNames.contains(replica.getNodeName())) {
313-
sourceReplicas.add(replica);
314-
}
315-
}
316-
}
317-
}
318-
return sourceReplicas;
306+
return state
307+
.collectionStream()
308+
.flatMap(dc -> dc.getSlices().stream())
309+
.flatMap(s -> s.getReplicas().stream())
310+
.filter(r -> nodeNames.contains(r.getNodeName()))
311+
.toList();
319312
}
320313

321314
static List<Replica> getReplicasOfNode(String nodeName, ClusterState state) {
322-
List<Replica> sourceReplicas = new ArrayList<>();
323-
for (Map.Entry<String, DocCollection> e : state.getCollectionsMap().entrySet()) {
324-
for (Slice slice : e.getValue().getSlices()) {
325-
for (Replica replica : slice.getReplicas()) {
326-
if (nodeName.equals(replica.getNodeName())) {
327-
sourceReplicas.add(replica);
328-
}
329-
}
330-
}
331-
}
332-
return sourceReplicas;
315+
return state
316+
.collectionStream()
317+
.flatMap(dc -> dc.getSlices().stream())
318+
.flatMap(s -> s.getReplicas().stream())
319+
.filter(r -> nodeName.equals(r.getNodeName()))
320+
.toList();
333321
}
334322
}

solr/core/src/java/org/apache/solr/cloud/overseer/NodeMutator.java

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,23 +49,12 @@ public List<ZkWriteCommand> downNode(ClusterState clusterState, ZkNodeProps mess
4949

5050
log.debug("DownNode state invoked for node: {}", nodeName);
5151

52-
List<ZkWriteCommand> zkWriteCommands = new ArrayList<>();
53-
54-
Map<String, DocCollection> collections = clusterState.getCollectionsMap();
55-
for (Map.Entry<String, DocCollection> entry : collections.entrySet()) {
56-
String collectionName = entry.getKey();
57-
DocCollection docCollection = entry.getValue();
58-
if (docCollection.isPerReplicaState()) continue;
59-
60-
Optional<ZkWriteCommand> zkWriteCommand =
61-
computeCollectionUpdate(nodeName, collectionName, docCollection, zkClient);
62-
63-
if (zkWriteCommand.isPresent()) {
64-
zkWriteCommands.add(zkWriteCommand.get());
65-
}
66-
}
67-
68-
return zkWriteCommands;
52+
return clusterState
53+
.collectionStream()
54+
.filter(entry -> !entry.isPerReplicaState())
55+
.map(docCollection -> computeCollectionUpdate(nodeName, docCollection, zkClient))
56+
.flatMap(Optional::stream)
57+
.toList();
6958
}
7059

7160
/**
@@ -77,7 +66,7 @@ public List<ZkWriteCommand> downNode(ClusterState clusterState, ZkNodeProps mess
7766
* for an update to state.json, depending on the configuration of the collection.
7867
*/
7968
public static Optional<ZkWriteCommand> computeCollectionUpdate(
80-
String nodeName, String collectionName, DocCollection docCollection, SolrZkClient client) {
69+
String nodeName, DocCollection docCollection, SolrZkClient client) {
8170
boolean needToUpdateCollection = false;
8271
List<String> downedReplicas = new ArrayList<>();
8372
final Map<String, Slice> slicesCopy = new LinkedHashMap<>(docCollection.getSlicesMap());
@@ -107,13 +96,13 @@ public static Optional<ZkWriteCommand> computeCollectionUpdate(
10796

10897
return Optional.of(
10998
new ZkWriteCommand(
110-
collectionName,
99+
docCollection.getName(),
111100
docCollection.copyWithSlices(slicesCopy),
112101
PerReplicaStatesOps.downReplicas(downedReplicas, prs),
113102
false));
114103
} else {
115104
return Optional.of(
116-
new ZkWriteCommand(collectionName, docCollection.copyWithSlices(slicesCopy)));
105+
new ZkWriteCommand(docCollection.getName(), docCollection.copyWithSlices(slicesCopy)));
117106
}
118107
} else {
119108
// No update needed for this collection

solr/core/src/java/org/apache/solr/cluster/maintenance/InactiveShardRemover.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ public void stop() {
143143
void deleteInactiveSlices() {
144144
final ClusterState clusterState = coreContainer.getZkController().getClusterState();
145145
Collection<Slice> inactiveSlices =
146-
clusterState.getCollectionsMap().values().stream()
146+
clusterState
147+
.collectionStream()
147148
.flatMap(v -> collectInactiveSlices(v).stream())
148149
.collect(Collectors.toSet());
149150

solr/core/src/java/org/apache/solr/cluster/placement/impl/SimpleClusterAbstractionsImpl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ public SolrCollection getCollection(String collectionName) {
9393

9494
@Override
9595
public Iterator<SolrCollection> iterator() {
96-
return clusterState.getCollectionsMap().values().stream()
96+
return clusterState
97+
.collectionStream()
9798
.map(SolrCollectionImpl::fromDocCollection)
9899
.collect(Collectors.toSet())
99100
.iterator();

solr/core/src/java/org/apache/solr/handler/admin/ClusterStatus.java

Lines changed: 59 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@
2424
import java.util.HashSet;
2525
import java.util.List;
2626
import java.util.Map;
27+
import java.util.Objects;
2728
import java.util.Set;
28-
import java.util.stream.Collectors;
29+
import java.util.stream.Stream;
2930
import org.apache.solr.common.SolrException;
3031
import org.apache.solr.common.cloud.Aliases;
3132
import org.apache.solr.common.cloud.ClusterState;
@@ -43,6 +44,7 @@
4344
import org.apache.zookeeper.KeeperException;
4445

4546
public class ClusterStatus {
47+
4648
private final ZkStateReader zkStateReader;
4749
private final SolrParams solrParams;
4850
private final String collection; // maybe null
@@ -178,79 +180,74 @@ private void fetchClusterStatusForCollOrAlias(
178180
String routeKey = solrParams.get(ShardParams._ROUTE_);
179181
String shard = solrParams.get(ZkStateReader.SHARD_ID_PROP);
180182

181-
Map<String, DocCollection> collectionsMap = null;
183+
Stream<DocCollection> collectionStream;
182184
if (collection == null) {
183-
collectionsMap = clusterState.getCollectionsMap();
185+
collectionStream = clusterState.collectionStream();
184186
} else {
185-
collectionsMap =
186-
Collections.singletonMap(collection, clusterState.getCollectionOrNull(collection));
187-
}
188-
189-
boolean isAlias = aliasVsCollections.containsKey(collection);
190-
boolean didNotFindCollection = collectionsMap.get(collection) == null;
191-
192-
if (didNotFindCollection && isAlias) {
193-
// In this case this.collection is an alias name not a collection
194-
// get all collections and filter out collections not in the alias
195-
// clusterState.getCollectionsMap() should be replaced with an inexpensive call
196-
collectionsMap =
197-
clusterState.getCollectionsMap().entrySet().stream()
198-
.filter((entry) -> aliasVsCollections.get(collection).contains(entry.getKey()))
199-
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
200-
}
201-
202-
NamedList<Object> collectionProps = new SimpleOrderedMap<>();
203-
204-
for (Map.Entry<String, DocCollection> entry : collectionsMap.entrySet()) {
205-
Map<String, Object> collectionStatus;
206-
String name = entry.getKey();
207-
DocCollection clusterStateCollection = entry.getValue();
208-
if (clusterStateCollection == null) {
209-
if (collection != null) {
187+
DocCollection collState = clusterState.getCollectionOrNull(collection);
188+
if (collState != null) {
189+
collectionStream = Stream.of(collState);
190+
} else { // couldn't find collection
191+
// hopefully an alias...
192+
if (!aliasVsCollections.containsKey(collection)) { // not an alias either
210193
SolrException solrException =
211194
new SolrException(
212-
SolrException.ErrorCode.BAD_REQUEST, "Collection: " + name + " not found");
195+
SolrException.ErrorCode.BAD_REQUEST, "Collection: " + collection + " not found");
213196
solrException.setMetadata("CLUSTERSTATUS", "NOT_FOUND");
214197
throw solrException;
215-
} else {
216-
// collection might have got deleted at the same time
217-
continue;
218198
}
199+
// In this case this.collection is an alias name not a collection
200+
// Resolve them (not recursively but maybe should?).
201+
collectionStream =
202+
aliasVsCollections.get(collection).stream()
203+
.map(clusterState::getCollectionOrNull)
204+
.filter(Objects::nonNull);
219205
}
206+
}
220207

221-
Set<String> requestedShards = new HashSet<>();
222-
if (routeKey != null) {
223-
DocRouter router = clusterStateCollection.getRouter();
224-
Collection<Slice> slices = router.getSearchSlices(routeKey, null, clusterStateCollection);
225-
for (Slice slice : slices) {
226-
requestedShards.add(slice.getName());
227-
}
228-
}
229-
if (shard != null) {
230-
String[] paramShards = shard.split(",");
231-
requestedShards.addAll(Arrays.asList(paramShards));
232-
}
208+
// TODO use an Iterable to stream the data to the client instead of gathering it all in mem
233209

234-
byte[] bytes = Utils.toJSON(clusterStateCollection);
235-
@SuppressWarnings("unchecked")
236-
Map<String, Object> docCollection = (Map<String, Object>) Utils.fromJSON(bytes);
237-
collectionStatus = getCollectionStatus(docCollection, name, requestedShards);
210+
NamedList<Object> collectionProps = new SimpleOrderedMap<>();
238211

239-
collectionStatus.put("znodeVersion", clusterStateCollection.getZNodeVersion());
240-
collectionStatus.put(
241-
"creationTimeMillis", clusterStateCollection.getCreationTime().toEpochMilli());
212+
collectionStream.forEach(
213+
clusterStateCollection -> {
214+
Map<String, Object> collectionStatus;
215+
String name = clusterStateCollection.getName();
216+
217+
Set<String> requestedShards = new HashSet<>();
218+
if (routeKey != null) {
219+
DocRouter router = clusterStateCollection.getRouter();
220+
Collection<Slice> slices =
221+
router.getSearchSlices(routeKey, null, clusterStateCollection);
222+
for (Slice slice : slices) {
223+
requestedShards.add(slice.getName());
224+
}
225+
}
226+
if (shard != null) {
227+
String[] paramShards = shard.split(",");
228+
requestedShards.addAll(Arrays.asList(paramShards));
229+
}
242230

243-
if (collectionVsAliases.containsKey(name) && !collectionVsAliases.get(name).isEmpty()) {
244-
collectionStatus.put("aliases", collectionVsAliases.get(name));
245-
}
246-
String configName = clusterStateCollection.getConfigName();
247-
collectionStatus.put("configName", configName);
248-
if (solrParams.getBool("prs", false) && clusterStateCollection.isPerReplicaState()) {
249-
PerReplicaStates prs = clusterStateCollection.getPerReplicaStates();
250-
collectionStatus.put("PRS", prs);
251-
}
252-
collectionProps.add(name, collectionStatus);
253-
}
231+
byte[] bytes = Utils.toJSON(clusterStateCollection);
232+
@SuppressWarnings("unchecked")
233+
Map<String, Object> docCollection = (Map<String, Object>) Utils.fromJSON(bytes);
234+
collectionStatus = getCollectionStatus(docCollection, name, requestedShards);
235+
236+
collectionStatus.put("znodeVersion", clusterStateCollection.getZNodeVersion());
237+
collectionStatus.put(
238+
"creationTimeMillis", clusterStateCollection.getCreationTime().toEpochMilli());
239+
240+
if (collectionVsAliases.containsKey(name) && !collectionVsAliases.get(name).isEmpty()) {
241+
collectionStatus.put("aliases", collectionVsAliases.get(name));
242+
}
243+
String configName = clusterStateCollection.getConfigName();
244+
collectionStatus.put("configName", configName);
245+
if (solrParams.getBool("prs", false) && clusterStateCollection.isPerReplicaState()) {
246+
PerReplicaStates prs = clusterStateCollection.getPerReplicaStates();
247+
collectionStatus.put("PRS", prs);
248+
}
249+
collectionProps.add(name, collectionStatus);
250+
});
254251

255252
// now we need to walk the collectionProps tree to cross-check replica state with live nodes
256253
crossCheckReplicaStateWithLiveNodes(liveNodes, collectionProps);

solr/core/src/java/org/apache/solr/handler/admin/api/ListCollections.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@
2020
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM;
2121

2222
import jakarta.inject.Inject;
23-
import java.util.ArrayList;
24-
import java.util.Collections;
2523
import java.util.List;
26-
import java.util.Map;
2724
import org.apache.solr.client.api.endpoint.ListCollectionsApi;
2825
import org.apache.solr.client.api.model.ListCollectionsResponse;
2926
import org.apache.solr.common.cloud.DocCollection;
@@ -51,10 +48,16 @@ public ListCollectionsResponse listCollections() {
5148
instantiateJerseyResponse(ListCollectionsResponse.class);
5249
validateZooKeeperAwareCoreContainer(coreContainer);
5350

54-
Map<String, DocCollection> collections =
55-
coreContainer.getZkController().getZkStateReader().getClusterState().getCollectionsMap();
56-
List<String> collectionList = new ArrayList<>(collections.keySet());
57-
Collections.sort(collectionList);
51+
// resolve each name to ensure it exists.
52+
// TODO https://issues.apache.org/jira/browse/SOLR-16909 to go direct to ZK?
53+
List<String> collectionList =
54+
coreContainer
55+
.getZkController()
56+
.getClusterState()
57+
.collectionStream()
58+
.map(DocCollection::getName)
59+
.sorted()
60+
.toList();
5861
// XXX should we add aliases here?
5962
response.collections = collectionList;
6063

0 commit comments

Comments
 (0)