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 }