github.com/matrixorigin/matrixone@v1.2.0/pkg/hakeeper/checkers/dnservice/check.go (about)

     1  // Copyright 2021 - 2022 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package dnservice
    16  
    17  import (
    18  	"sort"
    19  
    20  	"go.uber.org/zap"
    21  
    22  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    23  	"github.com/matrixorigin/matrixone/pkg/common/runtime"
    24  	"github.com/matrixorigin/matrixone/pkg/hakeeper"
    25  	"github.com/matrixorigin/matrixone/pkg/hakeeper/checkers/util"
    26  	"github.com/matrixorigin/matrixone/pkg/hakeeper/operator"
    27  	pb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
    28  )
    29  
    30  var (
    31  	// waitingShards makes check logic stateful.
    32  	waitingShards *initialShards
    33  
    34  	// bootstrapping indicates the tn is bootstrapping.
    35  	// When tn checker finds a new tn shard should be added,
    36  	// it waits for a while if bootstrapping is false to avoid thrashing.
    37  	// If bootstrapping is true, tn checker will construct create tn shard command immediately.
    38  	// This flag helps to accelarate cluster bootstrapping.
    39  	bootstrapping bool
    40  )
    41  
    42  func init() {
    43  	waitingShards = newInitialShards()
    44  	bootstrapping = true
    45  }
    46  
    47  // Check checks tn state and generate operator for expired tn store.
    48  // The less shard ID, the higher priority.
    49  // NB: the returned order should be deterministic.
    50  func Check(
    51  	idAlloc util.IDAllocator,
    52  	cfg hakeeper.Config,
    53  	cluster pb.ClusterInfo,
    54  	tnState pb.TNState,
    55  	user pb.TaskTableUser,
    56  	currTick uint64,
    57  ) []*operator.Operator {
    58  	stores, reportedShards := parseTnState(cfg, tnState, currTick)
    59  	runtime.ProcessLevelRuntime().Logger().Debug("reported tn shards in cluster",
    60  		zap.Any("dn shard IDs", reportedShards.shardIDs),
    61  		zap.Any("dn shards", reportedShards.shards),
    62  	)
    63  	for _, node := range stores.ExpiredStores() {
    64  		runtime.ProcessLevelRuntime().Logger().Info("node is expired", zap.String("uuid", node.ID))
    65  	}
    66  	if len(stores.WorkingStores()) < 1 {
    67  		runtime.ProcessLevelRuntime().Logger().Warn("no working tn stores")
    68  		return nil
    69  	}
    70  
    71  	mapper := parseClusterInfo(cluster)
    72  
    73  	var operators []*operator.Operator
    74  
    75  	// 1. check reported tn state
    76  	operators = append(operators,
    77  		checkReportedState(reportedShards, mapper, stores.WorkingStores(), idAlloc)...,
    78  	)
    79  
    80  	// 2. check expected tn state
    81  	operators = append(operators,
    82  		checkInitiatingShards(reportedShards, mapper, stores.WorkingStores(), idAlloc, cluster, cfg, currTick)...,
    83  	)
    84  
    85  	if user.Username != "" {
    86  		for _, store := range stores.WorkingStores() {
    87  			if !tnState.Stores[store.ID].TaskServiceCreated {
    88  				operators = append(operators, operator.CreateTaskServiceOp("",
    89  					store.ID, pb.TNService, user))
    90  			}
    91  		}
    92  	}
    93  
    94  	return operators
    95  }
    96  
    97  // schedule generator operator as much as possible
    98  // NB: the returned order should be deterministic.
    99  func checkShard(shard *tnShard, mapper ShardMapper, workingStores []*util.Store, idAlloc util.IDAllocator) []operator.OpStep {
   100  	switch len(shard.workingReplicas()) {
   101  	case 0: // need add replica
   102  		newReplicaID, ok := idAlloc.Next()
   103  		if !ok {
   104  			runtime.ProcessLevelRuntime().Logger().Warn("fail to allocate replica ID")
   105  			return nil
   106  		}
   107  
   108  		target, err := consumeLeastSpareStore(workingStores)
   109  		if err != nil {
   110  			runtime.ProcessLevelRuntime().Logger().Warn("no working tn stores")
   111  			return nil
   112  		}
   113  
   114  		logShardID, err := mapper.getLogShardID(shard.shardID)
   115  		if err != nil {
   116  			runtime.ProcessLevelRuntime().Logger().Warn("shard not registered", zap.Uint64("ShardID", shard.shardID))
   117  			return nil
   118  		}
   119  
   120  		s := newAddStep(
   121  			target, shard.shardID, newReplicaID, logShardID,
   122  		)
   123  		runtime.ProcessLevelRuntime().Logger().Info(s.String())
   124  		return []operator.OpStep{s}
   125  
   126  	case 1: // ignore expired replicas
   127  		return nil
   128  
   129  	default: // remove extra working replicas
   130  		replicas := extraWorkingReplicas(shard)
   131  		steps := make([]operator.OpStep, 0, len(replicas))
   132  
   133  		logShardID, err := mapper.getLogShardID(shard.shardID)
   134  		if err != nil {
   135  			runtime.ProcessLevelRuntime().Logger().Warn("shard not registered", zap.Uint64("ShardID", shard.shardID))
   136  			return nil
   137  		}
   138  
   139  		for _, r := range replicas {
   140  			s := newRemoveStep(
   141  				r.storeID, r.shardID, r.replicaID, logShardID,
   142  			)
   143  			runtime.ProcessLevelRuntime().Logger().Info(s.String())
   144  			steps = append(steps, s)
   145  		}
   146  		return steps
   147  	}
   148  }
   149  
   150  // newAddStep constructs operator to launch a tn shard replica
   151  func newAddStep(target string, shardID, replicaID, logShardID uint64) operator.OpStep {
   152  	return operator.AddTnReplica{
   153  		StoreID:    target,
   154  		ShardID:    shardID,
   155  		ReplicaID:  replicaID,
   156  		LogShardID: logShardID,
   157  	}
   158  }
   159  
   160  // newRemoveStep constructs operator to remove a tn shard replica
   161  func newRemoveStep(target string, shardID, replicaID, logShardID uint64) operator.OpStep {
   162  	return operator.RemoveTnReplica{
   163  		StoreID:    target,
   164  		ShardID:    shardID,
   165  		ReplicaID:  replicaID,
   166  		LogShardID: logShardID,
   167  	}
   168  }
   169  
   170  // expiredReplicas return all expired replicas.
   171  // NB: the returned order should be deterministic.
   172  func expiredReplicas(shard *tnShard) []*tnReplica {
   173  	expired := shard.expiredReplicas()
   174  	// less replica first
   175  	sort.Slice(expired, func(i, j int) bool {
   176  		return expired[i].replicaID < expired[j].replicaID
   177  	})
   178  	return expired
   179  }
   180  
   181  // extraWorkingReplicas return all working replicas except the largest.
   182  // NB: the returned order should be deterministic.
   183  func extraWorkingReplicas(shard *tnShard) []*tnReplica {
   184  	working := shard.workingReplicas()
   185  	if len(working) == 0 {
   186  		return working
   187  	}
   188  
   189  	// less replica first
   190  	sort.Slice(working, func(i, j int) bool {
   191  		return working[i].replicaID < working[j].replicaID
   192  	})
   193  
   194  	return working[0 : len(working)-1]
   195  }
   196  
   197  // consumeLeastSpareStore consume a slot from the least spare tn store.
   198  // If there are multiple tn store with the same least slots,
   199  // the store with less ID would be chosen.
   200  // NB: the returned result should be deterministic.
   201  func consumeLeastSpareStore(working []*util.Store) (string, error) {
   202  	if len(working) == 0 {
   203  		return "", moerr.NewNoWorkingStoreNoCtx()
   204  	}
   205  
   206  	// the least shards, the higher priority
   207  	sort.Slice(working, func(i, j int) bool {
   208  		return working[i].Length < working[j].Length
   209  	})
   210  
   211  	// stores with the same Length
   212  	var leastStores []*util.Store
   213  
   214  	least := working[0].Length
   215  	for i := 0; i < len(working); i++ {
   216  		store := working[i]
   217  		if least != store.Length {
   218  			break
   219  		}
   220  		leastStores = append(leastStores, store)
   221  	}
   222  	sort.Slice(leastStores, func(i, j int) bool {
   223  		return leastStores[i].ID < leastStores[j].ID
   224  	})
   225  
   226  	// consume a slot from this tn store
   227  	leastStores[0].Length += 1
   228  
   229  	return leastStores[0].ID, nil
   230  }