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  }