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  }