github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/cluster/shard/shard.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 shard 22 23 import ( 24 "errors" 25 "fmt" 26 "sort" 27 "strconv" 28 "strings" 29 30 "github.com/m3db/m3/src/cluster/generated/proto/placementpb" 31 32 "github.com/gogo/protobuf/types" 33 ) 34 35 var ( 36 errInvalidProtoShardState = errors.New("invalid proto shard state") 37 38 defaultShardState State 39 defaultShardStateProto placementpb.ShardState 40 ) 41 42 // NewShardStateFromProto creates new shard state from proto. 43 func NewShardStateFromProto(state placementpb.ShardState) (State, error) { 44 switch state { 45 case placementpb.ShardState_INITIALIZING: 46 return Initializing, nil 47 case placementpb.ShardState_AVAILABLE: 48 return Available, nil 49 case placementpb.ShardState_LEAVING: 50 return Leaving, nil 51 default: 52 return defaultShardState, errInvalidProtoShardState 53 } 54 } 55 56 // Proto returns the proto representation for the shard state. 57 func (s State) Proto() (placementpb.ShardState, error) { 58 switch s { 59 case Initializing: 60 return placementpb.ShardState_INITIALIZING, nil 61 case Available: 62 return placementpb.ShardState_AVAILABLE, nil 63 case Leaving: 64 return placementpb.ShardState_LEAVING, nil 65 default: 66 return defaultShardStateProto, errInvalidProtoShardState 67 } 68 } 69 70 // NewShard returns a new Shard 71 func NewShard(id uint32) Shard { return &shard{id: id, state: Unknown} } 72 73 // NewShardFromProto create a new shard from proto. 74 func NewShardFromProto(spb *placementpb.Shard) (Shard, error) { 75 state, err := NewShardStateFromProto(spb.State) 76 if err != nil { 77 return nil, err 78 } 79 80 var redirectToShardID *uint32 81 if spb.RedirectToShardId != nil { 82 redirectToShardID = new(uint32) 83 *redirectToShardID = spb.RedirectToShardId.Value 84 } 85 86 return &shard{ 87 id: spb.Id, 88 redirectToShardID: redirectToShardID, 89 state: state, 90 sourceID: spb.SourceId, 91 cutoverNanos: spb.CutoverNanos, 92 cutoffNanos: spb.CutoffNanos, 93 }, nil 94 } 95 96 type shard struct { 97 id uint32 98 redirectToShardID *uint32 99 100 state State 101 sourceID string 102 cutoverNanos int64 103 cutoffNanos int64 104 } 105 106 func (s *shard) ID() uint32 { return s.id } 107 func (s *shard) State() State { return s.state } 108 func (s *shard) SetState(state State) Shard { s.state = state; return s } 109 func (s *shard) SourceID() string { return s.sourceID } 110 func (s *shard) SetSourceID(sourceID string) Shard { s.sourceID = sourceID; return s } 111 func (s *shard) CutoverNanos() int64 { 112 if s.cutoverNanos != UnInitializedValue { 113 return s.cutoverNanos 114 } 115 116 // NB(xichen): if the value is not set, we return the default cutover nanos. 117 return DefaultShardCutoverNanos 118 } 119 120 func (s *shard) SetCutoverNanos(value int64) Shard { 121 // NB(cw): We use UnInitializedValue to represent the DefaultShardCutoverNanos 122 // so that we can save some space in the proto representation for the 123 // default value of cutover time. 124 if value == DefaultShardCutoverNanos { 125 value = UnInitializedValue 126 } 127 128 s.cutoverNanos = value 129 return s 130 } 131 132 func (s *shard) CutoffNanos() int64 { 133 if s.cutoffNanos != UnInitializedValue { 134 return s.cutoffNanos 135 } 136 137 // NB(xichen): if the value is not set, we return the default cutoff nanos. 138 return DefaultShardCutoffNanos 139 } 140 141 func (s *shard) SetCutoffNanos(value int64) Shard { 142 // NB(cw): We use UnInitializedValue to represent the DefaultShardCutoffNanos 143 // so that we can save some space in the proto representation for the 144 // default value of cutoff time. 145 if value == DefaultShardCutoffNanos { 146 value = UnInitializedValue 147 } 148 149 s.cutoffNanos = value 150 return s 151 } 152 153 func (s *shard) RedirectToShardID() *uint32 { 154 return s.redirectToShardID 155 } 156 157 // SetRedirectToShardID sets optional shard to redirect incoming writes to. 158 func (s *shard) SetRedirectToShardID(id *uint32) Shard { 159 s.redirectToShardID = nil 160 if id != nil { 161 s.redirectToShardID = new(uint32) 162 *s.redirectToShardID = *id 163 } 164 return s 165 } 166 167 func (s *shard) Equals(other Shard) bool { 168 return s.ID() == other.ID() && 169 s.State() == other.State() && 170 s.SourceID() == other.SourceID() && 171 s.CutoverNanos() == other.CutoverNanos() && 172 s.CutoffNanos() == other.CutoffNanos() && 173 ((s.RedirectToShardID() == nil && other.RedirectToShardID() == nil) || 174 (s.RedirectToShardID() != nil && other.RedirectToShardID() != nil && 175 *s.RedirectToShardID() == *other.RedirectToShardID())) 176 } 177 178 func (s *shard) Proto() (*placementpb.Shard, error) { 179 ss, err := s.state.Proto() 180 if err != nil { 181 return nil, err 182 } 183 184 var redirectToShardID *types.UInt32Value 185 if s.redirectToShardID != nil { 186 redirectToShardID = &types.UInt32Value{Value: *s.redirectToShardID} 187 } 188 189 return &placementpb.Shard{ 190 Id: s.id, 191 RedirectToShardId: redirectToShardID, 192 State: ss, 193 SourceId: s.sourceID, 194 CutoverNanos: s.cutoverNanos, 195 CutoffNanos: s.cutoffNanos, 196 }, nil 197 } 198 199 func (s *shard) Clone() Shard { 200 if s == nil { 201 return nil 202 } 203 clone := *s 204 return &clone 205 } 206 207 // SortableShardsByIDAsc are sortable shards by ID in ascending order 208 type SortableShardsByIDAsc []Shard 209 210 func (s SortableShardsByIDAsc) Len() int { return len(s) } 211 func (s SortableShardsByIDAsc) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 212 func (s SortableShardsByIDAsc) Less(i, j int) bool { 213 return s[i].ID() < s[j].ID() 214 } 215 216 // SortableIDsAsc are sortable shard IDs in ascending order 217 type SortableIDsAsc []uint32 218 219 func (s SortableIDsAsc) Len() int { return len(s) } 220 func (s SortableIDsAsc) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 221 func (s SortableIDsAsc) Less(i, j int) bool { 222 return s[i] < s[j] 223 } 224 225 // NewShards creates a new instance of Shards 226 func NewShards(ss []Shard) Shards { 227 // deduplicate first, last one wins 228 shardMap := make(map[uint32]Shard, len(ss)) 229 for _, s := range ss { 230 shardMap[s.ID()] = s 231 } 232 233 shrds := make([]Shard, 0, len(shardMap)) 234 for _, s := range shardMap { 235 shrds = append(shrds, s) 236 } 237 238 sort.Sort(SortableShardsByIDAsc(shrds)) 239 240 return &shards{ 241 shards: shrds, 242 shardMap: shardMap, 243 } 244 } 245 246 // NewShardsFromProto creates a new set of shards from proto. 247 func NewShardsFromProto(shards []*placementpb.Shard) (Shards, error) { 248 allShards := make([]Shard, 0, len(shards)) 249 for _, s := range shards { 250 shard, err := NewShardFromProto(s) 251 if err != nil { 252 return nil, err 253 } 254 allShards = append(allShards, shard) 255 } 256 return NewShards(allShards), nil 257 } 258 259 type shards struct { 260 shards []Shard 261 shardMap map[uint32]Shard 262 } 263 264 func (ss *shards) All() []Shard { 265 shards := make([]Shard, len(ss.shards)) 266 copy(shards, ss.shards) 267 268 return shards 269 } 270 271 func (ss *shards) AllIDs() []uint32 { 272 shardIDs := make([]uint32, 0, len(ss.shards)) 273 for _, shrd := range ss.shards { 274 shardIDs = append(shardIDs, shrd.ID()) 275 } 276 277 return shardIDs 278 } 279 280 func (ss *shards) NumShards() int { 281 return len(ss.shards) 282 } 283 284 func (ss *shards) Shard(id uint32) (Shard, bool) { 285 shard, ok := ss.shardMap[id] 286 if !ok { 287 return nil, false 288 } 289 290 return shard, true 291 } 292 293 func (ss *shards) Add(shard Shard) { 294 id := shard.ID() 295 // we keep a sorted slice of shards, do a binary search to either find the index 296 // of an existing shard for replacement, or the target index position 297 i := sort.Search(len(ss.shards), func(i int) bool { return ss.shards[i].ID() >= id }) 298 if i < len(ss.shards) && ss.shards[i].ID() == id { 299 ss.shards[i] = shard 300 ss.shardMap[id] = shard 301 return 302 } 303 304 // extend the sorted shard slice by 1 305 ss.shards = append(ss.shards, shard) 306 ss.shardMap[id] = shard 307 308 // target position was at the end, so extending with the new shard was enough 309 if i >= len(ss.shards)-1 { 310 return 311 } 312 313 // if not, copy over all slice elements shifted by 1 and overwrite data at index 314 copy(ss.shards[i+1:], ss.shards[i:]) 315 ss.shards[i] = shard 316 } 317 318 func (ss *shards) Remove(id uint32) { 319 // we keep a sorted slice of shards, do a binary search to find the index 320 i := sort.Search(len(ss.shards), func(i int) bool { return ss.shards[i].ID() >= id }) 321 if i < len(ss.shards) && ss.shards[i].ID() == id { 322 delete(ss.shardMap, id) 323 // shift all other elements back after removal 324 ss.shards = ss.shards[:i+copy(ss.shards[i:], ss.shards[i+1:])] 325 } 326 } 327 328 func (ss *shards) Contains(shard uint32) bool { 329 _, ok := ss.shardMap[shard] 330 return ok 331 } 332 333 func (ss *shards) NumShardsForState(state State) int { 334 count := 0 335 for _, s := range ss.shards { 336 if s.State() == state { 337 count++ 338 } 339 } 340 return count 341 } 342 343 func (ss *shards) ShardsForState(state State) []Shard { 344 r := make([]Shard, 0, len(ss.shards)) 345 for _, s := range ss.shards { 346 if s.State() == state { 347 r = append(r, s) 348 } 349 } 350 return r 351 } 352 353 func (ss *shards) Equals(other Shards) bool { 354 if len(ss.shards) != other.NumShards() { 355 return false 356 } 357 358 otherShards := other.All() 359 for i, shard := range ss.shards { 360 otherShard := otherShards[i] 361 if !shard.Equals(otherShard) { 362 return false 363 } 364 } 365 return true 366 } 367 368 func (ss *shards) String() string { 369 var strs []string 370 for _, state := range validStates() { 371 shardsInState := ss.ShardsForState(state) 372 idStrs := make([]string, 0, len(shardsInState)) 373 for _, shard := range shardsInState { 374 var idStr string 375 if shard.RedirectToShardID() != nil { 376 idStr = fmt.Sprintf("%d -> %d", shard.ID(), *shard.RedirectToShardID()) 377 } else { 378 idStr = strconv.Itoa(int(shard.ID())) 379 } 380 idStrs = append(idStrs, idStr) 381 } 382 str := fmt.Sprintf("%s=%v", state.String(), idStrs) 383 strs = append(strs, str) 384 } 385 return fmt.Sprintf("[%s]", strings.Join(strs, ", ")) 386 } 387 388 func (ss *shards) Proto() ([]*placementpb.Shard, error) { 389 res := make([]*placementpb.Shard, 0, len(ss.shards)) 390 for _, shard := range ss.shards { 391 sp, err := shard.Proto() 392 if err != nil { 393 return nil, err 394 } 395 res = append(res, sp) 396 } 397 398 return res, nil 399 } 400 401 func (ss *shards) Clone() Shards { 402 shrds := make([]Shard, 0, len(ss.shards)) 403 shardMap := make(map[uint32]Shard, len(ss.shards)) 404 405 for _, shrd := range ss.shards { 406 cloned := shrd.Clone() 407 shrds = append(shrds, cloned) 408 shardMap[shrd.ID()] = cloned 409 } 410 411 return &shards{ 412 shards: shrds, 413 shardMap: shardMap, 414 } 415 } 416 417 // validStates returns all the valid states. 418 func validStates() []State { 419 return []State{ 420 Initializing, 421 Available, 422 Leaving, 423 } 424 }