|
27 | 27 | import org.elasticsearch.common.settings.Setting;
|
28 | 28 | import org.elasticsearch.common.settings.Setting.Property;
|
29 | 29 | import org.elasticsearch.common.settings.Settings;
|
30 |
| -import org.elasticsearch.common.util.set.Sets; |
31 | 30 |
|
32 |
| -import java.util.Collection; |
33 |
| -import java.util.Collections; |
34 | 31 | import java.util.Set;
|
35 |
| -import java.util.SortedSet; |
36 | 32 | import java.util.TreeSet;
|
37 | 33 | import java.util.stream.Collectors;
|
38 |
| -import java.util.stream.Stream; |
39 | 34 |
|
40 | 35 | /**
|
41 | 36 | * Computes the optimal configuration of voting nodes in the cluster.
|
@@ -102,81 +97,86 @@ public VotingConfiguration reconfigure(Set<DiscoveryNode> liveNodes, Set<String>
|
102 | 97 | logger.trace("{} reconfiguring {} based on liveNodes={}, retiredNodeIds={}, currentMaster={}",
|
103 | 98 | this, currentConfig, liveNodes, retiredNodeIds, currentMaster);
|
104 | 99 |
|
105 |
| - /* |
106 |
| - * There are three true/false properties of each node in play: live/non-live, retired/non-retired and in-config/not-in-config. |
107 |
| - * Firstly we divide the nodes into disjoint sets based on these properties: |
108 |
| - * |
109 |
| - * - nonRetiredMaster |
110 |
| - * - nonRetiredNotMasterInConfigNotLiveIds |
111 |
| - * - nonRetiredInConfigLiveIds |
112 |
| - * - nonRetiredLiveNotInConfigIds |
113 |
| - * |
114 |
| - * The other 5 possibilities are not relevant: |
115 |
| - * - retired, in-config, live -- retired nodes should be removed from the config |
116 |
| - * - retired, in-config, non-live -- retired nodes should be removed from the config |
117 |
| - * - retired, not-in-config, live -- cannot add a retired node back to the config |
118 |
| - * - retired, not-in-config, non-live -- cannot add a retired node back to the config |
119 |
| - * - non-retired, non-live, not-in-config -- no evidence this node exists at all |
120 |
| - */ |
121 |
| - |
122 | 100 | final Set<String> liveNodeIds = liveNodes.stream()
|
123 | 101 | .filter(DiscoveryNode::isMasterNode).map(DiscoveryNode::getId).collect(Collectors.toSet());
|
124 |
| - final Set<String> liveInConfigIds = new TreeSet<>(currentConfig.getNodeIds()); |
125 |
| - liveInConfigIds.retainAll(liveNodeIds); |
126 |
| - |
127 |
| - final SortedSet<String> inConfigNotLiveIds = Sets.unmodifiableSortedDifference(currentConfig.getNodeIds(), liveInConfigIds); |
128 |
| - final SortedSet<String> nonRetiredInConfigNotLiveIds = new TreeSet<>(inConfigNotLiveIds); |
129 |
| - nonRetiredInConfigNotLiveIds.removeAll(retiredNodeIds); |
130 |
| - |
131 |
| - final Set<String> nonRetiredInConfigLiveIds = new TreeSet<>(liveInConfigIds); |
132 |
| - nonRetiredInConfigLiveIds.removeAll(retiredNodeIds); |
133 |
| - |
134 |
| - final Set<String> nonRetiredInConfigLiveMasterIds; |
135 |
| - final Set<String> nonRetiredInConfigLiveNotMasterIds; |
136 |
| - if (nonRetiredInConfigLiveIds.contains(currentMaster.getId())) { |
137 |
| - nonRetiredInConfigLiveNotMasterIds = new TreeSet<>(nonRetiredInConfigLiveIds); |
138 |
| - nonRetiredInConfigLiveNotMasterIds.remove(currentMaster.getId()); |
139 |
| - nonRetiredInConfigLiveMasterIds = Collections.singleton(currentMaster.getId()); |
140 |
| - } else { |
141 |
| - nonRetiredInConfigLiveNotMasterIds = nonRetiredInConfigLiveIds; |
142 |
| - nonRetiredInConfigLiveMasterIds = Collections.emptySet(); |
143 |
| - } |
144 |
| - |
145 |
| - final SortedSet<String> nonRetiredLiveNotInConfigIds = Sets.sortedDifference(liveNodeIds, currentConfig.getNodeIds()); |
146 |
| - nonRetiredLiveNotInConfigIds.removeAll(retiredNodeIds); |
| 102 | + final Set<String> currentConfigNodeIds = currentConfig.getNodeIds(); |
| 103 | + |
| 104 | + final Set<VotingConfigNode> orderedCandidateNodes = new TreeSet<>(); |
| 105 | + liveNodes.stream() |
| 106 | + .filter(DiscoveryNode::isMasterNode) |
| 107 | + .filter(n -> retiredNodeIds.contains(n.getId()) == false) |
| 108 | + .forEach(n -> orderedCandidateNodes.add(new VotingConfigNode(n.getId(), true, |
| 109 | + n.getId().equals(currentMaster.getId()), currentConfigNodeIds.contains(n.getId())))); |
| 110 | + currentConfigNodeIds.stream() |
| 111 | + .filter(nid -> liveNodeIds.contains(nid) == false) |
| 112 | + .filter(nid -> retiredNodeIds.contains(nid) == false) |
| 113 | + .forEach(nid -> orderedCandidateNodes.add(new VotingConfigNode(nid, false, false, true))); |
147 | 114 |
|
148 | 115 | /*
|
149 | 116 | * Now we work out how many nodes should be in the configuration:
|
150 | 117 | */
|
151 |
| - final int targetSize; |
152 |
| - |
153 |
| - final int nonRetiredLiveNodeCount = nonRetiredInConfigLiveIds.size() + nonRetiredLiveNotInConfigIds.size(); |
154 |
| - final int nonRetiredConfigSize = nonRetiredInConfigLiveIds.size() + nonRetiredInConfigNotLiveIds.size(); |
155 |
| - if (autoShrinkVotingConfiguration) { |
156 |
| - if (nonRetiredLiveNodeCount >= 3) { |
157 |
| - targetSize = roundDownToOdd(nonRetiredLiveNodeCount); |
158 |
| - } else { |
159 |
| - // only have one or two available nodes; may not shrink below 3 nodes automatically, but if |
160 |
| - // the config (excluding retired nodes) is already smaller than 3 then it's ok. |
161 |
| - targetSize = nonRetiredConfigSize < 3 ? 1 : 3; |
162 |
| - } |
163 |
| - } else { |
164 |
| - targetSize = Math.max(roundDownToOdd(nonRetiredLiveNodeCount), nonRetiredConfigSize); |
165 |
| - } |
| 118 | + final int nonRetiredConfigSize = Math.toIntExact(orderedCandidateNodes.stream().filter(n -> n.inCurrentConfig).count()); |
| 119 | + final int minimumConfigEnforcedSize = autoShrinkVotingConfiguration ? (nonRetiredConfigSize < 3 ? 1 : 3) : nonRetiredConfigSize; |
| 120 | + final int nonRetiredLiveNodeCount = Math.toIntExact(orderedCandidateNodes.stream().filter(n -> n.live).count()); |
| 121 | + final int targetSize = Math.max(roundDownToOdd(nonRetiredLiveNodeCount), minimumConfigEnforcedSize); |
166 | 122 |
|
167 |
| - /* |
168 |
| - * The new configuration is formed by taking this many nodes in the following preference order: |
169 |
| - */ |
170 | 123 | final VotingConfiguration newConfig = new VotingConfiguration(
|
171 |
| - // live master first, then other live nodes, preferring the current config, and if we need more then use non-live nodes |
172 |
| - Stream.of(nonRetiredInConfigLiveMasterIds, nonRetiredInConfigLiveNotMasterIds, nonRetiredLiveNotInConfigIds, |
173 |
| - nonRetiredInConfigNotLiveIds).flatMap(Collection::stream).limit(targetSize).collect(Collectors.toSet())); |
| 124 | + orderedCandidateNodes.stream() |
| 125 | + .limit(targetSize) |
| 126 | + .map(n -> n.id) |
| 127 | + .collect(Collectors.toSet())); |
174 | 128 |
|
| 129 | + // new configuration should have a quorum |
175 | 130 | if (newConfig.hasQuorum(liveNodeIds)) {
|
176 | 131 | return newConfig;
|
177 | 132 | } else {
|
178 | 133 | // If there are not enough live nodes to form a quorum in the newly-proposed configuration, it's better to do nothing.
|
179 | 134 | return currentConfig;
|
180 | 135 | }
|
181 | 136 | }
|
| 137 | + |
| 138 | + static class VotingConfigNode implements Comparable<VotingConfigNode> { |
| 139 | + final String id; |
| 140 | + final boolean live; |
| 141 | + final boolean currentMaster; |
| 142 | + final boolean inCurrentConfig; |
| 143 | + |
| 144 | + VotingConfigNode(String id, boolean live, boolean currentMaster, boolean inCurrentConfig) { |
| 145 | + this.id = id; |
| 146 | + this.live = live; |
| 147 | + this.currentMaster = currentMaster; |
| 148 | + this.inCurrentConfig = inCurrentConfig; |
| 149 | + } |
| 150 | + |
| 151 | + @Override |
| 152 | + public int compareTo(VotingConfigNode other) { |
| 153 | + // prefer nodes that are live |
| 154 | + final int liveComp = Boolean.compare(other.live, live); |
| 155 | + if (liveComp != 0) { |
| 156 | + return liveComp; |
| 157 | + } |
| 158 | + // prefer nodes that are in current config for stability |
| 159 | + final int inCurrentConfigComp = Boolean.compare(other.inCurrentConfig, inCurrentConfig); |
| 160 | + if (inCurrentConfigComp != 0) { |
| 161 | + return inCurrentConfigComp; |
| 162 | + } |
| 163 | + // prefer current master |
| 164 | + final int currentMasterComp = Boolean.compare(other.currentMaster, currentMaster); |
| 165 | + if (currentMasterComp != 0) { |
| 166 | + return currentMasterComp; |
| 167 | + } |
| 168 | + // tiebreak by node id to have stable ordering |
| 169 | + return id.compareTo(other.id); |
| 170 | + } |
| 171 | + |
| 172 | + @Override |
| 173 | + public String toString() { |
| 174 | + return "VotingConfigNode{" + |
| 175 | + "id='" + id + '\'' + |
| 176 | + ", live=" + live + |
| 177 | + ", currentMaster=" + currentMaster + |
| 178 | + ", inCurrentConfig=" + inCurrentConfig + |
| 179 | + '}'; |
| 180 | + } |
| 181 | + } |
182 | 182 | }
|
0 commit comments