Repository: tikv/client-go (go)

Issue:
replica selector v2 not compatible with v1 in mixed mode
# Issue

### 1. Minimal reproduce step

This issue was found in #1267

```go
	s.changeRegionLeader(3)
	ca = replicaSelectorAccessPathCase{
		reqType:   tikvrpc.CmdGet,
		readType:  kv.ReplicaReadMixed,
		staleRead: false,
		accessErr: []RegionErrorType{ServerIsBusyErr, ServerIsBusyErr, ServerIsBusyErr},
```

### 2. What did you expect to see?

```go
		expect: &accessPathResult{
			accessPath: []string{
				"{addr: store1, replica-read: true, stale-read: false}",
				"{addr: store2, replica-read: true, stale-read: false}",
				"{addr: store3, replica-read: true, stale-read: false}",
			},
```


### 3. What did you see instead?

```go
		expect: &accessPathResult{
			accessPath: []string{
				"{addr: store1, replica-read: true, stale-read: false}",
				"{addr: store3, replica-read: true, stale-read: false}",
				"{addr: store2, replica-read: true, stale-read: false}",
			},
```


Pull request:
fix issue replica selector v2 not compatible with v1 in mixed mode
close #1268

Patch:
diff --git a/internal/locate/replica_selector.go b/internal/locate/replica_selector.go
index 5d5a927..3a20eac 100644
--- a/internal/locate/replica_selector.go
+++ b/internal/locate/replica_selector.go
@@ -79,6 +79,9 @@ func newReplicaSelectorV2(
 	for _, op := range opts {
 		op(&option)
 	}
+	if req.ReplicaReadType == kv.ReplicaReadPreferLeader {
+		WithPerferLeader()(&option)
+	}
 	return &replicaSelectorV2{
 		baseReplicaSelector: baseReplicaSelector{
 			regionCache:   regionCache,
@@ -166,17 +169,10 @@ func (s *replicaSelectorV2) nextForReplicaReadMixed(req *tikvrpc.Request) {
 			return
 		}
 	}
-	preferLeader := req.ReplicaReadType == kv.ReplicaReadPreferLeader
-	if s.attempts > 1 {
-		if req.ReplicaReadType == kv.ReplicaReadMixed {
-			// For mixed read retry, prefer retry leader first.
-			preferLeader = true
-		}
-	}
 	strategy := ReplicaSelectMixedStrategy{
 		leaderIdx:    leaderIdx,
 		tryLeader:    req.ReplicaReadType == kv.ReplicaReadMixed || req.ReplicaReadType == kv.ReplicaReadPreferLeader,
-		preferLeader: preferLeader,
+		preferLeader: s.option.preferLeader,
 		leaderOnly:   s.option.leaderOnly,
 		learnerOnly:  req.ReplicaReadType == kv.ReplicaReadLearner,
 		labels:       s.option.labels,
@@ -201,6 +197,14 @@ func (s *replicaSelectorV2) nextForReplicaReadMixed(req *tikvrpc.Request) {
 			req.StaleRead = false
 			req.ReplicaRead = s.isReadOnlyReq
 		}
+		// Monitor the flows destination if selector is under `ReplicaReadPreferLeader` mode.
+		if s.option.preferLeader {
+			if s.target.peer.Id != s.region.GetLeaderPeerID() {
+				s.target.store.recordReplicaFlowsStats(toFollower)
+			} else {
+				s.target.store.recordReplicaFlowsStats(toLeader)
+			}
+		}
 	}
 }
 


Test patch:
diff --git a/internal/locate/replica_selector_test.go b/internal/locate/replica_selector_test.go
index bb6cd01..bdb8325 100644
--- a/internal/locate/replica_selector_test.go
+++ b/internal/locate/replica_selector_test.go
@@ -663,6 +663,69 @@ func TestReplicaReadAccessPathByCase(t *testing.T) {
 		},
 	}
 	s.True(s.runCaseAndCompare(ca))
+
+	s.changeRegionLeader(3)
+	ca = replicaSelectorAccessPathCase{
+		reqType:   tikvrpc.CmdGet,
+		readType:  kv.ReplicaReadMixed,
+		staleRead: false,
+		accessErr: []RegionErrorType{ServerIsBusyErr, ServerIsBusyErr, ServerIsBusyErr},
+		expect: &accessPathResult{
+			accessPath: []string{
+				"{addr: store1, replica-read: true, stale-read: false}",
+				"{addr: store2, replica-read: true, stale-read: false}",
+				"{addr: store3, replica-read: true, stale-read: false}",
+			},
+			respErr:         "",
+			respRegionError: fakeEpochNotMatch,
+			backoffCnt:      1,
+			backoffDetail:   []string{"tikvServerBusy+1"},
+			regionIsValid:   false,
+		},
+	}
+	s.True(s.runCaseAndCompare(ca))
+
+	ca = replicaSelectorAccessPathCase{
+		reqType:   tikvrpc.CmdGet,
+		readType:  kv.ReplicaReadPreferLeader,
+		staleRead: false,
+		accessErr: []RegionErrorType{ServerIsBusyErr, ServerIsBusyErr, ServerIsBusyErr},
+		expect: &accessPathResult{
+			accessPath: []string{
+				"{addr: store3, replica-read: true, stale-read: false}",
+				"{addr: store1, replica-read: true, stale-read: false}",
+				"{addr: store2, replica-read: true, stale-read: false}",
+			},
+			respErr:         "",
+			respRegionError: fakeEpochNotMatch,
+			backoffCnt:      1,
+			backoffDetail:   []string{"tikvServerBusy+1"},
+			regionIsValid:   false,
+		},
+	}
+	s.True(s.runCaseAndCompare(ca))
+
+	ca = replicaSelectorAccessPathCase{
+		reqType:   tikvrpc.CmdGet,
+		readType:  kv.ReplicaReadPreferLeader,
+		staleRead: false,
+		label:     &metapb.StoreLabel{Key: "id", Value: "2"},
+		accessErr: []RegionErrorType{ServerIsBusyErr, ServerIsBusyErr, ServerIsBusyErr},
+		expect: &accessPathResult{
+			accessPath: []string{
+				"{addr: store2, replica-read: true, stale-read: false}",
+				"{addr: store3, replica-read: true, stale-read: false}",
+				"{addr: store1, replica-read: true, stale-read: false}",
+			},
+			respErr:         "",
+			respRegionError: fakeEpochNotMatch,
+			backoffCnt:      1,
+			backoffDetail:   []string{"tikvServerBusy+1"},
+			regionIsValid:   false,
+		},
+	}
+	s.True(s.runCaseAndCompare(ca))
+	s.changeRegionLeader(1)
 }
 
 func TestReplicaReadAccessPathByCase2(t *testing.T) {