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 }