diff options
author | Henning Baldersheim <balder@yahoo-inc.com> | 2022-11-24 12:10:45 +0100 |
---|---|---|
committer | Henning Baldersheim <balder@yahoo-inc.com> | 2022-11-24 12:10:45 +0100 |
commit | 8e2abfc58e936bd6be3194f9e02a21a9b70b7692 (patch) | |
tree | 9918c66fc57647c0b1c831223f6b0f9c5d6b680d /container-search/src/main/java/com/yahoo/search/dispatch/searchcluster | |
parent | 90b260e83678edc36eb877ec235e0e6ce5892a48 (diff) |
Put loadbalancer and invokerfactory in a volatile object to ensure atomic switch when reconfiguring.
Diffstat (limited to 'container-search/src/main/java/com/yahoo/search/dispatch/searchcluster')
-rw-r--r-- | container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Group.java | 8 | ||||
-rw-r--r-- | container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/GroupListImpl.java | 38 | ||||
-rw-r--r-- | container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java | 63 | ||||
-rw-r--r-- | container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchGroups.java (renamed from container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/GroupList.java) | 11 | ||||
-rw-r--r-- | container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchGroupsImpl.java | 63 |
5 files changed, 90 insertions, 93 deletions
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Group.java b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Group.java index cf161638104..fbea58054da 100644 --- a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Group.java +++ b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/Group.java @@ -1,8 +1,6 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.search.dispatch.searchcluster; -import com.google.common.collect.ImmutableList; - import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; @@ -22,7 +20,7 @@ public class Group { private final static int minDocsPerNodeToRequireLowSkew = 100; private final int id; - private final ImmutableList<Node> nodes; + private final List<Node> nodes; private final AtomicBoolean hasSufficientCoverage = new AtomicBoolean(true); private final AtomicBoolean hasFullCoverage = new AtomicBoolean(true); private final AtomicLong activeDocuments = new AtomicLong(0); @@ -32,7 +30,7 @@ public class Group { public Group(int id, List<Node> nodes) { this.id = id; - this.nodes = ImmutableList.copyOf(nodes); + this.nodes = List.copyOf(nodes); int idx = 0; for(var node: nodes) { @@ -48,7 +46,7 @@ public class Group { public int id() { return id; } /** Returns the nodes in this group as an immutable list */ - public ImmutableList<Node> nodes() { return nodes; } + public List<Node> nodes() { return nodes; } /** * Returns whether this group has sufficient active documents diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/GroupListImpl.java b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/GroupListImpl.java deleted file mode 100644 index b0cbf7d1d1d..00000000000 --- a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/GroupListImpl.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.yahoo.search.dispatch.searchcluster; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class GroupListImpl implements GroupList { - private final Map<Integer, Group> groups; - public GroupListImpl(Map<Integer, Group> groups) { - this.groups = Map.copyOf(groups); - } - @Override public Group group(int id) { return groups.get(id); } - @Override public Set<Integer> groupKeys() { return groups.keySet();} - @Override public Collection<Group> groups() { return groups.values(); } - @Override public int numGroups() { return groups.size(); } - public static GroupList buildGroupListForTest(int numGroups, int nodesPerGroup) { - return new GroupListImpl(buildGroupMapForTest(numGroups, nodesPerGroup)); - } - public static Map<Integer, Group> buildGroupMapForTest(int numGroups, int nodesPerGroup) { - Map<Integer, Group> groups = new HashMap<>(); - int distributionKey = 0; - for (int group = 0; group < numGroups; group++) { - List<Node> groupNodes = new ArrayList<>(); - for (int i = 0; i < nodesPerGroup; i++) { - Node node = new Node(distributionKey, "host" + distributionKey, group); - node.setWorking(true); - groupNodes.add(node); - distributionKey++; - } - Group g = new Group(group, groupNodes); - groups.put(group, g); - } - return Map.copyOf(groups); - } -} diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java index bedfa965229..aa3797cb627 100644 --- a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java +++ b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java @@ -2,7 +2,6 @@ package com.yahoo.search.dispatch.searchcluster; import com.google.common.collect.ImmutableMap; -import com.google.common.math.Quantiles; import com.yahoo.container.handler.VipStatus; import com.yahoo.net.HostName; import com.yahoo.prelude.Pong; @@ -14,7 +13,6 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.concurrent.Executor; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -24,15 +22,14 @@ import java.util.stream.Collectors; * * @author bratseth */ -public class SearchCluster implements NodeManager<Node>, GroupList { +public class SearchCluster implements NodeManager<Node> { private static final Logger log = Logger.getLogger(SearchCluster.class.getName()); - private final double minActivedocsPercentage; private final String clusterId; private final VipStatus vipStatus; private final PingFactory pingFactory; - private final GroupList groups; + private final SearchGroupsImpl groups; private long nextLogTime = 0; /** @@ -52,12 +49,10 @@ public class SearchCluster implements NodeManager<Node>, GroupList { } public SearchCluster(String clusterId, double minActivedocsPercentage, List<Node> nodes, VipStatus vipStatus, PingFactory pingFactory) { - this(clusterId, minActivedocsPercentage, toGroups(nodes), vipStatus, pingFactory); + this(clusterId, toGroups(nodes, minActivedocsPercentage), vipStatus, pingFactory); } - public SearchCluster(String clusterId, double minActivedocsPercentage, GroupList groups, - VipStatus vipStatus, PingFactory pingFactory) { + public SearchCluster(String clusterId, SearchGroupsImpl groups, VipStatus vipStatus, PingFactory pingFactory) { this.clusterId = clusterId; - this.minActivedocsPercentage = minActivedocsPercentage; this.vipStatus = vipStatus; this.pingFactory = pingFactory; this.groups = groups; @@ -74,7 +69,7 @@ public class SearchCluster implements NodeManager<Node>, GroupList { } } - private static Node findLocalCorpusDispatchTarget(String selfHostname, GroupList groups) { + private static Node findLocalCorpusDispatchTarget(String selfHostname, SearchGroups groups) { // A search node in the search cluster in question is configured on the same host as the currently running container. // It has all the data <==> No other nodes in the search cluster have the same group id as this node. // That local search node responds. @@ -86,7 +81,7 @@ public class SearchCluster implements NodeManager<Node>, GroupList { if (localSearchNodes.size() != 1) return null; Node localSearchNode = localSearchNodes.iterator().next(); - Group localSearchGroup = groups.group(localSearchNode.group()); + Group localSearchGroup = groups.get(localSearchNode.group()); // Only use direct dispatch if the local search node has the entire corpus if (localSearchGroup.nodes().size() != 1) return null; @@ -100,27 +95,19 @@ public class SearchCluster implements NodeManager<Node>, GroupList { .toList(); } - private static GroupList toGroups(Collection<Node> nodes) { + private static SearchGroupsImpl toGroups(Collection<Node> nodes, double minActivedocsPercentage) { ImmutableMap.Builder<Integer, Group> groupsBuilder = new ImmutableMap.Builder<>(); for (Map.Entry<Integer, List<Node>> group : nodes.stream().collect(Collectors.groupingBy(Node::group)).entrySet()) { Group g = new Group(group.getKey(), group.getValue()); groupsBuilder.put(group.getKey(), g); } - return new GroupListImpl(groupsBuilder.build()); + return new SearchGroupsImpl(groupsBuilder.build(), minActivedocsPercentage); } - @Override - public Group group(int id) { - return groups.group(id); - } - @Override - public Set<Integer> groupKeys() { return groups.groupKeys(); } - @Override - public Collection<Group> groups() { return groups.groups(); } - @Override - public int numGroups() { return groups.numGroups(); } + public SearchGroups groupList() { return groups; } + public Group group(int id) { return groups.get(id); } - public boolean allGroupsHaveSize1() { return groups().stream().allMatch(g -> g.nodes().size() == 1); } + private Collection<Group> groups() { return groups.groups(); } public int groupsWithSufficientCoverage() { return (int)groups().stream().filter(Group::hasSufficientCoverage).count(); @@ -134,7 +121,7 @@ public class SearchCluster implements NodeManager<Node>, GroupList { if ( localCorpusDispatchTarget == null) return Optional.empty(); // Only use direct dispatch if the local group has sufficient coverage - Group localSearchGroup = group(localCorpusDispatchTarget.group()); + Group localSearchGroup = groups.get(localCorpusDispatchTarget.group()); if ( ! localSearchGroup.hasSufficientCoverage()) return Optional.empty(); // Only use direct dispatch if the local search node is not down @@ -225,26 +212,20 @@ public class SearchCluster implements NodeManager<Node>, GroupList { // With just one group sufficient coverage may not be the same as full coverage, as the // group will always be marked sufficient for use. updateSufficientCoverage(group, true); - boolean sufficientCoverage = isGroupCoverageSufficient(group.activeDocuments(), group.activeDocuments()); + boolean sufficientCoverage = groups.isGroupCoverageSufficient(group.activeDocuments(), group.activeDocuments()); trackGroupCoverageChanges(group, sufficientCoverage, group.activeDocuments()); } private void pingIterationCompletedMultipleGroups() { groups().forEach(Group::aggregateNodeValues); - long medianDocuments = medianDocumentsPerGroup(); + long medianDocuments = groups.medianDocumentsPerGroup(); for (Group group : groups()) { - boolean sufficientCoverage = isGroupCoverageSufficient(group.activeDocuments(), medianDocuments); + boolean sufficientCoverage = groups.isGroupCoverageSufficient(group.activeDocuments(), medianDocuments); updateSufficientCoverage(group, sufficientCoverage); trackGroupCoverageChanges(group, sufficientCoverage, medianDocuments); } } - private long medianDocumentsPerGroup() { - if (isEmpty()) return 0; - var activeDocuments = groups().stream().map(Group::activeDocuments).collect(Collectors.toList()); - return (long)Quantiles.median().compute(activeDocuments); - } - /** * Update statistics after a round of issuing pings. * Note that this doesn't wait for pings to return, so it will typically accumulate data from @@ -252,27 +233,19 @@ public class SearchCluster implements NodeManager<Node>, GroupList { */ @Override public void pingIterationCompleted() { - if (numGroups() == 1) { + if (groups.size() == 1) { pingIterationCompletedSingleGroup(); } else { pingIterationCompletedMultipleGroups(); } } - private boolean isGroupCoverageSufficient(long activeDocuments, long medianDocuments) { - double documentCoverage = 100.0 * (double) activeDocuments / medianDocuments; - return ! (medianDocuments > 0 && documentCoverage < minActivedocsPercentage); - } + /** * Calculate whether a subset of nodes in a group has enough coverage */ - public boolean isPartialGroupCoverageSufficient(List<Node> nodes) { - if (numGroups() == 1) - return true; - long activeDocuments = nodes.stream().mapToLong(Node::getActiveDocuments).sum(); - return isGroupCoverageSufficient(activeDocuments, medianDocumentsPerGroup()); - } + private void trackGroupCoverageChanges(Group group, boolean fullCoverage, long medianDocuments) { if ( ! hasInformationAboutAllNodes()) return; // Be silent until we know what we are talking about. diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/GroupList.java b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchGroups.java index 7d625f55cb6..b041ba28db9 100644 --- a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/GroupList.java +++ b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchGroups.java @@ -7,12 +7,13 @@ import java.util.Set; * Simple interface for groups and their nodes in the content cluster * @author baldersheim */ -public interface GroupList { - Group group(int id); - Set<Integer> groupKeys(); +public interface SearchGroups { + Group get(int id); + Set<Integer> keys(); Collection<Group> groups(); default boolean isEmpty() { - return numGroups() == 0; + return size() == 0; } - int numGroups(); + int size(); + boolean isPartialGroupCoverageSufficient(Collection<Node> nodes); } diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchGroupsImpl.java b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchGroupsImpl.java new file mode 100644 index 00000000000..a8f0d07b494 --- /dev/null +++ b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchGroupsImpl.java @@ -0,0 +1,63 @@ +package com.yahoo.search.dispatch.searchcluster; + +import com.google.common.math.Quantiles; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class SearchGroupsImpl implements SearchGroups { + private final Map<Integer, Group> groups; + private final double minActivedocsPercentage; + public SearchGroupsImpl(Map<Integer, Group> groups, double minActivedocsPercentage) { + this.groups = Map.copyOf(groups); + this.minActivedocsPercentage = minActivedocsPercentage; + } + @Override public Group get(int id) { return groups.get(id); } + @Override public Set<Integer> keys() { return groups.keySet();} + @Override public Collection<Group> groups() { return groups.values(); } + @Override public int size() { return groups.size(); } + @Override + public boolean isPartialGroupCoverageSufficient(Collection<Node> nodes) { + if (size() == 1) + return true; + long activeDocuments = nodes.stream().mapToLong(Node::getActiveDocuments).sum(); + return isGroupCoverageSufficient(activeDocuments, medianDocumentsPerGroup()); + } + + public boolean isGroupCoverageSufficient(long activeDocuments, long medianDocuments) { + double documentCoverage = 100.0 * (double) activeDocuments / medianDocuments; + return ! (medianDocuments > 0 && documentCoverage < minActivedocsPercentage); + } + + public long medianDocumentsPerGroup() { + if (isEmpty()) return 0; + var activeDocuments = groups().stream().map(Group::activeDocuments).collect(Collectors.toList()); + return (long) Quantiles.median().compute(activeDocuments); + } + + + public static SearchGroupsImpl buildGroupListForTest(int numGroups, int nodesPerGroup, double minActivedocsPercentage) { + return new SearchGroupsImpl(buildGroupMapForTest(numGroups, nodesPerGroup), minActivedocsPercentage); + } + public static Map<Integer, Group> buildGroupMapForTest(int numGroups, int nodesPerGroup) { + Map<Integer, Group> groups = new HashMap<>(); + int distributionKey = 0; + for (int group = 0; group < numGroups; group++) { + List<Node> groupNodes = new ArrayList<>(); + for (int i = 0; i < nodesPerGroup; i++) { + Node node = new Node(distributionKey, "host" + distributionKey, group); + node.setWorking(true); + groupNodes.add(node); + distributionKey++; + } + Group g = new Group(group, groupNodes); + groups.put(group, g); + } + return Map.copyOf(groups); + } +} |