region cache: check if the pd returned regions covers the ranges (#1377)

* add reproduce test

Signed-off-by: you06 <you1474600@gmail.com>

* check if the pd returned regions covers the ranges

Signed-off-by: you06 <you1474600@gmail.com>

* handle limit

Signed-off-by: you06 <you1474600@gmail.com>

* fix lint

Signed-off-by: you06 <you1474600@gmail.com>

* add TODO for  func

Signed-off-by: you06 <you1474600@gmail.com>

* update tidb for integration test

Signed-off-by: you06 <you1474600@gmail.com>

* rename gap detection function

Signed-off-by: you06 <you1474600@gmail.com>

* address comment

Signed-off-by: you06 <you1474600@gmail.com>

* address comment

Signed-off-by: you06 <you1474600@gmail.com>

* add half bounded cases

Signed-off-by: you06 <you1474600@gmail.com>

---------

Signed-off-by: you06 <you1474600@gmail.com>
This commit is contained in:
you06 2024-07-03 18:58:01 +09:00 committed by GitHub
parent 0206a3c142
commit d73cc1ed65
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 248 additions and 28 deletions

View File

@ -7,11 +7,11 @@ require (
github.com/pingcap/errors v0.11.5-0.20240318064555-6bd07397691f
github.com/pingcap/failpoint v0.0.0-20240527053858-9b3b6e34194a
github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4
github.com/pingcap/tidb v1.1.0-beta.0.20240430081142-7481aa6d0b8b
github.com/pingcap/tidb v1.1.0-beta.0.20240703042657-230bbc2ef5ef
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.9.0
github.com/tidwall/gjson v1.14.1
github.com/tikv/client-go/v2 v2.0.8-0.20240621090319-d4f0f4cf12a9
github.com/tikv/client-go/v2 v2.0.8-0.20240626064248-4a72526f6c30
github.com/tikv/pd/client v0.0.0-20240620115049-049de1761e56
go.uber.org/goleak v1.3.0
)
@ -57,6 +57,7 @@ require (
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/ncw/directio v1.0.5 // indirect
github.com/ngaut/pools v0.0.0-20180318154953-b7bc8c42aac7 // indirect
github.com/ngaut/sync2 v0.0.0-20141008032647-7a24ed77b2ef // indirect
@ -67,13 +68,13 @@ require (
github.com/pingcap/goleveldb v0.0.0-20191226122134-f82aafb29989 // indirect
github.com/pingcap/log v1.1.1-0.20240314023424-862ccc32f18d // indirect
github.com/pingcap/sysutil v1.0.1-0.20240311050922-ae81ee01f3a5 // indirect
github.com/pingcap/tidb/pkg/parser v0.0.0-20240430081142-7481aa6d0b8b // indirect
github.com/pingcap/tidb/pkg/parser v0.0.0-20240703042657-230bbc2ef5ef // indirect
github.com/pingcap/tipb v0.0.0-20240318032315-55a7867ddd50 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
github.com/prometheus/client_golang v1.19.0 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.53.0 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/qri-io/jsonpointer v0.1.1 // indirect
github.com/qri-io/jsonschema v0.2.1 // indirect
@ -105,12 +106,12 @@ require (
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
golang.org/x/tools v0.22.0 // indirect
gonum.org/v1/gonum v0.8.2 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
google.golang.org/grpc v1.63.2 // indirect
google.golang.org/protobuf v1.34.1 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
@ -121,8 +122,3 @@ replace (
github.com/tikv/client-go/v2 => ../
)
replace (
github.com/pingcap/tidb => github.com/you06/tidb v1.1.0-beta.0.20240624051137-006cbc9cc886
github.com/pingcap/tidb/pkg/parser => github.com/you06/tidb/pkg/parser v0.0.0-20240624051137-006cbc9cc886
)

View File

@ -311,6 +311,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/ncw/directio v1.0.4/go.mod h1:CKGdcN7StAaqjT7Qack3lAXeX4pjnyc46YeqZH1yWVY=
github.com/ncw/directio v1.0.5 h1:JSUBhdjEvVaJvOoyPAbcW0fnd0tvRXD76wEfZ1KcQz4=
github.com/ncw/directio v1.0.5/go.mod h1:rX/pKEYkOXBGOggmcyJeJGloCkleSvphPx2eV3t6ROk=
@ -363,6 +365,10 @@ github.com/pingcap/log v1.1.1-0.20240314023424-862ccc32f18d h1:y3EueKVfVykdpTyfU
github.com/pingcap/log v1.1.1-0.20240314023424-862ccc32f18d/go.mod h1:ORfBOFp1eteu2odzsyaxI+b8TzJwgjwyQcGhI+9SfEA=
github.com/pingcap/sysutil v1.0.1-0.20240311050922-ae81ee01f3a5 h1:T4pXRhBflzDeAhmOQHNPRRogMYxP13V7BkYw3ZsoSfE=
github.com/pingcap/sysutil v1.0.1-0.20240311050922-ae81ee01f3a5/go.mod h1:rlimy0GcTvjiJqvD5mXTRr8O2eNZPBrcUgiWVYp9530=
github.com/pingcap/tidb v1.1.0-beta.0.20240703042657-230bbc2ef5ef h1:qf0QuRpodj5Yvm22BuuBoGFaiolL7KdkEYdnQ7DUstg=
github.com/pingcap/tidb v1.1.0-beta.0.20240703042657-230bbc2ef5ef/go.mod h1:vfEq5Kh+kAxB6ROcwTubBBk1lzZmIHMVzUwFYwQSJIA=
github.com/pingcap/tidb/pkg/parser v0.0.0-20240703042657-230bbc2ef5ef h1:HhIocGusIROnXhcXQ9DQL/+RgJpGXGlOkI/IX25pNrA=
github.com/pingcap/tidb/pkg/parser v0.0.0-20240703042657-230bbc2ef5ef/go.mod h1:c/4la2yfv1vBYvtIG8WCDyDinLMDIUC5+zLRHiafY+Y=
github.com/pingcap/tipb v0.0.0-20240318032315-55a7867ddd50 h1:fVNBE06Rjec+EIHaYAKAHa/bIt5lnu3Zh9O6kV7ZAdg=
github.com/pingcap/tipb v0.0.0-20240318032315-55a7867ddd50/go.mod h1:A7mrd7WHBl1o63LE2bIBGEJMTNWXqhgmYiOvMLxozfs=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
@ -378,15 +384,15 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v0.9.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE=
github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
@ -494,10 +500,6 @@ github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q
github.com/xitongsys/parquet-go v1.6.3-0.20240520233950-75e935fc3e17 h1:mr+7gGPUasLmH3/5Iv1zwQwiY0WgGO21Ym7Q4FVw+xs=
github.com/xitongsys/parquet-go v1.6.3-0.20240520233950-75e935fc3e17/go.mod h1:u9udtIEWeBkphB2isZ8V8xVIMWgcUobH+7FRMO/Ld6c=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/you06/tidb v1.1.0-beta.0.20240624051137-006cbc9cc886 h1:jw3Z7XTxtzPgqUqhMQHOlmETHvk+bxR9uG+30ZtWlPQ=
github.com/you06/tidb v1.1.0-beta.0.20240624051137-006cbc9cc886/go.mod h1:QBEHAvrZRsmpbrYcBkphRVf8tP8OaYPl2uetSb5PP64=
github.com/you06/tidb/pkg/parser v0.0.0-20240624051137-006cbc9cc886 h1:Eqw/mDIP4bH+g/KcYZngtA1X9zQ+mZn4CMDJKBumZI8=
github.com/you06/tidb/pkg/parser v0.0.0-20240624051137-006cbc9cc886/go.mod h1:c/4la2yfv1vBYvtIG8WCDyDinLMDIUC5+zLRHiafY+Y=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
@ -608,8 +610,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -708,8 +710,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -748,8 +750,8 @@ google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/grpc/examples v0.0.0-20231221225426-4f03f3ff32c9 h1:ATnmU8nL2NfIyTSiBvJVDIDIr3qBmeW+c7z7XU21eWs=
google.golang.org/grpc/examples v0.0.0-20231221225426-4f03f3ff32c9/go.mod h1:j5uROIAAgi3YmtiETMt1LW0d/lHqQ7wwrIY4uGRXLQ4=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -2154,7 +2154,12 @@ func (c *RegionCache) scanRegions(bo *retry.Backoffer, startKey, endKey []byte,
metrics.RegionCacheCounterWithScanRegionsOK.Inc()
if len(regionsInfo) == 0 {
return nil, errors.Errorf("PD returned no region, limit: %d", limit)
backoffErr = errors.Errorf("PD returned no region, limit: %d", limit)
continue
}
if regionsHaveGapInRanges([]pd.KeyRange{{StartKey: startKey, EndKey: endKey}}, regionsInfo, limit) {
backoffErr = errors.Errorf("PD returned regions have gaps, limit: %d", limit)
continue
}
return c.handleRegionInfos(bo, regionsInfo, true)
}
@ -2210,15 +2215,78 @@ func (c *RegionCache) batchScanRegions(bo *retry.Backoffer, keyRanges []pd.KeyRa
metrics.RegionCacheCounterWithBatchScanRegionsOK.Inc()
if len(regionsInfo) == 0 {
return nil, errors.Errorf(
backoffErr = errors.Errorf(
"PD returned no region, range num: %d, limit: %d",
len(keyRanges), limit,
)
continue
}
if regionsHaveGapInRanges(keyRanges, regionsInfo, limit) {
backoffErr = errors.Errorf(
"PD returned regions have gaps, range num: %d, limit: %d",
len(keyRanges), limit,
)
continue
}
return c.handleRegionInfos(bo, regionsInfo, opt.needRegionHasLeaderPeer)
}
}
// regionsHaveGapInRanges checks if the loaded regions can fully cover the key ranges.
// If there are any gaps between the regions, it returns true, then the requests might be retried.
// TODO: remove this function after PD client supports gap detection and handling it.
func regionsHaveGapInRanges(ranges []pd.KeyRange, regionsInfo []*pd.Region, limit int) bool {
if len(ranges) == 0 {
return false
}
if len(regionsInfo) == 0 {
return true
}
checkIdx := 0 // checked index of ranges
checkKey := ranges[0].StartKey // checked key of ranges
for _, r := range regionsInfo {
if r.Meta == nil {
return true
}
if bytes.Compare(r.Meta.StartKey, checkKey) > 0 {
// there is a gap between returned region's start_key and current check key
return true
}
if len(r.Meta.EndKey) == 0 {
// the current region contains all the rest ranges.
return false
}
checkKey = r.Meta.EndKey
for len(ranges[checkIdx].EndKey) > 0 && bytes.Compare(checkKey, ranges[checkIdx].EndKey) >= 0 {
// the end_key of returned region can cover multi ranges.
checkIdx++
if checkIdx == len(ranges) {
// all ranges are covered.
return false
}
}
if bytes.Compare(checkKey, ranges[checkIdx].StartKey) < 0 {
// if check_key < start_key, move it forward to start_key.
checkKey = ranges[checkIdx].StartKey
}
}
if limit > 0 && len(regionsInfo) == limit {
// the regionsInfo is limited by the limit, so there may be some ranges not covered.
// But the previous regions are continuous, so we just need to check the rest ranges.
return false
}
if checkIdx < len(ranges)-1 {
// there are still some ranges not covered.
return true
}
if len(checkKey) == 0 {
return false
} else if len(ranges[checkIdx].EndKey) == 0 {
return true
}
return bytes.Compare(checkKey, ranges[checkIdx].EndKey) < 0
}
func (c *RegionCache) batchScanRegionsFallback(bo *retry.Backoffer, keyRanges []pd.KeyRange, limit int, opts ...BatchLocateKeyRangesOpt) ([]*Region, error) {
logutil.BgLogger().Warn("batch scan regions fallback to scan regions", zap.Int("range-num", len(keyRanges)))
res := make([]*Region, 0, len(keyRanges))

View File

@ -2689,3 +2689,147 @@ func (s *testRegionCacheSuite) testBatchScanRegions() {
s.cluster.Split(newID1, newID2, []byte("e1"), newPeers, newPeers[0])
}, []uint64{regions[1], regions[3], regions[4], newID1, regions[5], regions[6]})
}
func (s *testRegionCacheSuite) TestRangesAreCoveredCheck() {
check := func(ranges []string, regions []string, limit int, expect bool) {
rs := make([]pd.KeyRange, 0, len(ranges)/2)
for i := 0; i < len(ranges); i += 2 {
rs = append(rs, pd.KeyRange{StartKey: []byte(ranges[i]), EndKey: []byte(ranges[i+1])})
}
rgs := make([]*pd.Region, 0, len(regions))
for i := 0; i < len(regions); i += 2 {
rgs = append(rgs, &pd.Region{Meta: &metapb.Region{
StartKey: []byte(regions[i]),
EndKey: []byte(regions[i+1]),
}})
}
s.Equal(expect, regionsHaveGapInRanges(rs, rgs, limit))
}
boundCases := [][]string{
{"a", "c"},
{"a", "b", "b", "c"},
{"a", "a1", "a1", "b", "b", "b1", "b1", "c"},
}
for _, boundCase := range boundCases {
// positive
check(boundCase, []string{"a", "c"}, -1, false)
check(boundCase, []string{"a", ""}, -1, false)
check(boundCase, []string{"", "c"}, -1, false)
// negative
check(boundCase, []string{"a", "b"}, -1, true)
check(boundCase, []string{"b", "c"}, -1, true)
check(boundCase, []string{"b", ""}, -1, true)
check(boundCase, []string{"", "b"}, -1, true)
// positive
check(boundCase, []string{"a", "b", "b", "c"}, -1, false)
check(boundCase, []string{"", "b", "b", "c"}, -1, false)
check(boundCase, []string{"a", "b", "b", ""}, -1, false)
check(boundCase, []string{"", "b", "b", ""}, -1, false)
// negative
check(boundCase, []string{"a", "b", "b1", "c"}, -1, true)
check(boundCase, []string{"", "b", "b1", "c"}, -1, true)
check(boundCase, []string{"a", "b", "b1", ""}, -1, true)
check(boundCase, []string{"", "b", "b1", ""}, -1, true)
check(boundCase, []string{}, -1, true)
}
nonContinuousCases := [][]string{
{"a", "b", "c", "d"},
{"a", "b1", "b1", "b", "c", "d"},
{"a", "b", "c", "c1", "c1", "d"},
{"a", "b1", "b1", "b", "c", "c1", "c1", "d"},
}
for _, nonContinuousCase := range nonContinuousCases {
// positive
check(nonContinuousCase, []string{"a", "d"}, -1, false)
check(nonContinuousCase, []string{"", "d"}, -1, false)
check(nonContinuousCase, []string{"a", ""}, -1, false)
check(nonContinuousCase, []string{"", ""}, -1, false)
// negative
check(nonContinuousCase, []string{"a", "b"}, -1, true)
check(nonContinuousCase, []string{"b", "c"}, -1, true)
check(nonContinuousCase, []string{"c", "d"}, -1, true)
check(nonContinuousCase, []string{"", "b"}, -1, true)
check(nonContinuousCase, []string{"c", ""}, -1, true)
}
unboundCases := [][]string{
{"", ""},
{"", "b", "b", ""},
{"", "a1", "a1", "b", "b", "b1", "b1", ""},
}
for _, unboundCase := range unboundCases {
// positive
check(unboundCase, []string{"", ""}, -1, false)
// negative
check(unboundCase, []string{"a", "c"}, -1, true)
check(unboundCase, []string{"a", ""}, -1, true)
check(unboundCase, []string{"", "c"}, -1, true)
// positive
check(unboundCase, []string{"", "b", "b", ""}, -1, false)
// negative
check(unboundCase, []string{"", "b", "b1", ""}, -1, true)
check(unboundCase, []string{"a", "b", "b", ""}, -1, true)
check(unboundCase, []string{"", "b", "b", "c"}, -1, true)
check(unboundCase, []string{}, -1, true)
}
// test half bounded ranges
check([]string{"", "b"}, []string{"", "a"}, -1, true)
check([]string{"", "b"}, []string{"", "a"}, 1, false) // it's just limitation reached
check([]string{"", "b"}, []string{"", "a"}, 2, true)
check([]string{"a", ""}, []string{"b", ""}, -1, true)
check([]string{"a", ""}, []string{"b", ""}, 1, true)
check([]string{"a", ""}, []string{"b", "c"}, 1, true)
check([]string{"a", ""}, []string{"a", ""}, -1, false)
}
func (s *testRegionCacheSuite) TestScanRegionsWithGaps() {
// Split at "a", "c", "e"
// nil --- 'a' --- 'c' --- 'e' --- nil
// <- 0 -> <- 1 -> <- 2 -> <- 3 -->
regions := s.cluster.AllocIDs(3)
regions = append([]uint64{s.region1}, regions...)
peers := [][]uint64{{s.peer1, s.peer2}}
for i := 0; i < 3; i++ {
peers = append(peers, s.cluster.AllocIDs(2))
}
for i := 0; i < 3; i++ {
s.cluster.Split(regions[i], regions[i+1], []byte{'a' + 2*byte(i)}, peers[i+1], peers[i+1][0])
}
// the last region is not reported to PD yet
getRegionIDsWithInject := func(fn func() ([]*Region, error)) []uint64 {
s.cache.clear()
err := failpoint.Enable("tikvclient/mockSplitRegionNotReportToPD", fmt.Sprintf(`return(%d)`, regions[2]))
s.Nil(err)
resCh := make(chan []*Region)
errCh := make(chan error)
go func() {
rs, err := fn()
errCh <- err
resCh <- rs
}()
time.Sleep(time.Second)
failpoint.Disable("tikvclient/mockSplitRegionNotReportToPD")
s.Nil(<-errCh)
rs := <-resCh
regionIDs := make([]uint64, 0, len(rs))
for _, r := range rs {
regionIDs = append(regionIDs, r.GetID())
}
return regionIDs
}
scanRegionRes := getRegionIDsWithInject(func() ([]*Region, error) {
return s.cache.BatchLoadRegionsWithKeyRange(s.bo, []byte(""), []byte(""), 10)
})
s.Equal(scanRegionRes, regions)
batchScanRegionRes := getRegionIDsWithInject(func() ([]*Region, error) {
return s.cache.BatchLoadRegionsWithKeyRanges(s.bo, []pd.KeyRange{{StartKey: []byte{}, EndKey: []byte{}}}, 10)
})
s.Equal(batchScanRegionRes, regions)
}

View File

@ -47,6 +47,7 @@ import (
"github.com/pingcap/kvproto/pkg/kvrpcpb"
"github.com/pingcap/kvproto/pkg/metapb"
"github.com/tikv/client-go/v2/internal/mockstore/cluster"
"github.com/tikv/client-go/v2/util"
pd "github.com/tikv/pd/client"
)
@ -378,6 +379,15 @@ func (c *Cluster) ScanRegions(startKey, endKey []byte, limit int, opts ...pd.Get
regions = regions[:endPos]
}
}
if rid, err := util.EvalFailpoint("mockSplitRegionNotReportToPD"); err == nil {
notReportRegionID := uint64(rid.(int))
for i, r := range regions {
if r.Meta.Id == notReportRegionID {
regions = append(regions[:i], regions[i+1:]...)
break
}
}
}
if limit > 0 && len(regions) > limit {
regions = regions[:limit]
}