Skip to content

Commit 26bf6c5

Browse files
hard-invalidate region on RegionNotFound instead of async reload (tikv#1916)
X-Original-Commit: 33f56d8f139de91d3e75bc0019a3d83a0185dc9f
1 parent 3e27d52 commit 26bf6c5

4 files changed

Lines changed: 29 additions & 28 deletions

File tree

internal/locate/region_cache.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1828,18 +1828,6 @@ func (c *RegionCache) BatchLoadRegionsFromKey(bo *retry.Backoffer, startKey []by
18281828
return regions[len(regions)-1].EndKey(), nil
18291829
}
18301830

1831-
// AsyncInvalidateCachedRegion marks a cached region for reload on next access
1832-
// without invalidating it, so in-flight retries can still proceed.
1833-
// It sets needDelayedReloadReady directly, skipping the GC promotion from
1834-
// needDelayedReloadPending for faster reload.
1835-
func (c *RegionCache) AsyncInvalidateCachedRegion(id RegionVerID) {
1836-
cachedRegion := c.GetCachedRegionWithRLock(id)
1837-
if cachedRegion == nil {
1838-
return
1839-
}
1840-
cachedRegion.setSyncFlags(needDelayedReloadReady)
1841-
}
1842-
18431831
// InvalidateCachedRegion removes a cached Region.
18441832
func (c *RegionCache) InvalidateCachedRegion(id RegionVerID) {
18451833
c.InvalidateCachedRegionWithReason(id, Other)

internal/locate/region_request_state_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -288,9 +288,9 @@ func TestRegionCacheStaleRead(t *testing.T) {
288288
leaderAsyncReload: util.Some(false),
289289
leaderSuccessReplica: []string{"z1"},
290290
leaderSuccessReadType: SuccessStaleRead,
291-
followerRegionValid: true,
292-
followerAsyncReload: util.Some(true),
293-
// may async reload region and access it from leader.
291+
followerRegionValid: false,
292+
followerAsyncReload: util.Some(false),
293+
// region is hard-invalidated but the current request succeeds via leader retry.
294294
followerSuccessReplica: []string{"z1"},
295295
followerSuccessReadType: SuccessStaleRead,
296296
},

internal/locate/replica_selector.go

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@ import (
3333

3434
type replicaSelector struct {
3535
baseReplicaSelector
36-
replicaReadType kv.ReplicaReadType
37-
isStaleRead bool
38-
isReadOnlyReq bool
39-
option storeSelectorOp
40-
target *replica
41-
proxy *replica
42-
attempts int
36+
replicaReadType kv.ReplicaReadType
37+
isStaleRead bool
38+
isReadOnlyReq bool
39+
option storeSelectorOp
40+
target *replica
41+
proxy *replica
42+
attempts int
43+
regionInvalidatedForRetry bool // set when region is hard-invalidated but this selector should still retry on leader
4344
}
4445

4546
func newReplicaSelector(
@@ -100,7 +101,13 @@ func buildTiKVReplicas(region *Region) []*replica {
100101
}
101102

102103
func (s *replicaSelector) next(bo *retry.Backoffer, req *tikvrpc.Request) (rpcCtx *RPCContext, err error) {
103-
if !s.region.isValid() {
104+
if s.regionInvalidatedForRetry {
105+
// Allow one more attempt on this selector even though the region is
106+
// invalidated. This is set by onRegionNotFound so that the current
107+
// request can retry on the leader while concurrent requests are
108+
// stopped by the hard invalidation.
109+
s.regionInvalidatedForRetry = false
110+
} else if !s.region.isValid() {
104111
metrics.TiKVReplicaSelectorFailureCounter.WithLabelValues("invalid").Inc()
105112
return nil, nil
106113
}
@@ -528,12 +535,16 @@ func (s *replicaSelector) onRegionNotFound(
528535
leaderIdx := s.region.getStore().workTiKVIdx
529536
leader := s.replicas[leaderIdx]
530537
if !leader.isExhausted(1, 0) {
531-
// if the request is not sent to leader, we can retry it with leader and invalidate the region cache asynchronously. It helps in the scenario
532-
// where region is split by the leader but not yet created in replica due to replica down.
538+
// if the request is not sent to leader, we can retry it with leader and invalidate the region cache
539+
// immediately. It helps in the scenario where region is split by the leader but not yet created
540+
// in replica due to replica down.
541+
// Hard-invalidate the region so that concurrent requests stop using the stale region,
542+
// but allow this selector to retry on the leader via regionInvalidatedForRetry.
533543
req.ReplicaRead = false
534544
req.ReplicaReadType = kv.ReplicaReadLeader
535545
s.replicaReadType = kv.ReplicaReadLeader
536-
s.regionCache.AsyncInvalidateCachedRegion(ctx.Region)
546+
s.regionInvalidatedForRetry = true
547+
s.regionCache.InvalidateCachedRegion(ctx.Region)
537548
return true, nil
538549
}
539550
s.regionCache.InvalidateCachedRegion(ctx.Region)

internal/locate/replica_selector_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,14 +1114,16 @@ func TestReplicaReadAccessPathByBasicCase(t *testing.T) {
11141114
case kv.ReplicaReadLeader:
11151115
accessPath = []string{"{addr: store1, replica-read: false, stale-read: false}"}
11161116
case kv.ReplicaReadFollower:
1117-
// For RegionNotFoundErr from follower, it will retry on leader
1117+
// For RegionNotFoundErr from follower, it will retry on leader.
1118+
// The region is hard-invalidated to stop concurrent requests,
1119+
// but the current selector bypasses the validity check.
11181120
if tp == RegionNotFoundErr {
11191121
accessPath = []string{
11201122
"{addr: store2, replica-read: true, stale-read: false}",
11211123
"{addr: store1, replica-read: false, stale-read: false}",
11221124
}
11231125
respRegionError = nil
1124-
regionIsValid = true
1126+
regionIsValid = false
11251127
} else {
11261128
accessPath = []string{"{addr: store2, replica-read: true, stale-read: false}"}
11271129
}

0 commit comments

Comments
 (0)