Repository: tikv/client-go
Pull request #1715 (2025-07-10 16:43:10)

region cache: Preserve needReloadOnAccess Flag on Region Cache Reload Failure
Tracked by issue https://github.com/tikv/client-go/issues/1647

Reset the **needReloadOnAccess** flag for a region when the region cache fails to reload region information(from pd) for an obsolete record.

Please refer to the issue description for the motivation behind this change.

ti-chi-bot[bot]: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
**Once this PR has been reviewed and has the lgtm label**, please assign [sticnarf](https://github.com/sticnarf) for approval. For more information see [the Code Review Process](https://book.prow.tidb.net/#/workflows/pr).
**Please ensure that each of them provides their approval before proceeding.**

The full list of commands accepted by this bot can be found [here](https://prow.tidb.net/command-help?repo=tikv%2Fclient-go).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/tikv/client-go/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["sticnarf"]} -->
ti-chi-bot[bot]: Welcome @HaoW30! <br><br>It looks like this is your first PR to <a href='https://github.com/tikv/client-go'>tikv/client-go</a> 🎉. <br><br>I'm the bot to help you request reviewers, add labels and more, See [available commands](https://prow.tidb.net/command-help). <br><br>We want to make sure your contribution gets all the attention it needs! <br><br> <br><br>Thank you, and welcome to tikv/client-go. :smiley:
ti-chi-bot[bot]: Thanks for your pull request. Before we can look at it, you'll need to add a 'DCO signoff' to your commits.

:memo: **Please follow instructions in the [contributing guide](https://github.com/tikv/client-go/blob/master/CONTRIBUTING.md) to update your commits with the DCO**

Full details of the Developer Certificate of Origin can be found at [developercertificate.org](https://developercertificate.org/).

**The list of commits missing DCO signoff**:

- [7f20f47](https://github.com/tikv/client-go/commits/7f20f4747fd0901f01e6f78d039999cce6d35185) region cache: Preserve needReloadOnAccess Flag on Region Cache Reload Failure

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>


Patch:
diff --git a/internal/locate/region_cache.go b/internal/locate/region_cache.go
index 93c2a09..2e83b0b 100644
--- a/internal/locate/region_cache.go
+++ b/internal/locate/region_cache.go
@@ -1350,7 +1350,16 @@ func (c *RegionCache) BatchLocateKeyRanges(bo *retry.Backoffer, keyRanges []kv.K
 
 	// 2. load remaining regions from pd client
 	for len(uncachedRanges) > 0 {
-		regions, err := c.BatchLoadRegionsWithKeyRanges(bo, uncachedRanges, defaultRegionsPerBatch, opts...)
+		// If we send too many ranges to PD, it may exceed the size of grpc limitation or cause a timeout error.
+		// So we limit the number of ranges per batch to avoid this issue.
+		maxRangesPerBatch := 16 * defaultRegionsPerBatch
+		var toBeSentRanges []router.KeyRange
+		if len(uncachedRanges) > maxRangesPerBatch {
+			toBeSentRanges = uncachedRanges[:maxRangesPerBatch]
+		} else {
+			toBeSentRanges = uncachedRanges
+		}
+		regions, err := c.BatchLoadRegionsWithKeyRanges(bo, toBeSentRanges, defaultRegionsPerBatch, opts...)
 		if err != nil {
 			return nil, err
 		}
@@ -1549,6 +1558,8 @@ func (c *RegionCache) findRegionByKey(bo *retry.Backoffer, key []byte, isEndKey
 			logutil.Logger(bo.GetCtx()).Error("load region failure",
 				zap.String("key", redact.Key(key)), zap.Error(err),
 				zap.String("encode-key", redact.Key(c.codec.EncodeRegionKey(key))))
+			// mark as need sync reload
+			r.setSyncFlags(needReloadOnAccess)
 		} else {
 			logutil.Eventf(bo.GetCtx(), "load region %d from pd, due to need-reload", lr.GetID())
 			reloadOnAccess := flags&needReloadOnAccess > 0
@@ -1693,6 +1704,8 @@ func (c *RegionCache) LocateRegionByID(bo *retry.Backoffer, regionID uint64) (*K
 				// ignore error and use old region info.
 				logutil.Logger(bo.GetCtx()).Error("load region failure",
 					zap.Uint64("regionID", regionID), zap.Error(err))
+				// mark as need sync reload
+				r.setSyncFlags(needReloadOnAccess)
 			} else {
 				r = lr
 				c.mu.Lock()
diff --git a/internal/locate/replica_selector.go b/internal/locate/replica_selector.go
index dc1fbaa..a29f924 100644
--- a/internal/locate/replica_selector.go
+++ b/internal/locate/replica_selector.go
@@ -199,9 +199,9 @@ func (s *replicaSelector) nextForReplicaReadMixed(req *tikvrpc.Request) {
 				req.ReplicaRead = true
 			}
 		} else {
-			// always use replica.
+			// use replica read only if the target is not leader.
 			req.StaleRead = false
-			req.ReplicaRead = s.isReadOnlyReq
+			req.ReplicaRead = s.isReadOnlyReq && s.target.peer.Id != s.region.GetLeaderPeerID()
 		}
 		// Monitor the flows destination if selector is under `ReplicaReadPreferLeader` mode.
 		if s.option.preferLeader {


Test patch:
diff --git a/integration_tests/snapshot_test.go b/integration_tests/snapshot_test.go
index b35c728..240ee6d 100644
--- a/integration_tests/snapshot_test.go
+++ b/integration_tests/snapshot_test.go
@@ -49,6 +49,7 @@ import (
 	"github.com/pkg/errors"
 	"github.com/stretchr/testify/suite"
 	"github.com/tikv/client-go/v2/config"
+	"github.com/tikv/client-go/v2/config/retry"
 	tikverr "github.com/tikv/client-go/v2/error"
 	"github.com/tikv/client-go/v2/kv"
 	"github.com/tikv/client-go/v2/oracle"
@@ -435,8 +436,27 @@ func (s *testSnapshotSuite) TestReplicaReadAdjuster() {
 		cfg.EnableAsyncBatchGet = originAsyncEnable
 		config.StoreGlobalConfig(cfg)
 	}()
-	_, err := s.store.SplitRegions(context.Background(), [][]byte{[]byte("y1")}, false, nil)
+	regionIDs, err := s.store.SplitRegions(context.Background(), [][]byte{[]byte("y1")}, false, nil)
 	s.Nil(err)
+	for _, regionID := range regionIDs {
+		var loc *tikv.KeyLocation
+		s.Eventually(func() bool {
+			loc, err = s.store.GetRegionCache().LocateRegionByID(retry.NewNoopBackoff(context.Background()), regionID)
+			return err == nil
+		}, 5*time.Second, time.Millisecond)
+		region := s.store.GetRegionCache().GetCachedRegionWithRLock(loc.Region)
+		s.NotNil(region)
+		s.Equal(region.GetLeaderStoreID(), uint64(1))
+	}
+	stores := s.store.GetRegionCache().GetAllStores()
+	var leaderStoreAddr string
+	for _, store := range stores {
+		if store.StoreID() == 1 {
+			leaderStoreAddr = store.GetAddr()
+			break
+		}
+	}
+	s.NotEqual(leaderStoreAddr, "")
 	for _, async := range []bool{true, false} {
 		cfg := config.GetGlobalConfig()
 		cfg.EnableAsyncBatchGet = async
@@ -447,8 +467,9 @@ func (s *testSnapshotSuite) TestReplicaReadAdjuster() {
 			// check the replica read type
 			fn := func(next interceptor.RPCInterceptorFunc) interceptor.RPCInterceptorFunc {
 				return func(target string, req *tikvrpc.Request) (*tikvrpc.Response, error) {
-					// when the request is fallback to leader read, the ReplicaRead should be false to avoid read-index in store.
-					s.Equal(hit, req.ReplicaRead)
+					// When the request falls back to leader read or when the target replica is the leader,
+					// ReplicaRead should be set to false to avoid read-index operations on the leader.
+					s.Equal(hit && target != leaderStoreAddr, req.ReplicaRead)
 					if hit {
 						s.Equal(kv.ReplicaReadMixed, req.ReplicaReadType)
 					} else {
diff --git a/internal/locate/region_cache_test.go b/internal/locate/region_cache_test.go
index 10dbac9..0cf1bb5 100644
--- a/internal/locate/region_cache_test.go
+++ b/internal/locate/region_cache_test.go
@@ -3086,3 +3086,25 @@ func (s *testRegionCacheSuite) TestRegionCacheValidAfterLoading() {
 		s.True(region.isValid())
 	}
 }
+
+func (s *testRegionCacheSuite) TestBatchLoadLimitRanges() {
+	ranges := make([]kv.KeyRange, 0, 100000)
+	for i := 0; i < 100000; i++ {
+		startKey := make([]byte, 8)
+		endKey := make([]byte, 8)
+		binary.BigEndian.PutUint64(startKey, uint64(i*2))
+		binary.BigEndian.PutUint64(endKey, uint64(i*2+1))
+		ranges = append(ranges, kv.KeyRange{StartKey: startKey, EndKey: endKey})
+	}
+
+	originalBatchScanRegions := s.cache.pdClient.BatchScanRegions
+	s.cache.pdClient = &inspectedPDClient{
+		Client: s.cache.pdClient,
+		batchScanRegions: func(ctx context.Context, keyRanges []router.KeyRange, limit int, opts ...opt.GetRegionOption) ([]*router.Region, error) {
+			s.LessOrEqual(len(keyRanges), 16*defaultRegionsPerBatch)
+			return originalBatchScanRegions(ctx, keyRanges, limit, opts...)
+		},
+	}
+	_, err := s.cache.BatchLocateKeyRanges(s.bo, ranges)
+	s.Nil(err)
+}
diff --git a/internal/locate/replica_selector_test.go b/internal/locate/replica_selector_test.go
index 553bda4..87d8acd 100644
--- a/internal/locate/replica_selector_test.go
+++ b/internal/locate/replica_selector_test.go
@@ -539,7 +539,7 @@ func testReplicaReadAccessPathByCase(s *testReplicaSelectorSuite) {
 		accessErr: []RegionErrorType{ServerIsBusyErr, ServerIsBusyErr, DeadLineExceededErr},
 		expect: &accessPathResult{
 			accessPath: []string{
-				"{addr: store1, replica-read: true, stale-read: false}",
+				"{addr: store1, replica-read: false, stale-read: false}",
 				"{addr: store2, replica-read: true, stale-read: false}",
 				"{addr: store3, replica-read: true, stale-read: false}"},
 			respErr:         "",
@@ -648,7 +648,7 @@ func testReplicaReadAccessPathByCase(s *testReplicaSelectorSuite) {
 		expect: &accessPathResult{
 			accessPath: []string{
 				"{addr: store2, replica-read: true, stale-read: false}",
-				"{addr: store1, replica-read: true, stale-read: false}",
+				"{addr: store1, replica-read: false, stale-read: false}",
 				"{addr: store3, replica-read: true, stale-read: false}"},
 			respErr:         "",
 			respRegionError: nil,
@@ -690,7 +690,7 @@ func testReplicaReadAccessPathByCase(s *testReplicaSelectorSuite) {
 			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}",
+				"{addr: store3, replica-read: false, stale-read: false}",
 			},
 			respErr:         "",
 			respRegionError: fakeEpochNotMatch,
@@ -708,7 +708,7 @@ func testReplicaReadAccessPathByCase(s *testReplicaSelectorSuite) {
 		accessErr: []RegionErrorType{ServerIsBusyErr, ServerIsBusyErr, ServerIsBusyErr},
 		expect: &accessPathResult{
 			accessPath: []string{
-				"{addr: store3, replica-read: true, stale-read: false}",
+				"{addr: store3, replica-read: false, stale-read: false}",
 				"{addr: store1, replica-read: true, stale-read: false}",
 				"{addr: store2, replica-read: true, stale-read: false}",
 			},
@@ -730,7 +730,7 @@ func testReplicaReadAccessPathByCase(s *testReplicaSelectorSuite) {
 		expect: &accessPathResult{
 			accessPath: []string{
 				"{addr: store2, replica-read: true, stale-read: false}",
-				"{addr: store3, replica-read: true, stale-read: false}",
+				"{addr: store3, replica-read: false, stale-read: false}",
 				"{addr: store1, replica-read: true, stale-read: false}",
 			},
 			respErr:         "",
@@ -775,7 +775,7 @@ func testReplicaReadAccessPathByCase2(s *testReplicaSelectorSuite) {
 		expect: &accessPathResult{
 			accessPath: []string{
 				"{addr: store2, replica-read: true, stale-read: false}",
-				"{addr: store1, replica-read: true, stale-read: false}",
+				"{addr: store1, replica-read: false, stale-read: false}",
 				"{addr: store3, replica-read: true, stale-read: false}"},
 			respErr:         "region 0 is not prepared for the flashback",
 			respRegionError: nil,
@@ -818,7 +818,7 @@ func testReplicaReadAccessPathByCase2(s *testReplicaSelectorSuite) {
 		accessErr: []RegionErrorType{ServerIsBusyErr, ServerIsBusyErr, DeadLineExceededErr},
 		expect: &accessPathResult{
 			accessPath: []string{
-				"{addr: store1, replica-read: true, stale-read: false}",
+				"{addr: store1, replica-read: false, stale-read: false}",
 				"{addr: store2, replica-read: true, stale-read: false}",
 				"{addr: store3, replica-read: true, stale-read: false}"},
 			respErr:         "",
@@ -968,7 +968,7 @@ func testReplicaReadAccessPathByCase2(s *testReplicaSelectorSuite) {
 		expect: &accessPathResult{
 			accessPath: []string{
 				"{addr: store2, replica-read: true, stale-read: false}",
-				"{addr: store1, replica-read: true, stale-read: false}",
+				"{addr: store1, replica-read: false, stale-read: false}",
 				"{addr: store3, replica-read: true, stale-read: false}"},
 			respErr:         "",
 			respRegionError: nil,
@@ -990,7 +990,7 @@ func testReplicaReadAccessPathByCase2(s *testReplicaSelectorSuite) {
 		expect: &accessPathResult{
 			accessPath: []string{
 				"{addr: store3, replica-read: true, stale-read: false}",
-				"{addr: store2, replica-read: true, stale-read: false}",
+				"{addr: store2, replica-read: false, stale-read: false}",
 				"{addr: store1, replica-read: true, stale-read: false}"},
 			respErr:         "",
 			respRegionError: nil,
@@ -1072,7 +1072,7 @@ func testReplicaReadAccessPathByBasicCase(s *testReplicaSelectorSuite) {
 						backoff = []string{"maxTsNotSynced+1"}
 					}
 					accessPath := []string{
-						"{addr: store1, replica-read: true, stale-read: false}",
+						"{addr: store1, replica-read: false, stale-read: false}",
 						"{addr: store2, replica-read: true, stale-read: false}",
 					}
 					switch readType {
@@ -1133,7 +1133,7 @@ func testReplicaReadAccessPathByBasicCase(s *testReplicaSelectorSuite) {
 					regionIsValid := false
 					respErr := ""
 					respRegionError := tp.GenRegionError()
-					accessPath := []string{"{addr: store1, replica-read: true, stale-read: false}"}
+					accessPath := []string{"{addr: store1, replica-read: false, stale-read: false}"}
 					switch tp {
 					case RecoveryInProgressErr:
 						backoff = []string{"regionRecoveryInProgress+1"}
@@ -1558,7 +1558,7 @@ func testReplicaReadAccessPathByFollowerCase(s *testReplicaSelectorSuite) {
 			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}",
+				"{addr: store1, replica-read: false, stale-read: false}",
 			},
 			respErr:         "",
 			respRegionError: nil,
@@ -1577,7 +1577,7 @@ func testReplicaReadAccessPathByFollowerCase(s *testReplicaSelectorSuite) {
 			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}",
+				"{addr: store1, replica-read: false, stale-read: false}",
 			},
 			respErr:         "",
 			respRegionError: fakeEpochNotMatch,
@@ -1596,7 +1596,7 @@ func testReplicaReadAccessPathByFollowerCase(s *testReplicaSelectorSuite) {
 			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}",
+				"{addr: store1, replica-read: false, stale-read: false}",
 			},
 			respErr:         "",
 			respRegionError: nil,
@@ -1616,7 +1616,7 @@ func testReplicaReadAccessPathByFollowerCase(s *testReplicaSelectorSuite) {
 			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}",
+				"{addr: store1, replica-read: false, stale-read: false}",
 			},
 			respErr:         "",
 			respRegionError: nil,
@@ -1657,7 +1657,7 @@ func testReplicaReadAccessPathByMixedAndPreferLeaderCase(s *testReplicaSelectorS
 			accessErr: nil,
 			expect: &accessPathResult{
 				accessPath: []string{
-					"{addr: store1, replica-read: true, stale-read: false}",
+					"{addr: store1, replica-read: false, stale-read: false}",
 				},
 				respErr:         "",
 				respRegionError: nil,
@@ -1674,7 +1674,7 @@ func testReplicaReadAccessPathByMixedAndPreferLeaderCase(s *testReplicaSelectorS
 			accessErr: []RegionErrorType{ServerIsBusyWithEstimatedWaitMsErr, StaleCommandErr, ServerIsBusyErr},
 			expect: &accessPathResult{
 				accessPath: []string{
-					"{addr: store1, replica-read: true, stale-read: false}",
+					"{addr: store1, replica-read: false, stale-read: false}",
 					"{addr: store2, replica-read: true, stale-read: false}",
 					"{addr: store3, replica-read: true, stale-read: false}",
 				},
@@ -1693,7 +1693,7 @@ func testReplicaReadAccessPathByMixedAndPreferLeaderCase(s *testReplicaSelectorS
 			accessErr: []RegionErrorType{ServerIsBusyErr, RegionNotFoundErr},
 			expect: &accessPathResult{
 				accessPath: []string{
-					"{addr: store1, replica-read: true, stale-read: false}",
+					"{addr: store1, replica-read: false, stale-read: false}",
 					"{addr: store2, replica-read: true, stale-read: false}",
 				},
 				respErr:         "",
@@ -1711,7 +1711,7 @@ func testReplicaReadAccessPathByMixedAndPreferLeaderCase(s *testReplicaSelectorS
 			accessErr: []RegionErrorType{DeadLineExceededErr, ServerIsBusyErr},
 			expect: &accessPathResult{
 				accessPath: []string{
-					"{addr: store1, replica-read: true, stale-read: false}",
+					"{addr: store1, replica-read: false, stale-read: false}",
 					"{addr: store2, replica-read: true, stale-read: false}",
 					"{addr: store3, replica-read: true, stale-read: false}",
 				},
@@ -1731,7 +1731,7 @@ func testReplicaReadAccessPathByMixedAndPreferLeaderCase(s *testReplicaSelectorS
 			accessErr: []RegionErrorType{DeadLineExceededErr, ServerIsBusyErr},
 			expect: &accessPathResult{
 				accessPath: []string{
-					"{addr: store1, replica-read: true, stale-read: false}",
+					"{addr: store1, replica-read: false, stale-read: false}",
 					"{addr: store2, replica-read: true, stale-read: false}",
 					"{addr: store3, replica-read: true, stale-read: false}",
 				},
@@ -1753,7 +1753,7 @@ func testReplicaReadAccessPathByMixedAndPreferLeaderCase(s *testReplicaSelectorS
 		expect: &accessPathResult{
 			accessPath: []string{
 				"{addr: store2, replica-read: true, stale-read: false}", // try match label first.
-				"{addr: store1, replica-read: true, stale-read: false}",
+				"{addr: store1, replica-read: false, stale-read: false}",
 				"{addr: store3, replica-read: true, stale-read: false}",
 			},
 			respErr:         "",
@@ -1772,7 +1772,7 @@ func testReplicaReadAccessPathByMixedAndPreferLeaderCase(s *testReplicaSelectorS
 		accessErr: []RegionErrorType{DeadLineExceededErr, ServerIsBusyErr},
 		expect: &accessPathResult{
 			accessPath: []string{
-				"{addr: store3, replica-read: true, stale-read: false}", // try leader first.
+				"{addr: store3, replica-read: false, stale-read: false}", // try leader first.
 				"{addr: store1, replica-read: true, stale-read: false}",
 				"{addr: store2, replica-read: true, stale-read: false}",
 			},
@@ -1792,8 +1792,8 @@ func testReplicaReadAccessPathByMixedAndPreferLeaderCase(s *testReplicaSelectorS
 		accessErr: []RegionErrorType{DeadLineExceededErr, ServerIsBusyErr},
 		expect: &accessPathResult{
 			accessPath: []string{
-				"{addr: store2, replica-read: true, stale-read: false}", // try match label first, since match label has higher priority.
-				"{addr: store3, replica-read: true, stale-read: false}", // try leader.
+				"{addr: store2, replica-read: true, stale-read: false}",  // try match label first, since match label has higher priority.
+				"{addr: store3, replica-read: false, stale-read: false}", // try leader.
 				"{addr: store1, replica-read: true, stale-read: false}",
 			},
 			respErr:         "",
@@ -1815,7 +1815,7 @@ func testReplicaReadAccessPathByMixedAndPreferLeaderCase(s *testReplicaSelectorS
 			accessErr: []RegionErrorType{ServerIsBusyErr},
 			expect: &accessPathResult{
 				accessPath: []string{
-					"{addr: store1, replica-read: true, stale-read: false}", // store1 will be marked already slow.
+					"{addr: store1, replica-read: false, stale-read: false}", // store1 will be marked already slow.
 					"{addr: store2, replica-read: true, stale-read: false}",
 				},
 				respErr:         "",
@@ -1837,7 +1837,7 @@ func testReplicaReadAccessPathByMixedAndPreferLeaderCase(s *testReplicaSelectorS
 				accessPath: []string{
 					"{addr: store2, replica-read: true, stale-read: false}", // won't try leader in store1, since it is slow.
 					"{addr: store3, replica-read: true, stale-read: false}",
-					"{addr: store1, replica-read: true, stale-read: false}",
+					"{addr: store1, replica-read: false, stale-read: false}",
 				},
 				respErr:         "",
 				respRegionError: fakeEpochNotMatch,
@@ -1858,8 +1858,8 @@ func testReplicaReadAccessPathByMixedAndPreferLeaderCase(s *testReplicaSelectorS
 			accessErr: []RegionErrorType{ServerIsBusyErr, ServerIsBusyErr},
 			expect: &accessPathResult{
 				accessPath: []string{
-					"{addr: store1, replica-read: true, stale-read: false}", // store1 will be marked already slow.
-					"{addr: store2, replica-read: true, stale-read: false}", // store2 will be marked already slow.
+					"{addr: store1, replica-read: false, stale-read: false}", // store1 will be marked already slow.
+					"{addr: store2, replica-read: true, stale-read: false}",  // store2 will be marked already slow.
 					"{addr: store3, replica-read: true, stale-read: false}",
 				},
 				respErr:         "",
@@ -1880,7 +1880,7 @@ func testReplicaReadAccessPathByMixedAndPreferLeaderCase(s *testReplicaSelectorS
 			expect: &accessPathResult{
 				accessPath: []string{
 					"{addr: store3, replica-read: true, stale-read: false}", // won't try leader in store1, since it is slow, ditto for store2.
-					"{addr: store1, replica-read: true, stale-read: false}",
+					"{addr: store1, replica-read: false, stale-read: false}",
 					// won't retry store2, since it is slow, and it is not leader replica.
 				},
 				respErr:         "",
@@ -2412,7 +2412,7 @@ func TestReplicaReadAccessPathByFlashbackInProgressCase(t *testing.T) {
 		accessErr: []RegionErrorType{FlashbackInProgressErr},
 		expect: &accessPathResult{
 			accessPath: []string{
-				"{addr: store1, replica-read: true, stale-read: false}",
+				"{addr: store1, replica-read: false, stale-read: false}",
 			},
 			respErr:         "region 0 is in flashback progress, FlashbackStartTS is 0",
 			respRegionError: nil,
@@ -2432,7 +2432,7 @@ func TestReplicaReadAccessPathByFlashbackInProgressCase(t *testing.T) {
 		accessErr: []RegionErrorType{DeadLineExceededErr, FlashbackInProgressErr, FlashbackInProgressErr},
 		expect: &accessPathResult{
 			accessPath: []string{
-				"{addr: store1, replica-read: true, stale-read: false}",
+				"{addr: store1, replica-read: false, stale-read: false}",
 				"{addr: store2, replica-read: true, stale-read: false}",
 				"{addr: store3, replica-read: true, stale-read: false}",
 			},
@@ -2633,7 +2633,7 @@ func TestReplicaReadAccessPathByLearnerCase(t *testing.T) {
 		expect: &accessPathResult{
 			accessPath: []string{
 				"{addr: store4, replica-read: true, stale-read: false}",
-				"{addr: store1, replica-read: true, stale-read: false}",
+				"{addr: store1, replica-read: false, stale-read: false}",
 			},
 			respErr:         "",
 			respRegionError: nil,
@@ -2700,7 +2700,7 @@ func TestReplicaReadAvoidSlowStore(t *testing.T) {
 				accessErr:       []RegionErrorType{},
 				expect: &accessPathResult{
 					accessPath: []string{
-						fmt.Sprintf("{addr: store%v, replica-read: %v, stale-read: %v}", expectedFirstStore, !staleRead, staleRead),
+						fmt.Sprintf("{addr: store%v, replica-read: %v, stale-read: %v}", expectedFirstStore, !staleRead && expectedFirstStore != 3, staleRead),
 					},
 					respErr:         "",
 					respRegionError: nil,
@@ -2723,7 +2723,7 @@ func TestReplicaReadAvoidSlowStore(t *testing.T) {
 			} else {
 				if withLabel {
 					// Prefer retrying leader.
-					expectedSecondPath = "{addr: store3, replica-read: true, stale-read: false}"
+					expectedSecondPath = "{addr: store3, replica-read: false, stale-read: false}"
 				} else {
 					// Retry any another replica.
 					expectedSecondPath = "{addr: store2, replica-read: true, stale-read: false}"
@@ -2763,13 +2763,8 @@ func TestReplicaReadAvoidSlowStore(t *testing.T) {
 					expectedSecondPath = "{addr: store2, replica-read: true, stale-read: false}"
 				}
 			} else {
-				if staleRead {
-					// Retry in leader read mode
-					expectedSecondPath = "{addr: store3, replica-read: false, stale-read: false}"
-				} else {
-					// Retry with the same mode, which is replica-read mode.
-					expectedSecondPath = "{addr: store3, replica-read: true, stale-read: false}"
-				}
+				// Retry in leader read mode will not use replica-read.
+				expectedSecondPath = "{addr: store3, replica-read: false, stale-read: false}"
 			}
 
 			ca = replicaSelectorAccessPathCase{
@@ -2782,7 +2777,7 @@ func TestReplicaReadAvoidSlowStore(t *testing.T) {
 				accessErr:       []RegionErrorType{ServerIsBusyErr},
 				expect: &accessPathResult{
 					accessPath: []string{
-						fmt.Sprintf("{addr: store%v, replica-read: %v, stale-read: %v}", expectedFirstStore, !staleRead, staleRead),
+						fmt.Sprintf("{addr: store%v, replica-read: %v, stale-read: %v}", expectedFirstStore, !staleRead && expectedFirstStore != 3, staleRead),
 						expectedSecondPath,
 					},
 					respErr:         "",