github.com/pingcap/tidb-lightning@v5.0.0-rc.0.20210428090220-84b649866577+incompatible/lightning/backend/localhelper_test.go (about) 1 // Copyright 2019 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package backend 15 16 import ( 17 "bytes" 18 "context" 19 "sync" 20 "time" 21 22 "github.com/pingcap/br/pkg/restore" 23 . "github.com/pingcap/check" 24 "github.com/pingcap/errors" 25 "github.com/pingcap/kvproto/pkg/metapb" 26 "github.com/pingcap/kvproto/pkg/pdpb" 27 "github.com/pingcap/tidb/kv" 28 "github.com/pingcap/tidb/sessionctx/stmtctx" 29 "github.com/pingcap/tidb/tablecodec" 30 "github.com/pingcap/tidb/types" 31 "github.com/pingcap/tidb/util/codec" 32 "github.com/tikv/pd/server/core" 33 "github.com/tikv/pd/server/schedule/placement" 34 ) 35 36 type testClient struct { 37 mu sync.RWMutex 38 stores map[uint64]*metapb.Store 39 regions map[uint64]*restore.RegionInfo 40 regionsInfo *core.RegionsInfo // For now it's only used in ScanRegions 41 nextRegionID uint64 42 splitCount int 43 hook clientHook 44 } 45 46 func newTestClient( 47 stores map[uint64]*metapb.Store, 48 regions map[uint64]*restore.RegionInfo, 49 nextRegionID uint64, 50 hook clientHook, 51 ) *testClient { 52 regionsInfo := core.NewRegionsInfo() 53 for _, regionInfo := range regions { 54 regionsInfo.AddRegion(core.NewRegionInfo(regionInfo.Region, regionInfo.Leader)) 55 } 56 return &testClient{ 57 stores: stores, 58 regions: regions, 59 regionsInfo: regionsInfo, 60 nextRegionID: nextRegionID, 61 hook: hook, 62 } 63 } 64 65 func (c *testClient) GetAllRegions() map[uint64]*restore.RegionInfo { 66 c.mu.RLock() 67 defer c.mu.RUnlock() 68 return c.regions 69 } 70 71 func (c *testClient) GetStore(ctx context.Context, storeID uint64) (*metapb.Store, error) { 72 c.mu.RLock() 73 defer c.mu.RUnlock() 74 store, ok := c.stores[storeID] 75 if !ok { 76 return nil, errors.Errorf("store not found") 77 } 78 return store, nil 79 } 80 81 func (c *testClient) GetRegion(ctx context.Context, key []byte) (*restore.RegionInfo, error) { 82 c.mu.RLock() 83 defer c.mu.RUnlock() 84 for _, region := range c.regions { 85 if bytes.Compare(key, region.Region.StartKey) >= 0 && 86 (len(region.Region.EndKey) == 0 || bytes.Compare(key, region.Region.EndKey) < 0) { 87 return region, nil 88 } 89 } 90 return nil, errors.Errorf("region not found: key=%s", string(key)) 91 } 92 93 func (c *testClient) GetRegionByID(ctx context.Context, regionID uint64) (*restore.RegionInfo, error) { 94 c.mu.RLock() 95 defer c.mu.RUnlock() 96 region, ok := c.regions[regionID] 97 if !ok { 98 return nil, errors.Errorf("region not found: id=%d", regionID) 99 } 100 return region, nil 101 } 102 103 func (c *testClient) SplitRegion( 104 ctx context.Context, 105 regionInfo *restore.RegionInfo, 106 key []byte, 107 ) (*restore.RegionInfo, error) { 108 c.mu.Lock() 109 defer c.mu.Unlock() 110 var target *restore.RegionInfo 111 splitKey := codec.EncodeBytes([]byte{}, key) 112 for _, region := range c.regions { 113 if bytes.Compare(splitKey, region.Region.StartKey) >= 0 && 114 (len(region.Region.EndKey) == 0 || bytes.Compare(splitKey, region.Region.EndKey) < 0) { 115 target = region 116 } 117 } 118 if target == nil { 119 return nil, errors.Errorf("region not found: key=%s", string(key)) 120 } 121 newRegion := &restore.RegionInfo{ 122 Region: &metapb.Region{ 123 Peers: target.Region.Peers, 124 Id: c.nextRegionID, 125 StartKey: target.Region.StartKey, 126 EndKey: splitKey, 127 RegionEpoch: &metapb.RegionEpoch{ 128 Version: target.Region.RegionEpoch.Version, 129 ConfVer: target.Region.RegionEpoch.ConfVer + 1, 130 }, 131 }, 132 } 133 c.regions[c.nextRegionID] = newRegion 134 c.regionsInfo.SetRegion(core.NewRegionInfo(newRegion.Region, newRegion.Leader)) 135 c.nextRegionID++ 136 target.Region.StartKey = splitKey 137 target.Region.RegionEpoch.ConfVer++ 138 c.regions[target.Region.Id] = target 139 c.regionsInfo.SetRegion(core.NewRegionInfo(target.Region, target.Leader)) 140 return newRegion, nil 141 } 142 143 func (c *testClient) BatchSplitRegionsWithOrigin( 144 ctx context.Context, regionInfo *restore.RegionInfo, keys [][]byte, 145 ) (*restore.RegionInfo, []*restore.RegionInfo, error) { 146 if c.hook != nil { 147 regionInfo, keys = c.hook.BeforeSplitRegion(ctx, regionInfo, keys) 148 } 149 150 c.splitCount++ 151 c.mu.Lock() 152 defer c.mu.Unlock() 153 newRegions := make([]*restore.RegionInfo, 0) 154 var region *restore.RegionInfo 155 for _, key := range keys { 156 var target *restore.RegionInfo 157 splitKey := codec.EncodeBytes([]byte{}, key) 158 for _, region := range c.regions { 159 if region.ContainsInterior(splitKey) { 160 target = region 161 } 162 } 163 if target == nil { 164 continue 165 } 166 if target.Region.RegionEpoch.Version != regionInfo.Region.RegionEpoch.Version || 167 target.Region.RegionEpoch.ConfVer != regionInfo.Region.RegionEpoch.ConfVer { 168 return regionInfo, nil, errors.New("epoch not match") 169 } 170 newRegion := &restore.RegionInfo{ 171 Region: &metapb.Region{ 172 Peers: target.Region.Peers, 173 Id: c.nextRegionID, 174 StartKey: target.Region.StartKey, 175 EndKey: splitKey, 176 }, 177 } 178 c.regions[c.nextRegionID] = newRegion 179 c.regionsInfo.SetRegion(core.NewRegionInfo(newRegion.Region, newRegion.Leader)) 180 c.nextRegionID++ 181 target.Region.StartKey = splitKey 182 c.regions[target.Region.Id] = target 183 region = target 184 newRegions = append(newRegions, newRegion) 185 } 186 if region != nil { 187 c.regionsInfo.SetRegion(core.NewRegionInfo(region.Region, region.Leader)) 188 } 189 var err error 190 if c.hook != nil { 191 newRegions, err = c.hook.AfterSplitRegion(ctx, region, keys, newRegions, nil) 192 } 193 194 return region, newRegions, err 195 } 196 197 func (c *testClient) BatchSplitRegions( 198 ctx context.Context, regionInfo *restore.RegionInfo, keys [][]byte, 199 ) ([]*restore.RegionInfo, error) { 200 _, newRegions, err := c.BatchSplitRegionsWithOrigin(ctx, regionInfo, keys) 201 return newRegions, err 202 } 203 204 func (c *testClient) ScatterRegion(ctx context.Context, regionInfo *restore.RegionInfo) error { 205 return nil 206 } 207 208 func (c *testClient) GetOperator(ctx context.Context, regionID uint64) (*pdpb.GetOperatorResponse, error) { 209 return &pdpb.GetOperatorResponse{ 210 Header: new(pdpb.ResponseHeader), 211 }, nil 212 } 213 214 func (c *testClient) ScanRegions(ctx context.Context, key, endKey []byte, limit int) ([]*restore.RegionInfo, error) { 215 if c.hook != nil { 216 key, endKey, limit = c.hook.BeforeScanRegions(ctx, key, endKey, limit) 217 } 218 219 infos := c.regionsInfo.ScanRange(key, endKey, limit) 220 regions := make([]*restore.RegionInfo, 0, len(infos)) 221 for _, info := range infos { 222 regions = append(regions, &restore.RegionInfo{ 223 Region: info.GetMeta(), 224 Leader: info.GetLeader(), 225 }) 226 } 227 228 var err error 229 if c.hook != nil { 230 regions, err = c.hook.AfterScanRegions(regions, nil) 231 } 232 return regions, err 233 } 234 235 func (c *testClient) GetPlacementRule(ctx context.Context, groupID, ruleID string) (r placement.Rule, err error) { 236 return 237 } 238 239 func (c *testClient) SetPlacementRule(ctx context.Context, rule placement.Rule) error { 240 return nil 241 } 242 243 func (c *testClient) DeletePlacementRule(ctx context.Context, groupID, ruleID string) error { 244 return nil 245 } 246 247 func (c *testClient) SetStoresLabel(ctx context.Context, stores []uint64, labelKey, labelValue string) error { 248 return nil 249 } 250 251 func cloneRegion(region *restore.RegionInfo) *restore.RegionInfo { 252 r := &metapb.Region{} 253 if region.Region != nil { 254 b, _ := region.Region.Marshal() 255 _ = r.Unmarshal(b) 256 } 257 258 l := &metapb.Peer{} 259 if region.Region != nil { 260 b, _ := region.Region.Marshal() 261 _ = l.Unmarshal(b) 262 } 263 return &restore.RegionInfo{Region: r, Leader: l} 264 } 265 266 // region: [, aay), [aay, bba), [bba, bbh), [bbh, cca), [cca, ) 267 func initTestClient(keys [][]byte, hook clientHook) *testClient { 268 peers := make([]*metapb.Peer, 1) 269 peers[0] = &metapb.Peer{ 270 Id: 1, 271 StoreId: 1, 272 } 273 regions := make(map[uint64]*restore.RegionInfo) 274 for i := uint64(1); i < uint64(len(keys)); i++ { 275 startKey := keys[i-1] 276 if len(startKey) != 0 { 277 startKey = codec.EncodeBytes([]byte{}, startKey) 278 } 279 endKey := keys[i] 280 if len(endKey) != 0 { 281 endKey = codec.EncodeBytes([]byte{}, endKey) 282 } 283 regions[i] = &restore.RegionInfo{ 284 Region: &metapb.Region{ 285 Id: i, 286 Peers: peers, 287 StartKey: startKey, 288 EndKey: endKey, 289 RegionEpoch: &metapb.RegionEpoch{ConfVer: 1, Version: 1}, 290 }, 291 } 292 } 293 stores := make(map[uint64]*metapb.Store) 294 stores[1] = &metapb.Store{ 295 Id: 1, 296 } 297 return newTestClient(stores, regions, uint64(len(keys)), hook) 298 } 299 300 func checkRegionRanges(c *C, regions []*restore.RegionInfo, keys [][]byte) { 301 //c.Assert(len(regions)+1, Equals, len(keys)) 302 for i, r := range regions { 303 _, regionStart, _ := codec.DecodeBytes(r.Region.StartKey, []byte{}) 304 _, regionEnd, _ := codec.DecodeBytes(r.Region.EndKey, []byte{}) 305 c.Assert(regionStart, DeepEquals, keys[i]) 306 c.Assert(regionEnd, DeepEquals, keys[i+1]) 307 } 308 } 309 310 type clientHook interface { 311 BeforeSplitRegion(ctx context.Context, regionInfo *restore.RegionInfo, keys [][]byte) (*restore.RegionInfo, [][]byte) 312 AfterSplitRegion(context.Context, *restore.RegionInfo, [][]byte, []*restore.RegionInfo, error) ([]*restore.RegionInfo, error) 313 BeforeScanRegions(ctx context.Context, key, endKey []byte, limit int) ([]byte, []byte, int) 314 AfterScanRegions([]*restore.RegionInfo, error) ([]*restore.RegionInfo, error) 315 } 316 317 type noopHook struct{} 318 319 func (h *noopHook) BeforeSplitRegion(ctx context.Context, regionInfo *restore.RegionInfo, keys [][]byte) (*restore.RegionInfo, [][]byte) { 320 return regionInfo, keys 321 } 322 func (h *noopHook) AfterSplitRegion(c context.Context, r *restore.RegionInfo, keys [][]byte, res []*restore.RegionInfo, err error) ([]*restore.RegionInfo, error) { 323 return res, err 324 } 325 func (h *noopHook) BeforeScanRegions(ctx context.Context, key, endKey []byte, limit int) ([]byte, []byte, int) { 326 return key, endKey, limit 327 } 328 func (h *noopHook) AfterScanRegions(res []*restore.RegionInfo, err error) ([]*restore.RegionInfo, error) { 329 return res, err 330 } 331 332 func (s *localSuite) doTestBatchSplitRegionByRanges(c *C, hook clientHook, errPat string) { 333 oldLimit := maxBatchSplitKeys 334 oldSplitBackoffTime := splitRegionBaseBackOffTime 335 maxBatchSplitKeys = 4 336 splitRegionBaseBackOffTime = time.Millisecond 337 defer func() { 338 maxBatchSplitKeys = oldLimit 339 splitRegionBaseBackOffTime = oldSplitBackoffTime 340 }() 341 342 keys := [][]byte{[]byte(""), []byte("aay"), []byte("bba"), []byte("bbh"), []byte("cca"), []byte("")} 343 client := initTestClient(keys, hook) 344 local := &local{ 345 splitCli: client, 346 } 347 ctx := context.Background() 348 349 // current region ranges: [, aay), [aay, bba), [bba, bbh), [bbh, cca), [cca, ) 350 rangeStart := codec.EncodeBytes([]byte{}, []byte("b")) 351 rangeEnd := codec.EncodeBytes([]byte{}, []byte("c")) 352 regions, err := paginateScanRegion(ctx, client, rangeStart, rangeEnd, 5) 353 c.Assert(err, IsNil) 354 // regions is: [aay, bba), [bba, bbh), [bbh, cca) 355 checkRegionRanges(c, regions, [][]byte{[]byte("aay"), []byte("bba"), []byte("bbh"), []byte("cca")}) 356 357 // generate: ranges [b, ba), [ba, bb), [bb, bc), ... [by, bz) 358 ranges := make([]Range, 0) 359 start := []byte{'b'} 360 for i := byte('a'); i <= 'z'; i++ { 361 end := []byte{'b', i} 362 ranges = append(ranges, Range{start: start, end: end}) 363 start = end 364 } 365 366 err = local.SplitAndScatterRegionByRanges(ctx, ranges, true) 367 if len(errPat) == 0 { 368 c.Assert(err, IsNil) 369 } else { 370 c.Assert(err, ErrorMatches, errPat) 371 return 372 } 373 374 // so with a batch split size of 4, there will be 7 time batch split 375 // 1. region: [aay, bba), keys: [b, ba, bb] 376 // 2. region: [bbh, cca), keys: [bc, bd, be, bf] 377 // 3. region: [bf, cca), keys: [bg, bh, bi, bj] 378 // 4. region: [bj, cca), keys: [bk, bl, bm, bn] 379 // 5. region: [bn, cca), keys: [bo, bp, bq, br] 380 // 6. region: [br, cca), keys: [bs, bt, bu, bv] 381 // 7. region: [bv, cca), keys: [bw, bx, by, bz] 382 383 // since it may encounter error retries, here only check the lower threshold. 384 c.Assert(client.splitCount >= 7, IsTrue) 385 386 // check split ranges 387 regions, err = paginateScanRegion(ctx, client, rangeStart, rangeEnd, 5) 388 c.Assert(err, IsNil) 389 result := [][]byte{[]byte("b"), []byte("ba"), []byte("bb"), []byte("bba"), []byte("bbh"), []byte("bc"), 390 []byte("bd"), []byte("be"), []byte("bf"), []byte("bg"), []byte("bh"), []byte("bi"), []byte("bj"), 391 []byte("bk"), []byte("bl"), []byte("bm"), []byte("bn"), []byte("bo"), []byte("bp"), []byte("bq"), 392 []byte("br"), []byte("bs"), []byte("bt"), []byte("bu"), []byte("bv"), []byte("bw"), []byte("bx"), 393 []byte("by"), []byte("bz"), []byte("cca")} 394 checkRegionRanges(c, regions, result) 395 } 396 397 func (s *localSuite) TestBatchSplitRegionByRanges(c *C) { 398 s.doTestBatchSplitRegionByRanges(c, nil, "") 399 } 400 401 type scanRegionEmptyHook struct { 402 noopHook 403 cnt int 404 } 405 406 func (h *scanRegionEmptyHook) AfterScanRegions(res []*restore.RegionInfo, err error) ([]*restore.RegionInfo, error) { 407 h.cnt++ 408 // skip the first call 409 if h.cnt == 1 { 410 return res, err 411 } 412 return nil, err 413 } 414 415 func (s *localSuite) TestBatchSplitRegionByRangesScanFailed(c *C) { 416 s.doTestBatchSplitRegionByRanges(c, &scanRegionEmptyHook{}, "paginate scan region returns empty result") 417 } 418 419 type splitRegionEpochNotMatchHook struct { 420 noopHook 421 } 422 423 func (h *splitRegionEpochNotMatchHook) BeforeSplitRegion(ctx context.Context, regionInfo *restore.RegionInfo, keys [][]byte) (*restore.RegionInfo, [][]byte) { 424 regionInfo = cloneRegion(regionInfo) 425 // decrease the region epoch, so split region will fail 426 regionInfo.Region.RegionEpoch.Version-- 427 return regionInfo, keys 428 } 429 430 func (s *localSuite) TestBatchSplitByRangesEpochNotMatch(c *C) { 431 s.doTestBatchSplitRegionByRanges(c, &splitRegionEpochNotMatchHook{}, "batch split regions failed: epoch not match") 432 } 433 434 // return epoch not match error in every other call 435 type splitRegionEpochNotMatchHookRandom struct { 436 noopHook 437 cnt int 438 } 439 440 func (h *splitRegionEpochNotMatchHookRandom) BeforeSplitRegion(ctx context.Context, regionInfo *restore.RegionInfo, keys [][]byte) (*restore.RegionInfo, [][]byte) { 441 h.cnt++ 442 if h.cnt%2 != 0 { 443 return regionInfo, keys 444 } 445 regionInfo = cloneRegion(regionInfo) 446 // decrease the region epoch, so split region will fail 447 regionInfo.Region.RegionEpoch.Version-- 448 return regionInfo, keys 449 } 450 451 func (s *localSuite) TestBatchSplitByRangesEpochNotMatchOnce(c *C) { 452 s.doTestBatchSplitRegionByRanges(c, &splitRegionEpochNotMatchHookRandom{}, "") 453 } 454 455 func (s *localSuite) doTestBatchSplitByRangesWithClusteredIndex(c *C, hook clientHook) { 456 oldLimit := maxBatchSplitKeys 457 oldSplitBackoffTime := splitRegionBaseBackOffTime 458 maxBatchSplitKeys = 10 459 splitRegionBaseBackOffTime = time.Millisecond 460 defer func() { 461 maxBatchSplitKeys = oldLimit 462 splitRegionBaseBackOffTime = oldSplitBackoffTime 463 }() 464 465 stmtCtx := new(stmtctx.StatementContext) 466 467 tableId := int64(1) 468 tableStartKey := tablecodec.EncodeTablePrefix(tableId) 469 tableEndKey := tablecodec.EncodeTablePrefix(tableId + 1) 470 keys := [][]byte{[]byte(""), tableStartKey} 471 // pre split 2 regions 472 for i := int64(0); i < 2; i++ { 473 keyBytes, err := codec.EncodeKey(stmtCtx, nil, types.NewIntDatum(i)) 474 c.Assert(err, IsNil) 475 h, err := kv.NewCommonHandle(keyBytes) 476 c.Assert(err, IsNil) 477 key := tablecodec.EncodeRowKeyWithHandle(tableId, h) 478 keys = append(keys, key) 479 } 480 keys = append(keys, tableEndKey, []byte("")) 481 client := initTestClient(keys, hook) 482 local := &local{ 483 splitCli: client, 484 } 485 ctx := context.Background() 486 487 // we batch generate a batch of row keys for table 1 with common handle 488 rangeKeys := make([][]byte, 0, 20+1) 489 for i := int64(0); i < 2; i++ { 490 for j := int64(0); j < 10; j++ { 491 keyBytes, err := codec.EncodeKey(stmtCtx, nil, types.NewIntDatum(i), types.NewIntDatum(j*10000)) 492 c.Assert(err, IsNil) 493 h, err := kv.NewCommonHandle(keyBytes) 494 c.Assert(err, IsNil) 495 key := tablecodec.EncodeRowKeyWithHandle(tableId, h) 496 rangeKeys = append(rangeKeys, key) 497 } 498 } 499 500 start := rangeKeys[0] 501 ranges := make([]Range, 0, len(rangeKeys)-1) 502 for _, e := range rangeKeys[1:] { 503 ranges = append(ranges, Range{start: start, end: e}) 504 start = e 505 } 506 507 err := local.SplitAndScatterRegionByRanges(ctx, ranges, true) 508 c.Assert(err, IsNil) 509 510 startKey := codec.EncodeBytes([]byte{}, rangeKeys[0]) 511 endKey := codec.EncodeBytes([]byte{}, rangeKeys[len(rangeKeys)-1]) 512 // check split ranges 513 regions, err := paginateScanRegion(ctx, client, startKey, endKey, 5) 514 c.Assert(err, IsNil) 515 c.Assert(len(regions), Equals, len(ranges)+1) 516 517 checkKeys := append([][]byte{}, rangeKeys[:10]...) 518 checkKeys = append(checkKeys, keys[3]) 519 checkKeys = append(checkKeys, rangeKeys[10:]...) 520 checkRegionRanges(c, regions, checkKeys) 521 } 522 523 func (s *localSuite) TestBatchSplitByRangesWithClusteredIndex(c *C) { 524 s.doTestBatchSplitByRangesWithClusteredIndex(c, nil) 525 } 526 527 func (s *localSuite) TestBatchSplitByRangesWithClusteredIndexEpochNotMatch(c *C) { 528 s.doTestBatchSplitByRangesWithClusteredIndex(c, &splitRegionEpochNotMatchHookRandom{}) 529 }