github.com/m3db/m3@v1.5.0/src/cluster/placement/selector/non_mirrored.go (about) 1 // Copyright (c) 2017 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package selector 22 23 import ( 24 "errors" 25 "fmt" 26 "sort" 27 28 "github.com/m3db/m3/src/cluster/placement" 29 "github.com/m3db/m3/src/cluster/placement/algo" 30 ) 31 32 var ( 33 errInstanceAbsent = errors.New("could not remove or replace a instance that does not exist") 34 errNoValidInstance = errors.New("no valid instance in the candidate list") 35 ) 36 37 type nonMirroredSelector struct { 38 opts placement.Options 39 } 40 41 // NewNonMirroredSelector constructs an instance selector which doesn't mirror traffic 42 // (no shardsets) and which takes into account existing 43 // shard placement and instance weight in order to choose instances. 44 func NewNonMirroredSelector(opts placement.Options) placement.InstanceSelector { 45 return &nonMirroredSelector{opts: opts} 46 } 47 48 func (f *nonMirroredSelector) SelectInitialInstances( 49 candidates []placement.Instance, 50 rf int, 51 ) ([]placement.Instance, error) { 52 return getValidCandidates(placement.NewPlacement(), candidates, f.opts) 53 } 54 55 func (f *nonMirroredSelector) SelectAddingInstances( 56 candidates []placement.Instance, 57 p placement.Placement, 58 ) ([]placement.Instance, error) { 59 candidates, err := getValidCandidates(p, candidates, f.opts) 60 if err != nil { 61 return nil, err 62 } 63 64 if f.opts.AddAllCandidates() { 65 return candidates, nil 66 } 67 68 instance, err := selectSingleCandidate(candidates, p) 69 if err != nil { 70 return nil, err 71 } 72 return []placement.Instance{instance}, nil 73 } 74 75 func (f *nonMirroredSelector) SelectReplaceInstances( 76 candidates []placement.Instance, 77 leavingInstanceIDs []string, 78 p placement.Placement, 79 ) ([]placement.Instance, error) { 80 candidates, err := getValidCandidates(p, candidates, f.opts) 81 if err != nil { 82 return nil, err 83 } 84 85 leavingInstances, err := getLeavingInstances(p, leavingInstanceIDs) 86 if err != nil { 87 return nil, err 88 } 89 90 if f.opts.AddAllCandidates() { 91 if err := f.validateReplaceInstances(candidates, leavingInstances); err != nil { 92 return nil, err 93 } 94 return candidates, nil 95 } 96 97 return f.selectReplaceInstances(candidates, leavingInstances, p) 98 } 99 100 // selectReplaceInstances returns a sufficient number of instances to replace the weight 101 // of the leaving instances. 102 func (f *nonMirroredSelector) selectReplaceInstances( 103 candidates, leavingInstances []placement.Instance, 104 p placement.Placement, 105 ) ([]placement.Instance, error) { 106 // Map isolation group to instances. 107 candidateGroups := buildIsolationGroupMap(candidates) 108 109 // Sort the candidate instances by the number of conflicts. 110 ph := algo.NewPlacementHelper(p, f.opts) 111 instances := make([]sortableValue, 0, len(candidateGroups)) 112 for group, instancesInGroup := range candidateGroups { 113 conflicts := 0 114 for _, leaving := range leavingInstances { 115 for _, s := range leaving.Shards().All() { 116 if !ph.CanMoveShard(s.ID(), leaving, group) { 117 conflicts++ 118 } 119 } 120 } 121 for _, instance := range instancesInGroup { 122 instances = append(instances, sortableValue{value: instance, weight: conflicts}) 123 } 124 } 125 126 groups := groupInstancesByConflict(instances, f.opts) 127 if len(groups) == 0 { 128 return nil, errNoValidInstance 129 } 130 131 var totalWeight uint32 132 for _, instance := range leavingInstances { 133 totalWeight += instance.Weight() 134 } 135 result, leftWeight := fillWeight(groups, int(totalWeight)) 136 137 if leftWeight > 0 && !f.opts.AllowPartialReplace() { 138 return nil, fmt.Errorf("could not find enough instances to replace %v, %d weight could not be replaced", 139 leavingInstances, leftWeight) 140 } 141 return result, nil 142 } 143 144 func (f *nonMirroredSelector) validateReplaceInstances( 145 candidates, leavingInstances []placement.Instance, 146 ) error { 147 var leavingWeight int 148 for _, instance := range leavingInstances { 149 leavingWeight += int(instance.Weight()) 150 } 151 var candidateWeight int 152 for _, instance := range candidates { 153 candidateWeight += int(instance.Weight()) 154 } 155 156 if leavingWeight > candidateWeight && !f.opts.AllowPartialReplace() { 157 return fmt.Errorf("could not find enough instances to replace %v, %d weight could not be replaced", 158 leavingInstances, leavingWeight) 159 } 160 return nil 161 } 162 163 func groupInstancesByConflict(instancesSortedByConflicts []sortableValue, opts placement.Options) [][]placement.Instance { 164 allowConflict := opts.AllowPartialReplace() 165 sort.Sort(sortableValues(instancesSortedByConflicts)) 166 var groups [][]placement.Instance 167 lastSeenConflict := -1 168 for _, instance := range instancesSortedByConflicts { 169 if !allowConflict && instance.weight > 0 { 170 break 171 } 172 if instance.weight > lastSeenConflict { 173 lastSeenConflict = instance.weight 174 groups = append(groups, []placement.Instance{}) 175 } 176 if lastSeenConflict == instance.weight { 177 groups[len(groups)-1] = append(groups[len(groups)-1], instance.value.(placement.Instance)) 178 } 179 } 180 return groups 181 } 182 183 func fillWeight(groups [][]placement.Instance, targetWeight int) ([]placement.Instance, int) { 184 var ( 185 result []placement.Instance 186 instancesInGroup []placement.Instance 187 ) 188 for _, group := range groups { 189 sort.Sort(placement.ByIDAscending(group)) 190 instancesInGroup, targetWeight = knapsack(group, targetWeight) 191 result = append(result, instancesInGroup...) 192 if targetWeight <= 0 { 193 break 194 } 195 } 196 return result, targetWeight 197 } 198 199 func knapsack(instances []placement.Instance, targetWeight int) ([]placement.Instance, int) { 200 totalWeight := 0 201 for _, instance := range instances { 202 totalWeight += int(instance.Weight()) 203 } 204 if totalWeight <= targetWeight { 205 return instances[:], targetWeight - totalWeight 206 } 207 // totalWeight > targetWeight, there is a combination of instances to meet targetWeight for sure 208 // we do dp until totalWeight rather than targetWeight here because we need to 209 // at least cover the targetWeight, which is a little bit different than the knapsack problem 210 weights := make([]int, totalWeight+1) 211 combination := make([][]placement.Instance, totalWeight+1) 212 213 // dp: weights[i][j] = max(weights[i-1][j], weights[i-1][j-instance.Weight] + instance.Weight) 214 // when there are multiple combination to reach a target weight, we prefer the one with less instances 215 for i := range instances { 216 weight := int(instances[i].Weight()) 217 // this loop needs to go from len to 1 because weights is being updated in place 218 for j := totalWeight; j >= 1; j-- { 219 if j-weight < 0 { 220 continue 221 } 222 newWeight := weights[j-weight] + weight 223 if newWeight > weights[j] { 224 weights[j] = weights[j-weight] + weight 225 combination[j] = append(combination[j-weight], instances[i]) 226 } else if newWeight == weights[j] { 227 // if can reach same weight, find a combination with less instances 228 if len(combination[j-weight])+1 < len(combination[j]) { 229 combination[j] = append(combination[j-weight], instances[i]) 230 } 231 } 232 } 233 } 234 235 for i := targetWeight; i <= totalWeight; i++ { 236 if weights[i] >= targetWeight { 237 return combination[i], targetWeight - weights[i] 238 } 239 } 240 241 panic("should never reach here") 242 } 243 244 func buildIsolationGroupMap(candidates []placement.Instance) map[string][]placement.Instance { 245 result := make(map[string][]placement.Instance, len(candidates)) 246 for _, instance := range candidates { 247 if _, exist := result[instance.IsolationGroup()]; !exist { 248 result[instance.IsolationGroup()] = make([]placement.Instance, 0) 249 } 250 result[instance.IsolationGroup()] = append(result[instance.IsolationGroup()], instance) 251 } 252 return result 253 } 254 255 func selectSingleCandidate( 256 candidates []placement.Instance, 257 p placement.Placement, 258 ) (placement.Instance, error) { 259 candidateGroups := buildIsolationGroupMap(candidates) 260 existingGroups := buildIsolationGroupMap(p.Instances()) 261 262 // If there is an isolation group not in the current placement, prefer the isolation group. 263 for r, instances := range candidateGroups { 264 if _, exist := existingGroups[r]; !exist { 265 // All the isolation groups have at least 1 instance. 266 return instances[0], nil 267 } 268 } 269 270 // Otherwise sort the isolation groups in the current placement 271 // by capacity and find a instance from least sized isolation group. 272 groups := make(sortableValues, 0, len(existingGroups)) 273 for group, instances := range existingGroups { 274 weight := 0 275 for _, i := range instances { 276 weight += int(i.Weight()) 277 } 278 groups = append(groups, sortableValue{value: group, weight: weight}) 279 } 280 sort.Sort(groups) 281 282 for _, group := range groups { 283 if i, exist := candidateGroups[group.value.(string)]; exist { 284 for _, instance := range i { 285 return instance, nil 286 } 287 } 288 } 289 290 // no instance in the candidate instances can be added to the placement 291 return nil, errNoValidInstance 292 } 293 294 type sortableValue struct { 295 value interface{} 296 weight int 297 } 298 299 type sortableValues []sortableValue 300 301 func (values sortableValues) Len() int { 302 return len(values) 303 } 304 305 func (values sortableValues) Less(i, j int) bool { 306 return values[i].weight < values[j].weight 307 } 308 309 func (values sortableValues) Swap(i, j int) { 310 values[i], values[j] = values[j], values[i] 311 }