org.apache.solr.cluster.placement.impl.SimpleClusterAbstractionsImpl Maven / Gradle / Ivy
Show all versions of solr-core Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.cluster.placement.impl;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.solr.client.solrj.cloud.SolrCloudManager;
import org.apache.solr.cloud.api.collections.Assign;
import org.apache.solr.cluster.Cluster;
import org.apache.solr.cluster.Node;
import org.apache.solr.cluster.Replica;
import org.apache.solr.cluster.Shard;
import org.apache.solr.cluster.SolrCollection;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.Slice;
import org.apache.solr.common.params.CollectionAdminParams;
import org.apache.solr.common.util.Pair;
/**
* The implementation of the cluster abstractions from {@link org.apache.solr.cluster} as static
* inner classes of this one are a very straightforward approach for an initial implementation of
* the placement plugins, but are likely not the right implementations for the long term.
*
* Indeed there's a delay between the moment the Collection API computes a placement for a given
* command and when this placement decision is actually executed and Zookeeper for example updated
* with the new state (and that state visible to the node or nodes). Under high load when a large
* number of placement requests are computed, the naive implementation presented here could in some
* cases provide the same cluster state view to all placement requests over a period of time that
* can extend to over a minute and have the resulting placement decisions all place replicas on the
* same nodes, eventually leading to severe imbalance of the cluster.
*
*
By modifying the cluster abstractions implementations (without changing the API seen by
* placement plugins) to provide a view of the cluster that anticipates the way the cluster will be
* after in flight placement decisions are taken into account, the underlying Solr side framework
* supporting placement plugins can compensate to a point the delay between placement decision and
* that decision being observable.
*/
class SimpleClusterAbstractionsImpl {
static class ClusterImpl implements Cluster {
private final Set liveNodes;
private final Set liveNodesWithData;
private final ClusterState clusterState;
ClusterImpl(SolrCloudManager solrCloudManager) throws IOException {
Set liveNodes = solrCloudManager.getClusterStateProvider().getLiveNodes();
Collection liveNodesWithData =
Assign.filterNonDataNodes(solrCloudManager.getDistribStateManager(), liveNodes);
this.liveNodes = NodeImpl.getNodes(liveNodes);
this.liveNodesWithData =
liveNodesWithData.size() == liveNodes.size()
? this.liveNodes
: NodeImpl.getNodes(liveNodesWithData);
clusterState = solrCloudManager.getClusterState();
}
@Override
public Set getLiveNodes() {
return liveNodes;
}
@Override
public Set getLiveDataNodes() {
return liveNodesWithData;
}
@Override
public SolrCollection getCollection(String collectionName) {
return SolrCollectionImpl.createCollectionFacade(clusterState, collectionName);
}
@Override
public Iterator iterator() {
return clusterState.getCollectionsMap().values().stream()
.map(SolrCollectionImpl::fromDocCollection)
.collect(Collectors.toSet())
.iterator();
}
@Override
public Iterable collections() {
return ClusterImpl.this::iterator;
}
}
static class NodeImpl implements Node {
public final String nodeName;
/** Transforms a collection of node names into a set of {@link Node} instances. */
static Set getNodes(Collection nodeNames) {
return nodeNames.stream().map(NodeImpl::new).collect(Collectors.toSet());
}
NodeImpl(String nodeName) {
this.nodeName = nodeName;
}
@Override
public String getName() {
return nodeName;
}
@Override
public String toString() {
return getClass().getSimpleName() + "(" + getName() + ")";
}
/**
* This class ends up as a key in Maps in {@link
* org.apache.solr.cluster.placement.AttributeValues}. It is important to implement this method
* comparing node names given that new instances of {@link Node} are created with names equal to
* existing instances (See {@link ReplicaImpl} constructor).
*/
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof NodeImpl)) {
return false;
}
NodeImpl other = (NodeImpl) obj;
return Objects.equals(this.nodeName, other.nodeName);
}
@Override
public int hashCode() {
return Objects.hashCode(nodeName);
}
}
static class SolrCollectionImpl implements SolrCollection {
private final String collectionName;
/** Map from {@link Shard#getShardName()} to {@link Shard} */
private final Map shards;
private final DocCollection docCollection;
static SolrCollection createCollectionFacade(ClusterState clusterState, String collectionName) {
return fromDocCollection(clusterState.getCollectionOrNull(collectionName));
}
static SolrCollection fromDocCollection(DocCollection docCollection) {
return docCollection == null ? null : new SolrCollectionImpl(docCollection);
}
SolrCollectionImpl(DocCollection docCollection) {
this.collectionName = docCollection.getName();
this.shards = ShardImpl.getShards(this, docCollection.getSlices());
this.docCollection = docCollection;
}
@Override
public String getName() {
return collectionName;
}
@Override
public Shard getShard(String name) {
return shards.get(name);
}
@Override
public Iterator iterator() {
return shards.values().iterator();
}
@Override
public Iterable shards() {
return SolrCollectionImpl.this::iterator;
}
@Override
public Set getShardNames() {
return shards.keySet();
}
@Override
public String toString() {
return "SolrCollectionImpl{"
+ "collectionName='"
+ collectionName
+ '\''
+ ", shards="
+ shards.keySet()
+ ", docCollection="
+ docCollection
+ '}';
}
@Override
public String getCustomProperty(String customPropertyName) {
return docCollection.getStr(CollectionAdminParams.PROPERTY_PREFIX + customPropertyName);
}
}
static class ShardImpl implements Shard {
private final String shardName;
private final SolrCollection collection;
private final ShardState shardState;
private final Map replicas;
private final Replica leader;
/**
* Transforms {@link Slice}'s of a {@link org.apache.solr.common.cloud.DocCollection} into a map
* of {@link Shard}'s, keyed by shard name ({@link Shard#getShardName()}).
*/
static Map getShards(SolrCollection solrCollection, Collection slices) {
Map shards = new HashMap<>();
for (Slice slice : slices) {
String shardName = slice.getName();
shards.put(shardName, new ShardImpl(shardName, solrCollection, slice));
}
return shards;
}
private ShardImpl(String shardName, SolrCollection collection, Slice slice) {
this.shardName = shardName;
this.collection = collection;
this.shardState = translateState(slice.getState());
Pair