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  }