vitess.io/vitess@v0.16.2/go/vt/topotools/split.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package topotools 18 19 import ( 20 "errors" 21 "fmt" 22 "sort" 23 24 "context" 25 26 "vitess.io/vitess/go/vt/key" 27 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 28 "vitess.io/vitess/go/vt/topo" 29 ) 30 31 // ValidateForReshard returns an error if sourceShards cannot reshard into 32 // targetShards. 33 func ValidateForReshard(sourceShards, targetShards []*topo.ShardInfo) error { 34 for _, source := range sourceShards { 35 for _, target := range targetShards { 36 if key.KeyRangeEqual(source.KeyRange, target.KeyRange) { 37 return fmt.Errorf("same keyrange is present in source and target: %v", key.KeyRangeString(source.KeyRange)) 38 } 39 } 40 } 41 sourcekr, err := combineKeyRanges(sourceShards) 42 if err != nil { 43 return err 44 } 45 targetkr, err := combineKeyRanges(targetShards) 46 if err != nil { 47 return err 48 } 49 if !key.KeyRangeEqual(sourcekr, targetkr) { 50 return fmt.Errorf("source and target keyranges don't match: %v vs %v", key.KeyRangeString(sourcekr), key.KeyRangeString(targetkr)) 51 } 52 return nil 53 } 54 55 func combineKeyRanges(shards []*topo.ShardInfo) (*topodatapb.KeyRange, error) { 56 if len(shards) == 0 { 57 return nil, fmt.Errorf("there are no shards to combine") 58 } 59 result := shards[0].KeyRange 60 krmap := make(map[string]*topodatapb.KeyRange) 61 for _, si := range shards[1:] { 62 krmap[si.ShardName()] = si.KeyRange 63 } 64 for len(krmap) != 0 { 65 foundOne := false 66 for k, kr := range krmap { 67 newkr, ok := key.KeyRangeAdd(result, kr) 68 if ok { 69 foundOne = true 70 result = newkr 71 delete(krmap, k) 72 } 73 } 74 if !foundOne { 75 return nil, errors.New("shards don't form a contiguous keyrange") 76 } 77 } 78 return result, nil 79 } 80 81 // OverlappingShards contains sets of shards that overlap which each-other. 82 // With this library, there is no guarantee of which set will be left or right. 83 type OverlappingShards struct { 84 Left []*topo.ShardInfo 85 Right []*topo.ShardInfo 86 } 87 88 // ContainsShard returns true if either Left or Right lists contain 89 // the provided Shard. 90 func (os *OverlappingShards) ContainsShard(shardName string) bool { 91 for _, l := range os.Left { 92 if l.ShardName() == shardName { 93 return true 94 } 95 } 96 for _, r := range os.Right { 97 if r.ShardName() == shardName { 98 return true 99 } 100 } 101 return false 102 } 103 104 // OverlappingShardsForShard returns the OverlappingShards object 105 // from the list that has he provided shard, or nil 106 func OverlappingShardsForShard(os []*OverlappingShards, shardName string) *OverlappingShards { 107 for _, o := range os { 108 if o.ContainsShard(shardName) { 109 return o 110 } 111 } 112 return nil 113 } 114 115 // FindOverlappingShards will return an array of OverlappingShards 116 // for the provided keyspace. 117 // We do not support more than two overlapping shards (for instance, 118 // having 40-80, 40-60 and 40-50 in the same keyspace is not supported and 119 // will return an error). 120 // If shards don't perfectly overlap, they are not returned. 121 func FindOverlappingShards(ctx context.Context, ts *topo.Server, keyspace string) ([]*OverlappingShards, error) { 122 shardMap, err := ts.FindAllShardsInKeyspace(ctx, keyspace) 123 if err != nil { 124 return nil, err 125 } 126 127 return findOverlappingShards(shardMap) 128 } 129 130 // findOverlappingShards does the work for FindOverlappingShards but 131 // can be called on test data too. 132 func findOverlappingShards(shardMap map[string]*topo.ShardInfo) ([]*OverlappingShards, error) { 133 134 var result []*OverlappingShards 135 136 for len(shardMap) > 0 { 137 var left []*topo.ShardInfo 138 var right []*topo.ShardInfo 139 140 // get the first value from the map, seed our left array with it 141 var name string 142 var si *topo.ShardInfo 143 for name, si = range shardMap { 144 break 145 } 146 left = append(left, si) 147 delete(shardMap, name) 148 149 // keep adding entries until we have no more to add 150 for { 151 foundOne := false 152 153 // try left to right 154 si := findIntersectingShard(shardMap, left) 155 if si != nil { 156 if intersect(si, right) { 157 return nil, fmt.Errorf("shard %v intersects with more than one shard, this is not supported", si.ShardName()) 158 } 159 foundOne = true 160 right = append(right, si) 161 } 162 163 // try right to left 164 si = findIntersectingShard(shardMap, right) 165 if si != nil { 166 if intersect(si, left) { 167 return nil, fmt.Errorf("shard %v intersects with more than one shard, this is not supported", si.ShardName()) 168 } 169 foundOne = true 170 left = append(left, si) 171 } 172 173 // we haven't found anything new, we're done 174 if !foundOne { 175 break 176 } 177 } 178 179 // save what we found if it's good 180 if len(right) > 0 { 181 // sort both lists 182 sort.Sort(shardInfoList(left)) 183 sort.Sort(shardInfoList(right)) 184 185 // we should not have holes on either side 186 hasHoles := false 187 for i := 0; i < len(left)-1; i++ { 188 if string(left[i].KeyRange.End) != string(left[i+1].KeyRange.Start) { 189 hasHoles = true 190 } 191 } 192 for i := 0; i < len(right)-1; i++ { 193 if string(right[i].KeyRange.End) != string(right[i+1].KeyRange.Start) { 194 hasHoles = true 195 } 196 } 197 if hasHoles { 198 continue 199 } 200 201 // the two sides should match 202 if !key.KeyRangeStartEqual(left[0].KeyRange, right[0].KeyRange) { 203 continue 204 } 205 if !key.KeyRangeEndEqual(left[len(left)-1].KeyRange, right[len(right)-1].KeyRange) { 206 continue 207 } 208 209 // all good, we have a valid overlap 210 result = append(result, &OverlappingShards{ 211 Left: left, 212 Right: right, 213 }) 214 } 215 } 216 return result, nil 217 } 218 219 // findIntersectingShard will go through the map and take the first 220 // entry in there that intersect with the source array, remove it from 221 // the map, and return it 222 func findIntersectingShard(shardMap map[string]*topo.ShardInfo, sourceArray []*topo.ShardInfo) *topo.ShardInfo { 223 for name, si := range shardMap { 224 for _, sourceShardInfo := range sourceArray { 225 if si.KeyRange == nil || sourceShardInfo.KeyRange == nil || key.KeyRangesIntersect(si.KeyRange, sourceShardInfo.KeyRange) { 226 delete(shardMap, name) 227 return si 228 } 229 } 230 } 231 return nil 232 } 233 234 // intersect returns true if the provided shard intersect with any shard 235 // in the destination array 236 func intersect(si *topo.ShardInfo, allShards []*topo.ShardInfo) bool { 237 for _, shard := range allShards { 238 if key.KeyRangesIntersect(si.KeyRange, shard.KeyRange) { 239 return true 240 } 241 } 242 return false 243 } 244 245 // shardInfoList is a helper type to sort ShardInfo array by keyrange 246 type shardInfoList []*topo.ShardInfo 247 248 // Len is part of sort.Interface 249 func (sil shardInfoList) Len() int { 250 return len(sil) 251 } 252 253 // Less is part of sort.Interface 254 func (sil shardInfoList) Less(i, j int) bool { 255 return string(sil[i].KeyRange.Start) < string(sil[j].KeyRange.Start) 256 } 257 258 // Swap is part of sort.Interface 259 func (sil shardInfoList) Swap(i, j int) { 260 sil[i], sil[j] = sil[j], sil[i] 261 }