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