github.com/matrixorigin/matrixone@v1.2.0/pkg/hakeeper/checkers/logservice/parse.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 logservice 16 17 import ( 18 "sort" 19 20 "github.com/matrixorigin/matrixone/pkg/hakeeper" 21 pb "github.com/matrixorigin/matrixone/pkg/pb/logservice" 22 "github.com/matrixorigin/matrixone/pkg/pb/metadata" 23 ) 24 25 type fixingShard struct { 26 shardID uint64 27 replicas map[uint64]string 28 toAdd uint32 29 } 30 31 func newFixingShard(origin pb.LogShardInfo) *fixingShard { 32 shard := &fixingShard{ 33 shardID: origin.ShardID, 34 replicas: make(map[uint64]string), 35 toAdd: 0, 36 } 37 38 for replicaID, uuid := range origin.Replicas { 39 shard.replicas[replicaID] = uuid 40 } 41 42 return shard 43 } 44 45 func fixedLogShardInfo(record metadata.LogShardRecord, info pb.LogShardInfo, 46 expiredStores []string) *fixingShard { 47 fixing := newFixingShard(info) 48 diff := len(fixing.replicas) - int(record.NumberOfReplicas) 49 50 // The number of replicas is less than expected. 51 // Record how many replicas should be added. 52 if diff < 0 { 53 fixing.toAdd = uint32(-diff) 54 } 55 56 for replicaID, uuid := range info.Replicas { 57 if contains(expiredStores, uuid) { 58 delete(fixing.replicas, replicaID) 59 // do not remove replicas more than expected. 60 if diff > 0 { 61 diff-- 62 } 63 } 64 } 65 66 // The number of replicas is more than expected. 67 // Remove some of them. 68 if diff > 0 { 69 idSlice := sortedReplicaID(fixing.replicas, info.LeaderID) 70 71 for i := 0; i < diff; i++ { 72 delete(fixing.replicas, idSlice[i]) 73 } 74 } 75 76 return fixing 77 } 78 79 // parseLogShards collects stats for further use. 80 func parseLogShards(cluster pb.ClusterInfo, infos pb.LogState, expired []string) *stats { 81 collect := newStats() 82 83 for _, shardInfo := range infos.Shards { 84 shardID := shardInfo.ShardID 85 record := getRecord(shardID, cluster.LogShards) 86 fixing := fixedLogShardInfo(record, shardInfo, expired) 87 88 toRemove := make([]replica, 0, len(shardInfo.Replicas)-len(fixing.replicas)) 89 for id, uuid := range shardInfo.Replicas { 90 if _, ok := fixing.replicas[id]; ok { 91 continue 92 } 93 rep := replica{ 94 uuid: uuid, 95 shardID: shardID, 96 replicaID: id, 97 } 98 toRemove = append(toRemove, rep) 99 } 100 101 toStart := make([]replica, 0) 102 for id, uuid := range fixing.replicas { 103 store := infos.Stores[uuid] 104 // Check dangling 105 if !replicaStarted(shardID, store.Replicas) { 106 rep := replica{ 107 uuid: uuid, 108 shardID: shardID, 109 replicaID: id, 110 } 111 toStart = append(toStart, rep) 112 } 113 } 114 if fixing.toAdd > 0 { 115 collect.toAdd[shardID] = fixing.toAdd 116 } 117 if len(toRemove) > 0 { 118 collect.toRemove[shardID] = toRemove 119 } 120 collect.toStart = append(collect.toStart, toStart...) 121 } 122 123 // Check zombies 124 for uuid, storeInfo := range infos.Stores { 125 if contains(expired, uuid) { 126 continue 127 } 128 zombie := make([]replica, 0) 129 for _, replicaInfo := range storeInfo.Replicas { 130 _, ok := infos.Shards[replicaInfo.ShardID].Replicas[replicaInfo.ReplicaID] 131 if ok || replicaInfo.Epoch >= infos.Shards[replicaInfo.ShardID].Epoch { 132 continue 133 } 134 zombie = append(zombie, replica{uuid: uuid, shardID: replicaInfo.ShardID, 135 replicaID: replicaInfo.ReplicaID}) 136 } 137 collect.zombies = append(collect.zombies, zombie...) 138 } 139 140 return collect 141 } 142 143 // parseLogStores returns all expired stores' ids. 144 func parseLogStores(cfg hakeeper.Config, infos pb.LogState, currentTick uint64) ([]string, []string) { 145 working := make([]string, 0) 146 expired := make([]string, 0) 147 for uuid, storeInfo := range infos.Stores { 148 if cfg.LogStoreExpired(storeInfo.Tick, currentTick) { 149 expired = append(expired, uuid) 150 } else { 151 working = append(working, uuid) 152 } 153 } 154 155 return working, expired 156 } 157 158 // getRecord returns the LogShardRecord with the given shardID. 159 func getRecord(shardID uint64, LogShards []metadata.LogShardRecord) metadata.LogShardRecord { 160 for _, record := range LogShards { 161 if record.ShardID == shardID { 162 return record 163 } 164 } 165 return metadata.LogShardRecord{} 166 } 167 168 // sortedReplicaID returns a sorted replica id slice with leader at last. 169 // The first <expected-current> replicas will be removed as current replica 170 // num is larger than expected. So we put the leader replica at last to make 171 // sure that no leader election happened if replicas are removed. 172 func sortedReplicaID(replicas map[uint64]string, leaderID uint64) []uint64 { 173 var exist bool 174 idSlice := make([]uint64, 0, len(replicas)) 175 for id := range replicas { 176 if id != leaderID { 177 idSlice = append(idSlice, id) 178 } else { 179 exist = true 180 } 181 } 182 sort.Slice(idSlice, func(i, j int) bool { return idSlice[i] < idSlice[j] }) 183 if exist { 184 idSlice = append(idSlice, leaderID) 185 } 186 return idSlice 187 } 188 189 // replicaStarted checks if a replica is started in LogReplicaInfo. 190 func replicaStarted(shardID uint64, replicas []pb.LogReplicaInfo) bool { 191 for _, r := range replicas { 192 if r.ShardID == shardID { 193 return true 194 } 195 } 196 return false 197 }