github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvclient/kvcoord/replica_slice.go (about) 1 // Copyright 2015 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package kvcoord 12 13 import ( 14 "context" 15 "sort" 16 "time" 17 18 "github.com/cockroachdb/cockroach/pkg/roachpb" 19 "github.com/cockroachdb/cockroach/pkg/util/log" 20 "github.com/cockroachdb/cockroach/pkg/util/shuffle" 21 ) 22 23 // ReplicaInfo extends the Replica structure with the associated node 24 // descriptor. 25 type ReplicaInfo struct { 26 roachpb.ReplicaDescriptor 27 NodeDesc *roachpb.NodeDescriptor 28 } 29 30 func (i ReplicaInfo) locality() []roachpb.Tier { 31 return i.NodeDesc.Locality.Tiers 32 } 33 34 func (i ReplicaInfo) addr() string { 35 return i.NodeDesc.Address.String() 36 } 37 38 // A ReplicaSlice is a slice of ReplicaInfo. 39 type ReplicaSlice []ReplicaInfo 40 41 // NewReplicaSlice creates a ReplicaSlice from the replicas listed in the range 42 // descriptor and using gossip to lookup node descriptors. Replicas on nodes 43 // that are not gossiped are omitted from the result. 44 func NewReplicaSlice( 45 gossip interface { 46 GetNodeDescriptor(roachpb.NodeID) (*roachpb.NodeDescriptor, error) 47 }, 48 replicas []roachpb.ReplicaDescriptor, 49 ) ReplicaSlice { 50 if gossip == nil { 51 return nil 52 } 53 rs := make(ReplicaSlice, 0, len(replicas)) 54 for _, r := range replicas { 55 nd, err := gossip.GetNodeDescriptor(r.NodeID) 56 if err != nil { 57 if log.V(1) { 58 log.Infof(context.TODO(), "node %d is not gossiped: %v", r.NodeID, err) 59 } 60 continue 61 } 62 rs = append(rs, ReplicaInfo{ 63 ReplicaDescriptor: r, 64 NodeDesc: nd, 65 }) 66 } 67 return rs 68 } 69 70 // ReplicaSlice implements shuffle.Interface. 71 var _ shuffle.Interface = ReplicaSlice{} 72 73 // Len returns the total number of replicas in the slice. 74 func (rs ReplicaSlice) Len() int { return len(rs) } 75 76 // Swap swaps the replicas with indexes i and j. 77 func (rs ReplicaSlice) Swap(i, j int) { rs[i], rs[j] = rs[j], rs[i] } 78 79 // FindReplica returns the index of the replica which matches the specified store 80 // ID. If no replica matches, -1 is returned. 81 func (rs ReplicaSlice) FindReplica(storeID roachpb.StoreID) int { 82 for i := range rs { 83 if rs[i].StoreID == storeID { 84 return i 85 } 86 } 87 return -1 88 } 89 90 // MoveToFront moves the replica at the given index to the front 91 // of the slice, keeping the order of the remaining elements stable. 92 // The function will panic when invoked with an invalid index. 93 func (rs ReplicaSlice) MoveToFront(i int) { 94 if i >= len(rs) { 95 panic("out of bound index") 96 } 97 front := rs[i] 98 // Move the first i elements one index to the right 99 copy(rs[1:], rs[:i]) 100 rs[0] = front 101 } 102 103 // localityMatch returns the number of consecutive locality tiers 104 // which match between a and b. 105 func localityMatch(a, b []roachpb.Tier) int { 106 if len(a) == 0 { 107 return 0 108 } 109 for i := range a { 110 if i >= len(b) || a[i] != b[i] { 111 return i 112 } 113 } 114 return len(a) 115 } 116 117 // A LatencyFunc returns the latency from this node to a remote 118 // address and a bool indicating whether the latency is valid. 119 type LatencyFunc func(string) (time.Duration, bool) 120 121 // OptimizeReplicaOrder sorts the replicas in the order in which 122 // they're to be used for sending RPCs (meaning in the order in which 123 // they'll be probed for the lease). Lower latency and "closer" 124 // (matching in more attributes) replicas are ordered first. If the 125 // current node is a replica, then it'll be the first one. 126 // 127 // nodeDesc is the descriptor of the current node. It can be nil, in 128 // which case information about the current descriptor is not used in 129 // optimizing the order. 130 // 131 // Note that this method is not concerned with any information the 132 // node might have about who the lease holder might be. If the 133 // leaseholder is known by the caller, the caller will move it to the 134 // front if appropriate. 135 func (rs ReplicaSlice) OptimizeReplicaOrder( 136 nodeDesc *roachpb.NodeDescriptor, latencyFn LatencyFunc, 137 ) { 138 // If we don't know which node we're on, send the RPCs randomly. 139 if nodeDesc == nil { 140 shuffle.Shuffle(rs) 141 return 142 } 143 // Sort replicas by latency and then attribute affinity. 144 sort.Slice(rs, func(i, j int) bool { 145 // If there is a replica in local node, it sorts first. 146 if rs[i].NodeID == nodeDesc.NodeID { 147 return true 148 } 149 if latencyFn != nil { 150 latencyI, okI := latencyFn(rs[i].addr()) 151 latencyJ, okJ := latencyFn(rs[j].addr()) 152 if okI && okJ { 153 return latencyI < latencyJ 154 } 155 } 156 attrMatchI := localityMatch(nodeDesc.Locality.Tiers, rs[i].locality()) 157 attrMatchJ := localityMatch(nodeDesc.Locality.Tiers, rs[j].locality()) 158 // Longer locality matches sort first (the assumption is that 159 // they'll have better latencies). 160 return attrMatchI > attrMatchJ 161 }) 162 }