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  }