github.com/m3db/m3@v1.5.0/src/cluster/placement/algo/sharded.go (about) 1 // Copyright (c) 2016 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 algo 22 23 import ( 24 "errors" 25 "fmt" 26 27 "github.com/m3db/m3/src/cluster/placement" 28 ) 29 30 var ( 31 errNotEnoughIsolationGroups = errors.New("not enough isolation groups to take shards, please make sure RF is less than number of isolation groups") 32 errIncompatibleWithShardedAlgo = errors.New("could not apply sharded algo on the placement") 33 ) 34 35 type shardedPlacementAlgorithm struct { 36 opts placement.Options 37 } 38 39 func newShardedAlgorithm(opts placement.Options) placement.Algorithm { 40 return shardedPlacementAlgorithm{opts: opts} 41 } 42 43 func (a shardedPlacementAlgorithm) IsCompatibleWith(p placement.Placement) error { 44 if !p.IsSharded() { 45 return errIncompatibleWithShardedAlgo 46 } 47 48 return nil 49 } 50 51 func (a shardedPlacementAlgorithm) InitialPlacement( 52 instances []placement.Instance, 53 shards []uint32, 54 rf int, 55 ) (placement.Placement, error) { 56 ph := newInitHelper(placement.Instances(instances).Clone(), shards, a.opts) 57 if err := ph.placeShards(newShards(shards), nil, ph.Instances()); err != nil { 58 return nil, err 59 } 60 61 var ( 62 p = ph.generatePlacement() 63 err error 64 ) 65 for i := 1; i < rf; i++ { 66 p, err = a.AddReplica(p) 67 if err != nil { 68 return nil, err 69 } 70 } 71 72 return tryCleanupShardState(p, a.opts) 73 } 74 75 func (a shardedPlacementAlgorithm) AddReplica(p placement.Placement) (placement.Placement, error) { 76 if err := a.IsCompatibleWith(p); err != nil { 77 return nil, err 78 } 79 80 p = p.Clone() 81 ph := newAddReplicaHelper(p, a.opts) 82 if err := ph.placeShards(newShards(p.Shards()), nil, nonLeavingInstances(ph.Instances())); err != nil { 83 return nil, err 84 } 85 86 if err := ph.optimize(safe); err != nil { 87 return nil, err 88 } 89 90 return tryCleanupShardState(ph.generatePlacement(), a.opts) 91 } 92 93 func (a shardedPlacementAlgorithm) RemoveInstances( 94 p placement.Placement, 95 instanceIDs []string, 96 ) (placement.Placement, error) { 97 if err := a.IsCompatibleWith(p); err != nil { 98 return nil, err 99 } 100 101 p = p.Clone() 102 for _, instanceID := range instanceIDs { 103 ph, leavingInstance, err := newRemoveInstanceHelper(p, instanceID, a.opts) 104 if err != nil { 105 return nil, err 106 } 107 // place the shards from the leaving instance to the rest of the cluster 108 if err := ph.placeShards(leavingInstance.Shards().All(), leavingInstance, ph.Instances()); err != nil { 109 return nil, err 110 } 111 112 if err := ph.optimize(safe); err != nil { 113 return nil, err 114 } 115 116 if p, _, err = addInstanceToPlacement(ph.generatePlacement(), leavingInstance, withShards); err != nil { 117 return nil, err 118 } 119 } 120 return tryCleanupShardState(p, a.opts) 121 } 122 123 func (a shardedPlacementAlgorithm) AddInstances( 124 p placement.Placement, 125 instances []placement.Instance, 126 ) (placement.Placement, error) { 127 if err := a.IsCompatibleWith(p); err != nil { 128 return nil, err 129 } 130 131 p = p.Clone() 132 for _, instance := range instances { 133 ph, addingInstance, err := newAddInstanceHelper(p, instance, a.opts, withLeavingShardsOnly) 134 if err != nil { 135 return nil, err 136 } 137 138 if err := ph.addInstance(addingInstance); err != nil { 139 return nil, err 140 } 141 142 p = ph.generatePlacement() 143 } 144 145 return tryCleanupShardState(p, a.opts) 146 } 147 148 func (a shardedPlacementAlgorithm) ReplaceInstances( 149 p placement.Placement, 150 leavingInstanceIDs []string, 151 addingInstances []placement.Instance, 152 ) (placement.Placement, error) { 153 if err := a.IsCompatibleWith(p); err != nil { 154 return nil, err 155 } 156 157 p = p.Clone() 158 ph, leavingInstances, addingInstances, err := newReplaceInstanceHelper(p, leavingInstanceIDs, addingInstances, a.opts) 159 if err != nil { 160 return nil, err 161 } 162 163 for _, leavingInstance := range leavingInstances { 164 err = ph.placeShards(leavingInstance.Shards().All(), leavingInstance, addingInstances) 165 if err != nil && err != errNotEnoughIsolationGroups { 166 // errNotEnoughIsolationGroups means the adding instances do not 167 // have enough isolation groups to take all the shards, but the rest 168 // instances might have more isolation groups to take all the shards. 169 return nil, err 170 } 171 load := loadOnInstance(leavingInstance) 172 if load != 0 && !a.opts.AllowPartialReplace() { 173 return nil, fmt.Errorf("could not fully replace all shards from %s, %d shards left unassigned", 174 leavingInstance.ID(), load) 175 } 176 } 177 178 if a.opts.AllowPartialReplace() { 179 // Place the shards left on the leaving instance to the rest of the cluster. 180 for _, leavingInstance := range leavingInstances { 181 if err = ph.placeShards(leavingInstance.Shards().All(), leavingInstance, ph.Instances()); err != nil { 182 return nil, err 183 } 184 } 185 186 if err := ph.optimize(unsafe); err != nil { 187 return nil, err 188 } 189 } 190 191 p = ph.generatePlacement() 192 for _, leavingInstance := range leavingInstances { 193 if p, _, err = addInstanceToPlacement(p, leavingInstance, withShards); err != nil { 194 return nil, err 195 } 196 } 197 return tryCleanupShardState(p, a.opts) 198 } 199 200 func (a shardedPlacementAlgorithm) MarkShardsAvailable( 201 p placement.Placement, 202 instanceID string, 203 shardIDs ...uint32, 204 ) (placement.Placement, error) { 205 if err := a.IsCompatibleWith(p); err != nil { 206 return nil, err 207 } 208 209 return markShardsAvailable(p.Clone(), instanceID, shardIDs, a.opts) 210 } 211 212 func (a shardedPlacementAlgorithm) MarkAllShardsAvailable( 213 p placement.Placement, 214 ) (placement.Placement, bool, error) { 215 if err := a.IsCompatibleWith(p); err != nil { 216 return nil, false, err 217 } 218 219 return markAllShardsAvailable(p, a.opts) 220 } 221 222 func (a shardedPlacementAlgorithm) BalanceShards( 223 p placement.Placement, 224 ) (placement.Placement, error) { 225 ph := newHelper(p, p.ReplicaFactor(), a.opts) 226 if err := ph.optimize(unsafe); err != nil { 227 return nil, fmt.Errorf("shard balance optimization failed: %w", err) 228 } 229 230 return tryCleanupShardState(ph.generatePlacement(), a.opts) 231 }