github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/followerreadsccl/followerreads.go (about)

     1  // Copyright 2019 The Cockroach Authors.
     2  //
     3  // Licensed as a CockroachDB Enterprise file under the Cockroach Community
     4  // License (the "License"); you may not use this file except in compliance with
     5  // the License. You may obtain a copy of the License at
     6  //
     7  //     https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt
     8  
     9  // Package followerreadsccl implements and injects the functionality needed to
    10  // expose follower reads to clients.
    11  package followerreadsccl
    12  
    13  import (
    14  	"fmt"
    15  	"time"
    16  
    17  	"github.com/cockroachdb/cockroach/pkg/base"
    18  	"github.com/cockroachdb/cockroach/pkg/ccl/utilccl"
    19  	"github.com/cockroachdb/cockroach/pkg/kv"
    20  	"github.com/cockroachdb/cockroach/pkg/kv/kvclient/kvcoord"
    21  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver"
    22  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/closedts"
    23  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    24  	"github.com/cockroachdb/cockroach/pkg/settings"
    25  	"github.com/cockroachdb/cockroach/pkg/settings/cluster"
    26  	"github.com/cockroachdb/cockroach/pkg/sql"
    27  	"github.com/cockroachdb/cockroach/pkg/sql/physicalplan/replicaoracle"
    28  	"github.com/cockroachdb/cockroach/pkg/sql/sem/builtins"
    29  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    30  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    31  	"github.com/cockroachdb/cockroach/pkg/util/uuid"
    32  )
    33  
    34  // followerReadMultiple is the multiple of kv.closed_timestmap.target_duration
    35  // which the implementation of the follower read capable replica policy ought
    36  // to use to determine if a request can be used for reading.
    37  var followerReadMultiple = settings.RegisterValidatedFloatSetting(
    38  	"kv.follower_read.target_multiple",
    39  	"if above 1, encourages the distsender to perform a read against the "+
    40  		"closest replica if a request is older than kv.closed_timestamp.target_duration"+
    41  		" * (1 + kv.closed_timestamp.close_fraction * this) less a clock uncertainty "+
    42  		"interval. This value also is used to create follower_timestamp().",
    43  	3,
    44  	func(v float64) error {
    45  		if v < 1 {
    46  			return fmt.Errorf("%v is not >= 1", v)
    47  		}
    48  		return nil
    49  	},
    50  )
    51  
    52  // getFollowerReadOffset returns the offset duration which should be used to as
    53  // the offset from now to request a follower read. The same value less the clock
    54  // uncertainty, then is used to determine at the kv layer if a query can use a
    55  // follower read.
    56  func getFollowerReadDuration(st *cluster.Settings) time.Duration {
    57  	targetMultiple := followerReadMultiple.Get(&st.SV)
    58  	targetDuration := closedts.TargetDuration.Get(&st.SV)
    59  	closeFraction := closedts.CloseFraction.Get(&st.SV)
    60  	return -1 * time.Duration(float64(targetDuration)*
    61  		(1+closeFraction*targetMultiple))
    62  }
    63  
    64  func checkEnterpriseEnabled(clusterID uuid.UUID, st *cluster.Settings) error {
    65  	org := sql.ClusterOrganization.Get(&st.SV)
    66  	return utilccl.CheckEnterpriseEnabled(st, clusterID, org, "follower reads")
    67  }
    68  
    69  func evalFollowerReadOffset(clusterID uuid.UUID, st *cluster.Settings) (time.Duration, error) {
    70  	if err := checkEnterpriseEnabled(clusterID, st); err != nil {
    71  		return 0, err
    72  	}
    73  	return getFollowerReadDuration(st), nil
    74  }
    75  
    76  // batchCanBeEvaluatedOnFollower determines if a batch consists exclusively of
    77  // requests that can be evaluated on a follower replica.
    78  func batchCanBeEvaluatedOnFollower(ba roachpb.BatchRequest) bool {
    79  	return !ba.IsLocking() && ba.IsAllTransactional()
    80  }
    81  
    82  // txnCanPerformFollowerRead determines if the provided transaction can perform
    83  // follower reads.
    84  func txnCanPerformFollowerRead(txn *roachpb.Transaction) bool {
    85  	// If the request is transactional and that transaction has acquired any
    86  	// locks then that request should not perform follower reads. Doing so could
    87  	// allow the request to miss its own writes or observe state that conflicts
    88  	// with its locks.
    89  	return txn != nil && !txn.IsLocking()
    90  }
    91  
    92  // canUseFollowerRead determines if a query can be sent to a follower.
    93  func canUseFollowerRead(clusterID uuid.UUID, st *cluster.Settings, ts hlc.Timestamp) bool {
    94  	if !kvserver.FollowerReadsEnabled.Get(&st.SV) {
    95  		return false
    96  	}
    97  	threshold := (-1 * getFollowerReadDuration(st)) - 1*base.DefaultMaxClockOffset
    98  	if timeutil.Since(ts.GoTime()) < threshold {
    99  		return false
   100  	}
   101  	return checkEnterpriseEnabled(clusterID, st) == nil
   102  }
   103  
   104  // canSendToFollower implements the logic for checking whether a batch request
   105  // may be sent to a follower.
   106  func canSendToFollower(clusterID uuid.UUID, st *cluster.Settings, ba roachpb.BatchRequest) bool {
   107  	return batchCanBeEvaluatedOnFollower(ba) &&
   108  		txnCanPerformFollowerRead(ba.Txn) &&
   109  		canUseFollowerRead(clusterID, st, forward(ba.Txn.ReadTimestamp, ba.Txn.MaxTimestamp))
   110  }
   111  
   112  func forward(ts hlc.Timestamp, to hlc.Timestamp) hlc.Timestamp {
   113  	ts.Forward(to)
   114  	return ts
   115  }
   116  
   117  type oracleFactory struct {
   118  	clusterID *base.ClusterIDContainer
   119  	st        *cluster.Settings
   120  
   121  	binPacking replicaoracle.OracleFactory
   122  	closest    replicaoracle.OracleFactory
   123  }
   124  
   125  func newOracleFactory(cfg replicaoracle.Config) replicaoracle.OracleFactory {
   126  	return &oracleFactory{
   127  		clusterID:  &cfg.RPCContext.ClusterID,
   128  		st:         cfg.Settings,
   129  		binPacking: replicaoracle.NewOracleFactory(replicaoracle.BinPackingChoice, cfg),
   130  		closest:    replicaoracle.NewOracleFactory(replicaoracle.ClosestChoice, cfg),
   131  	}
   132  }
   133  
   134  func (f oracleFactory) Oracle(txn *kv.Txn) replicaoracle.Oracle {
   135  	if txn != nil && canUseFollowerRead(f.clusterID.Get(), f.st, txn.ReadTimestamp()) {
   136  		return f.closest.Oracle(txn)
   137  	}
   138  	return f.binPacking.Oracle(txn)
   139  }
   140  
   141  // followerReadAwareChoice is a leaseholder choosing policy that detects
   142  // whether a query can be used with a follower read.
   143  var followerReadAwareChoice = replicaoracle.RegisterPolicy(newOracleFactory)
   144  
   145  func init() {
   146  	sql.ReplicaOraclePolicy = followerReadAwareChoice
   147  	builtins.EvalFollowerReadOffset = evalFollowerReadOffset
   148  	kvcoord.CanSendToFollower = canSendToFollower
   149  }