github.com/pingcap/br@v5.3.0-alpha.0.20220125034240-ec59c7b6ce30+incompatible/pkg/lightning/backend/local/local_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 local 15 16 import ( 17 "bytes" 18 "context" 19 "encoding/binary" 20 "math" 21 "math/rand" 22 "os" 23 "path/filepath" 24 "sort" 25 "sync" 26 "sync/atomic" 27 "testing" 28 29 "github.com/cockroachdb/pebble" 30 "github.com/coreos/go-semver/semver" 31 "github.com/docker/go-units" 32 "github.com/golang/mock/gomock" 33 . "github.com/pingcap/check" 34 "github.com/pingcap/errors" 35 "github.com/pingcap/kvproto/pkg/errorpb" 36 sst "github.com/pingcap/kvproto/pkg/import_sstpb" 37 "github.com/pingcap/kvproto/pkg/metapb" 38 tidbkv "github.com/pingcap/tidb/kv" 39 "github.com/pingcap/tidb/sessionctx/stmtctx" 40 "github.com/pingcap/tidb/tablecodec" 41 "github.com/pingcap/tidb/types" 42 "github.com/pingcap/tidb/util/codec" 43 "github.com/pingcap/tidb/util/hack" 44 45 "github.com/pingcap/br/pkg/lightning/backend" 46 "github.com/pingcap/br/pkg/lightning/backend/kv" 47 "github.com/pingcap/br/pkg/lightning/common" 48 "github.com/pingcap/br/pkg/lightning/mydump" 49 "github.com/pingcap/br/pkg/mock" 50 "github.com/pingcap/br/pkg/restore" 51 ) 52 53 type localSuite struct{} 54 55 var _ = Suite(&localSuite{}) 56 57 func Test(t *testing.T) { 58 TestingT(t) 59 } 60 61 func (s *localSuite) TestNextKey(c *C) { 62 c.Assert(nextKey([]byte{}), DeepEquals, []byte{}) 63 64 cases := [][]byte{ 65 {0}, 66 {255}, 67 {1, 255}, 68 } 69 for _, b := range cases { 70 next := nextKey(b) 71 c.Assert(next, DeepEquals, append(b, 0)) 72 } 73 74 // in the old logic, this should return []byte{} which is not the actually smallest eky 75 next := nextKey([]byte{1, 255}) 76 c.Assert(bytes.Compare(next, []byte{2}), Equals, -1) 77 78 // another test case, nextkey()'s return should be smaller than key with a prefix of the origin key 79 next = nextKey([]byte{1, 255}) 80 c.Assert(bytes.Compare(next, []byte{1, 255, 0, 1, 2}), Equals, -1) 81 82 // test recode key 83 // key with int handle 84 for _, handleID := range []int64{math.MinInt64, 1, 255, math.MaxInt32 - 1} { 85 key := tablecodec.EncodeRowKeyWithHandle(1, tidbkv.IntHandle(handleID)) 86 c.Assert(nextKey(key), DeepEquals, []byte(tablecodec.EncodeRowKeyWithHandle(1, tidbkv.IntHandle(handleID+1)))) 87 } 88 89 // overflowed 90 key := tablecodec.EncodeRowKeyWithHandle(1, tidbkv.IntHandle(math.MaxInt64)) 91 next = tablecodec.EncodeTablePrefix(2) 92 c.Assert([]byte(key), Less, next) 93 c.Assert(nextKey(key), DeepEquals, next) 94 95 testDatums := [][]types.Datum{ 96 {types.NewIntDatum(1), types.NewIntDatum(2)}, 97 {types.NewIntDatum(255), types.NewIntDatum(256)}, 98 {types.NewIntDatum(math.MaxInt32), types.NewIntDatum(math.MaxInt32 + 1)}, 99 {types.NewStringDatum("test"), types.NewStringDatum("test\000")}, 100 {types.NewStringDatum("test\255"), types.NewStringDatum("test\255\000")}, 101 } 102 103 stmtCtx := new(stmtctx.StatementContext) 104 for _, datums := range testDatums { 105 keyBytes, err := codec.EncodeKey(stmtCtx, nil, types.NewIntDatum(123), datums[0]) 106 c.Assert(err, IsNil) 107 h, err := tidbkv.NewCommonHandle(keyBytes) 108 c.Assert(err, IsNil) 109 key := tablecodec.EncodeRowKeyWithHandle(1, h) 110 nextKeyBytes, err := codec.EncodeKey(stmtCtx, nil, types.NewIntDatum(123), datums[1]) 111 c.Assert(err, IsNil) 112 nextHdl, err := tidbkv.NewCommonHandle(nextKeyBytes) 113 c.Assert(err, IsNil) 114 expectNextKey := []byte(tablecodec.EncodeRowKeyWithHandle(1, nextHdl)) 115 c.Assert(nextKey(key), DeepEquals, expectNextKey) 116 } 117 118 // dIAAAAAAAAD/PV9pgAAAAAD/AAABA4AAAAD/AAAAAQOAAAD/AAAAAAEAAAD8 119 // a index key with: table: 61, index: 1, int64: 1, int64: 1 120 a := []byte{116, 128, 0, 0, 0, 0, 0, 0, 255, 61, 95, 105, 128, 0, 0, 0, 0, 255, 0, 0, 1, 3, 128, 0, 0, 0, 255, 0, 0, 0, 1, 3, 128, 0, 0, 255, 0, 0, 0, 0, 1, 0, 0, 0, 252} 121 c.Assert(nextKey(a), DeepEquals, append(a, 0)) 122 } 123 124 // The first half of this test is same as the test in tikv: 125 // https://github.com/tikv/tikv/blob/dbfe7730dd0fddb34cb8c3a7f8a079a1349d2d41/components/engine_rocks/src/properties.rs#L572 126 func (s *localSuite) TestRangeProperties(c *C) { 127 type testCase struct { 128 key []byte 129 vLen int 130 count int 131 } 132 cases := []testCase{ 133 // handle "a": size(size = 1, offset = 1),keys(1,1) 134 {[]byte("a"), 0, 1}, 135 {[]byte("b"), defaultPropSizeIndexDistance / 8, 1}, 136 {[]byte("c"), defaultPropSizeIndexDistance / 4, 1}, 137 {[]byte("d"), defaultPropSizeIndexDistance / 2, 1}, 138 {[]byte("e"), defaultPropSizeIndexDistance / 8, 1}, 139 // handle "e": size(size = DISTANCE + 4, offset = DISTANCE + 5),keys(4,5) 140 {[]byte("f"), defaultPropSizeIndexDistance / 4, 1}, 141 {[]byte("g"), defaultPropSizeIndexDistance / 2, 1}, 142 {[]byte("h"), defaultPropSizeIndexDistance / 8, 1}, 143 {[]byte("i"), defaultPropSizeIndexDistance / 4, 1}, 144 // handle "i": size(size = DISTANCE / 8 * 9 + 4, offset = DISTANCE / 8 * 17 + 9),keys(4,5) 145 {[]byte("j"), defaultPropSizeIndexDistance / 2, 1}, 146 {[]byte("k"), defaultPropSizeIndexDistance / 2, 1}, 147 // handle "k": size(size = DISTANCE + 2, offset = DISTANCE / 8 * 25 + 11),keys(2,11) 148 {[]byte("l"), 0, defaultPropKeysIndexDistance / 2}, 149 {[]byte("m"), 0, defaultPropKeysIndexDistance / 2}, 150 // handle "m": keys = DEFAULT_PROP_KEYS_INDEX_DISTANCE,offset = 11+DEFAULT_PROP_KEYS_INDEX_DISTANCE 151 {[]byte("n"), 1, defaultPropKeysIndexDistance}, 152 // handle "n": keys = DEFAULT_PROP_KEYS_INDEX_DISTANCE, offset = 11+2*DEFAULT_PROP_KEYS_INDEX_DISTANCE 153 {[]byte("o"), 1, 1}, 154 // handle "o": keys = 1, offset = 12 + 2*DEFAULT_PROP_KEYS_INDEX_DISTANCE 155 } 156 157 collector := newRangePropertiesCollector() 158 for _, p := range cases { 159 v := make([]byte, p.vLen) 160 for i := 0; i < p.count; i++ { 161 _ = collector.Add(pebble.InternalKey{UserKey: p.key}, v) 162 } 163 } 164 165 userProperties := make(map[string]string, 1) 166 _ = collector.Finish(userProperties) 167 168 props, err := decodeRangeProperties(hack.Slice(userProperties[propRangeIndex])) 169 c.Assert(err, IsNil) 170 171 // Smallest key in props. 172 c.Assert(props[0].Key, DeepEquals, cases[0].key) 173 // Largest key in props. 174 c.Assert(props[len(props)-1].Key, DeepEquals, cases[len(cases)-1].key) 175 c.Assert(len(props), Equals, 7) 176 177 a := props.get([]byte("a")) 178 c.Assert(a.Size, Equals, uint64(1)) 179 e := props.get([]byte("e")) 180 c.Assert(e.Size, Equals, uint64(defaultPropSizeIndexDistance+5)) 181 i := props.get([]byte("i")) 182 c.Assert(i.Size, Equals, uint64(defaultPropSizeIndexDistance/8*17+9)) 183 k := props.get([]byte("k")) 184 c.Assert(k.Size, Equals, uint64(defaultPropSizeIndexDistance/8*25+11)) 185 m := props.get([]byte("m")) 186 c.Assert(m.Keys, Equals, uint64(defaultPropKeysIndexDistance+11)) 187 n := props.get([]byte("n")) 188 c.Assert(n.Keys, Equals, uint64(defaultPropKeysIndexDistance*2+11)) 189 o := props.get([]byte("o")) 190 c.Assert(o.Keys, Equals, uint64(defaultPropKeysIndexDistance*2+12)) 191 192 props2 := rangeProperties([]rangeProperty{ 193 {[]byte("b"), rangeOffsets{defaultPropSizeIndexDistance + 10, defaultPropKeysIndexDistance / 2}}, 194 {[]byte("h"), rangeOffsets{defaultPropSizeIndexDistance * 3 / 2, defaultPropKeysIndexDistance * 3 / 2}}, 195 {[]byte("k"), rangeOffsets{defaultPropSizeIndexDistance * 3, defaultPropKeysIndexDistance * 7 / 4}}, 196 {[]byte("mm"), rangeOffsets{defaultPropSizeIndexDistance * 5, defaultPropKeysIndexDistance * 2}}, 197 {[]byte("q"), rangeOffsets{defaultPropSizeIndexDistance * 7, defaultPropKeysIndexDistance*9/4 + 10}}, 198 {[]byte("y"), rangeOffsets{defaultPropSizeIndexDistance*7 + 100, defaultPropKeysIndexDistance*9/4 + 1010}}, 199 }) 200 201 sizeProps := newSizeProperties() 202 sizeProps.addAll(props) 203 sizeProps.addAll(props2) 204 205 res := []*rangeProperty{ 206 {[]byte("a"), rangeOffsets{1, 1}}, 207 {[]byte("b"), rangeOffsets{defaultPropSizeIndexDistance + 10, defaultPropKeysIndexDistance / 2}}, 208 {[]byte("e"), rangeOffsets{defaultPropSizeIndexDistance + 4, 4}}, 209 {[]byte("h"), rangeOffsets{defaultPropSizeIndexDistance/2 - 10, defaultPropKeysIndexDistance}}, 210 {[]byte("i"), rangeOffsets{defaultPropSizeIndexDistance*9/8 + 4, 4}}, 211 {[]byte("k"), rangeOffsets{defaultPropSizeIndexDistance*5/2 + 2, defaultPropKeysIndexDistance/4 + 2}}, 212 {[]byte("m"), rangeOffsets{defaultPropKeysIndexDistance, defaultPropKeysIndexDistance}}, 213 {[]byte("mm"), rangeOffsets{defaultPropSizeIndexDistance * 2, defaultPropKeysIndexDistance / 4}}, 214 {[]byte("n"), rangeOffsets{defaultPropKeysIndexDistance * 2, defaultPropKeysIndexDistance}}, 215 {[]byte("o"), rangeOffsets{2, 1}}, 216 {[]byte("q"), rangeOffsets{defaultPropSizeIndexDistance * 2, defaultPropKeysIndexDistance/4 + 10}}, 217 {[]byte("y"), rangeOffsets{100, 1000}}, 218 } 219 220 c.Assert(sizeProps.indexHandles.Len(), Equals, 12) 221 idx := 0 222 sizeProps.iter(func(p *rangeProperty) bool { 223 c.Assert(p, DeepEquals, res[idx]) 224 idx++ 225 return true 226 }) 227 228 fullRange := Range{start: []byte("a"), end: []byte("z")} 229 ranges := splitRangeBySizeProps(fullRange, sizeProps, 2*defaultPropSizeIndexDistance, defaultPropKeysIndexDistance*5/2) 230 231 c.Assert(ranges, DeepEquals, []Range{ 232 {start: []byte("a"), end: []byte("e")}, 233 {start: []byte("e"), end: []byte("k")}, 234 {start: []byte("k"), end: []byte("mm")}, 235 {start: []byte("mm"), end: []byte("q")}, 236 {start: []byte("q"), end: []byte("z")}, 237 }) 238 239 ranges = splitRangeBySizeProps(fullRange, sizeProps, 2*defaultPropSizeIndexDistance, defaultPropKeysIndexDistance) 240 c.Assert(ranges, DeepEquals, []Range{ 241 {start: []byte("a"), end: []byte("e")}, 242 {start: []byte("e"), end: []byte("h")}, 243 {start: []byte("h"), end: []byte("k")}, 244 {start: []byte("k"), end: []byte("m")}, 245 {start: []byte("m"), end: []byte("mm")}, 246 {start: []byte("mm"), end: []byte("n")}, 247 {start: []byte("n"), end: []byte("q")}, 248 {start: []byte("q"), end: []byte("z")}, 249 }) 250 } 251 252 func (s *localSuite) TestRangePropertiesWithPebble(c *C) { 253 dir := c.MkDir() 254 255 sizeDistance := uint64(500) 256 keysDistance := uint64(20) 257 opt := &pebble.Options{ 258 MemTableSize: 512 * units.MiB, 259 MaxConcurrentCompactions: 16, 260 L0CompactionThreshold: math.MaxInt32, // set to max try to disable compaction 261 L0StopWritesThreshold: math.MaxInt32, // set to max try to disable compaction 262 MaxOpenFiles: 10000, 263 DisableWAL: true, 264 ReadOnly: false, 265 TablePropertyCollectors: []func() pebble.TablePropertyCollector{ 266 func() pebble.TablePropertyCollector { 267 return &RangePropertiesCollector{ 268 props: make([]rangeProperty, 0, 1024), 269 propSizeIdxDistance: sizeDistance, 270 propKeysIdxDistance: keysDistance, 271 } 272 }, 273 }, 274 } 275 db, err := pebble.Open(filepath.Join(dir, "test"), opt) 276 c.Assert(err, IsNil) 277 defer db.Close() 278 279 // local collector 280 collector := &RangePropertiesCollector{ 281 props: make([]rangeProperty, 0, 1024), 282 propSizeIdxDistance: sizeDistance, 283 propKeysIdxDistance: keysDistance, 284 } 285 writeOpt := &pebble.WriteOptions{Sync: false} 286 value := make([]byte, 100) 287 for i := 0; i < 10; i++ { 288 wb := db.NewBatch() 289 for j := 0; j < 100; j++ { 290 key := make([]byte, 8) 291 valueLen := rand.Intn(50) 292 binary.BigEndian.PutUint64(key, uint64(i*100+j)) 293 err = wb.Set(key, value[:valueLen], writeOpt) 294 c.Assert(err, IsNil) 295 err = collector.Add(pebble.InternalKey{UserKey: key}, value[:valueLen]) 296 c.Assert(err, IsNil) 297 } 298 c.Assert(wb.Commit(writeOpt), IsNil) 299 } 300 // flush one sst 301 c.Assert(db.Flush(), IsNil) 302 303 props := make(map[string]string, 1) 304 c.Assert(collector.Finish(props), IsNil) 305 306 sstMetas, err := db.SSTables(pebble.WithProperties()) 307 c.Assert(err, IsNil) 308 for i, level := range sstMetas { 309 if i == 0 { 310 c.Assert(len(level), Equals, 1) 311 } else { 312 c.Assert(len(level), Equals, 0) 313 } 314 } 315 316 c.Assert(sstMetas[0][0].Properties.UserProperties, DeepEquals, props) 317 } 318 319 func testLocalWriter(c *C, needSort bool, partitialSort bool) { 320 dir := c.MkDir() 321 opt := &pebble.Options{ 322 MemTableSize: 1024 * 1024, 323 MaxConcurrentCompactions: 16, 324 L0CompactionThreshold: math.MaxInt32, // set to max try to disable compaction 325 L0StopWritesThreshold: math.MaxInt32, // set to max try to disable compaction 326 DisableWAL: true, 327 ReadOnly: false, 328 } 329 db, err := pebble.Open(filepath.Join(dir, "test"), opt) 330 c.Assert(err, IsNil) 331 defer db.Close() 332 tmpPath := filepath.Join(dir, "test.sst") 333 err = os.Mkdir(tmpPath, 0o755) 334 c.Assert(err, IsNil) 335 336 _, engineUUID := backend.MakeUUID("ww", 0) 337 engineCtx, cancel := context.WithCancel(context.Background()) 338 f := &File{ 339 db: db, 340 UUID: engineUUID, 341 sstDir: tmpPath, 342 ctx: engineCtx, 343 cancel: cancel, 344 sstMetasChan: make(chan metaOrFlush, 64), 345 keyAdapter: noopKeyAdapter{}, 346 } 347 f.sstIngester = dbSSTIngester{e: f} 348 f.wg.Add(1) 349 go f.ingestSSTLoop() 350 sorted := needSort && !partitialSort 351 w, err := openLocalWriter(context.Background(), &backend.LocalWriterConfig{IsKVSorted: sorted}, f, 1<<20) 352 c.Assert(err, IsNil) 353 354 ctx := context.Background() 355 var kvs []common.KvPair 356 value := make([]byte, 128) 357 for i := 0; i < 16; i++ { 358 binary.BigEndian.PutUint64(value[i*8:], uint64(i)) 359 } 360 var keys [][]byte 361 for i := 1; i <= 20000; i++ { 362 var kv common.KvPair 363 kv.Key = make([]byte, 16) 364 kv.Val = make([]byte, 128) 365 copy(kv.Val, value) 366 key := rand.Intn(1000) 367 binary.BigEndian.PutUint64(kv.Key, uint64(key)) 368 binary.BigEndian.PutUint64(kv.Key[8:], uint64(i)) 369 kvs = append(kvs, kv) 370 keys = append(keys, kv.Key) 371 } 372 var rows1 []common.KvPair 373 var rows2 []common.KvPair 374 var rows3 []common.KvPair 375 rows4 := kvs[:12000] 376 if partitialSort { 377 sort.Slice(rows4, func(i, j int) bool { 378 return bytes.Compare(rows4[i].Key, rows4[j].Key) < 0 379 }) 380 rows1 = rows4[:6000] 381 rows3 = rows4[6000:] 382 rows2 = kvs[12000:] 383 } else { 384 if needSort { 385 sort.Slice(kvs, func(i, j int) bool { 386 return bytes.Compare(kvs[i].Key, kvs[j].Key) < 0 387 }) 388 } 389 rows1 = kvs[:6000] 390 rows2 = kvs[6000:12000] 391 rows3 = kvs[12000:] 392 } 393 err = w.AppendRows(ctx, "", []string{}, kv.MakeRowsFromKvPairs(rows1)) 394 c.Assert(err, IsNil) 395 err = w.AppendRows(ctx, "", []string{}, kv.MakeRowsFromKvPairs(rows2)) 396 c.Assert(err, IsNil) 397 err = w.AppendRows(ctx, "", []string{}, kv.MakeRowsFromKvPairs(rows3)) 398 c.Assert(err, IsNil) 399 flushStatus, err := w.Close(context.Background()) 400 c.Assert(err, IsNil) 401 c.Assert(f.flushEngineWithoutLock(ctx), IsNil) 402 c.Assert(flushStatus.Flushed(), IsTrue) 403 o := &pebble.IterOptions{} 404 it := db.NewIter(o) 405 406 sort.Slice(keys, func(i, j int) bool { 407 return bytes.Compare(keys[i], keys[j]) < 0 408 }) 409 c.Assert(int(f.Length.Load()), Equals, 20000) 410 c.Assert(int(f.TotalSize.Load()), Equals, 144*20000) 411 valid := it.SeekGE(keys[0]) 412 c.Assert(valid, IsTrue) 413 for _, k := range keys { 414 c.Assert(it.Key(), DeepEquals, k) 415 it.Next() 416 } 417 close(f.sstMetasChan) 418 f.wg.Wait() 419 } 420 421 func (s *localSuite) TestLocalWriterWithSort(c *C) { 422 testLocalWriter(c, false, false) 423 } 424 425 func (s *localSuite) TestLocalWriterWithIngest(c *C) { 426 testLocalWriter(c, true, false) 427 } 428 429 func (s *localSuite) TestLocalWriterWithIngestUnsort(c *C) { 430 testLocalWriter(c, true, true) 431 } 432 433 type mockSplitClient struct { 434 restore.SplitClient 435 } 436 437 func (c *mockSplitClient) GetRegion(ctx context.Context, key []byte) (*restore.RegionInfo, error) { 438 return &restore.RegionInfo{ 439 Leader: &metapb.Peer{Id: 1}, 440 Region: &metapb.Region{ 441 Id: 1, 442 StartKey: key, 443 }, 444 }, nil 445 } 446 447 func (s *localSuite) TestIsIngestRetryable(c *C) { 448 local := &local{ 449 splitCli: &mockSplitClient{}, 450 } 451 452 resp := &sst.IngestResponse{ 453 Error: &errorpb.Error{ 454 NotLeader: &errorpb.NotLeader{ 455 Leader: &metapb.Peer{Id: 2}, 456 }, 457 }, 458 } 459 ctx := context.Background() 460 region := &restore.RegionInfo{ 461 Leader: &metapb.Peer{Id: 1}, 462 Region: &metapb.Region{ 463 Id: 1, 464 StartKey: []byte{1}, 465 EndKey: []byte{3}, 466 RegionEpoch: &metapb.RegionEpoch{ 467 ConfVer: 1, 468 Version: 1, 469 }, 470 }, 471 } 472 metas := []*sst.SSTMeta{ 473 { 474 Range: &sst.Range{ 475 Start: []byte{1}, 476 End: []byte{2}, 477 }, 478 }, 479 { 480 Range: &sst.Range{ 481 Start: []byte{1, 1}, 482 End: []byte{2}, 483 }, 484 }, 485 } 486 retryType, newRegion, err := local.isIngestRetryable(ctx, resp, region, metas) 487 c.Assert(retryType, Equals, retryWrite) 488 c.Assert(newRegion.Leader.Id, Equals, uint64(2)) 489 c.Assert(err, NotNil) 490 491 resp.Error = &errorpb.Error{ 492 EpochNotMatch: &errorpb.EpochNotMatch{ 493 CurrentRegions: []*metapb.Region{ 494 { 495 Id: 1, 496 StartKey: []byte{1}, 497 EndKey: []byte{3}, 498 RegionEpoch: &metapb.RegionEpoch{ 499 ConfVer: 1, 500 Version: 2, 501 }, 502 Peers: []*metapb.Peer{{Id: 1}}, 503 }, 504 }, 505 }, 506 } 507 retryType, newRegion, err = local.isIngestRetryable(ctx, resp, region, metas) 508 c.Assert(retryType, Equals, retryWrite) 509 c.Assert(newRegion.Region.RegionEpoch.Version, Equals, uint64(2)) 510 c.Assert(err, NotNil) 511 512 resp.Error = &errorpb.Error{Message: "raft: proposal dropped"} 513 retryType, _, err = local.isIngestRetryable(ctx, resp, region, metas) 514 c.Assert(retryType, Equals, retryWrite) 515 c.Assert(err, NotNil) 516 517 resp.Error = &errorpb.Error{Message: "unknown error"} 518 retryType, _, err = local.isIngestRetryable(ctx, resp, region, metas) 519 c.Assert(retryType, Equals, retryNone) 520 c.Assert(err, ErrorMatches, "non-retryable error: unknown error") 521 } 522 523 type testIngester struct{} 524 525 func (i testIngester) mergeSSTs(metas []*sstMeta, dir string) (*sstMeta, error) { 526 if len(metas) == 0 { 527 return nil, errors.New("sst metas is empty") 528 } else if len(metas) == 1 { 529 return metas[0], nil 530 } 531 if metas[len(metas)-1].seq-metas[0].seq != int32(len(metas)-1) { 532 panic("metas is not add in order") 533 } 534 535 newMeta := &sstMeta{ 536 seq: metas[len(metas)-1].seq, 537 } 538 for _, m := range metas { 539 newMeta.totalSize += m.totalSize 540 newMeta.totalCount += m.totalCount 541 } 542 return newMeta, nil 543 } 544 545 func (i testIngester) ingest([]*sstMeta) error { 546 return nil 547 } 548 549 func (s *localSuite) TestLocalIngestLoop(c *C) { 550 dir := c.MkDir() 551 opt := &pebble.Options{ 552 MemTableSize: 1024 * 1024, 553 MaxConcurrentCompactions: 16, 554 L0CompactionThreshold: math.MaxInt32, // set to max try to disable compaction 555 L0StopWritesThreshold: math.MaxInt32, // set to max try to disable compaction 556 DisableWAL: true, 557 ReadOnly: false, 558 } 559 db, err := pebble.Open(filepath.Join(dir, "test"), opt) 560 c.Assert(err, IsNil) 561 defer db.Close() 562 tmpPath := filepath.Join(dir, "test.sst") 563 err = os.Mkdir(tmpPath, 0o755) 564 c.Assert(err, IsNil) 565 _, engineUUID := backend.MakeUUID("ww", 0) 566 engineCtx, cancel := context.WithCancel(context.Background()) 567 f := File{ 568 db: db, 569 UUID: engineUUID, 570 sstDir: "", 571 ctx: engineCtx, 572 cancel: cancel, 573 sstMetasChan: make(chan metaOrFlush, 64), 574 config: backend.LocalEngineConfig{ 575 Compact: true, 576 CompactThreshold: 100, 577 CompactConcurrency: 4, 578 }, 579 } 580 f.sstIngester = testIngester{} 581 f.wg.Add(1) 582 go f.ingestSSTLoop() 583 584 // add some routines to add ssts 585 var wg sync.WaitGroup 586 wg.Add(4) 587 totalSize := int64(0) 588 concurrency := 4 589 count := 500 590 var metaSeqLock sync.Mutex 591 maxMetaSeq := int32(0) 592 for i := 0; i < concurrency; i++ { 593 go func() { 594 defer wg.Done() 595 flushCnt := rand.Int31n(10) + 1 596 seq := int32(0) 597 for i := 0; i < count; i++ { 598 size := int64(rand.Int31n(50) + 1) 599 m := &sstMeta{totalSize: size, totalCount: 1} 600 atomic.AddInt64(&totalSize, size) 601 metaSeq, err := f.addSST(engineCtx, m) 602 c.Assert(err, IsNil) 603 if int32(i) >= flushCnt { 604 f.mutex.RLock() 605 err = f.flushEngineWithoutLock(engineCtx) 606 c.Assert(err, IsNil) 607 f.mutex.RUnlock() 608 flushCnt += rand.Int31n(10) + 1 609 } 610 seq = metaSeq 611 } 612 metaSeqLock.Lock() 613 if atomic.LoadInt32(&maxMetaSeq) < seq { 614 atomic.StoreInt32(&maxMetaSeq, seq) 615 } 616 metaSeqLock.Unlock() 617 }() 618 } 619 wg.Wait() 620 621 f.mutex.RLock() 622 err = f.flushEngineWithoutLock(engineCtx) 623 c.Assert(err, IsNil) 624 f.mutex.RUnlock() 625 626 close(f.sstMetasChan) 627 f.wg.Wait() 628 c.Assert(f.ingestErr.Get(), IsNil) 629 c.Assert(totalSize, Equals, f.TotalSize.Load()) 630 c.Assert(f.Length.Load(), Equals, int64(concurrency*count)) 631 c.Assert(f.finishedMetaSeq.Load(), Equals, atomic.LoadInt32(&maxMetaSeq)) 632 } 633 634 func (s *localSuite) TestCheckRequirementsTiFlash(c *C) { 635 controller := gomock.NewController(c) 636 defer controller.Finish() 637 glue := mock.NewMockGlue(controller) 638 exec := mock.NewMockSQLExecutor(controller) 639 ctx := context.Background() 640 641 dbMetas := []*mydump.MDDatabaseMeta{ 642 { 643 Name: "test", 644 Tables: []*mydump.MDTableMeta{ 645 { 646 DB: "test", 647 Name: "t1", 648 DataFiles: []mydump.FileInfo{{}}, 649 }, 650 { 651 DB: "test", 652 Name: "tbl", 653 DataFiles: []mydump.FileInfo{{}}, 654 }, 655 }, 656 }, 657 { 658 Name: "test1", 659 Tables: []*mydump.MDTableMeta{ 660 { 661 DB: "test1", 662 Name: "t", 663 DataFiles: []mydump.FileInfo{{}}, 664 }, 665 { 666 DB: "test1", 667 Name: "tbl", 668 DataFiles: []mydump.FileInfo{{}}, 669 }, 670 }, 671 }, 672 } 673 checkCtx := &backend.CheckCtx{DBMetas: dbMetas} 674 675 glue.EXPECT().GetSQLExecutor().Return(exec) 676 exec.EXPECT().QueryStringsWithLog(ctx, tiFlashReplicaQuery, gomock.Any(), gomock.Any()). 677 Return([][]string{{"db", "tbl"}, {"test", "t1"}, {"test1", "tbl"}}, nil) 678 679 err := checkTiFlashVersion(ctx, glue, checkCtx, *semver.New("4.0.2")) 680 c.Assert(err, ErrorMatches, "lightning local backend doesn't support TiFlash in this TiDB version. conflict tables: \\[`test`.`t1`, `test1`.`tbl`\\].*") 681 } 682 683 func makeRanges(input []string) []Range { 684 ranges := make([]Range, 0, len(input)/2) 685 for i := 0; i < len(input)-1; i += 2 { 686 ranges = append(ranges, Range{start: []byte(input[i]), end: []byte(input[i+1])}) 687 } 688 return ranges 689 } 690 691 func (s *localSuite) TestDedupAndMergeRanges(c *C) { 692 cases := [][]string{ 693 // empty 694 {}, 695 {}, 696 // without overlap 697 {"1", "2", "3", "4", "5", "6", "7", "8"}, 698 {"1", "2", "3", "4", "5", "6", "7", "8"}, 699 // merge all as one 700 {"1", "12", "12", "13", "13", "14", "14", "15", "15", "999"}, 701 {"1", "999"}, 702 // overlap 703 {"1", "12", "12", "13", "121", "129", "122", "133", "14", "15", "15", "999"}, 704 {"1", "133", "14", "999"}, 705 706 // out of order, same as test 3 707 {"15", "999", "1", "12", "121", "129", "12", "13", "122", "133", "14", "15"}, 708 {"1", "133", "14", "999"}, 709 710 // not continuous 711 {"000", "001", "002", "004", "100", "108", "107", "200", "255", "300"}, 712 {"000", "001", "002", "004", "100", "200", "255", "300"}, 713 } 714 715 for i := 0; i < len(cases)-1; i += 2 { 716 input := makeRanges(cases[i]) 717 output := makeRanges(cases[i+1]) 718 719 c.Assert(sortAndMergeRanges(input), DeepEquals, output) 720 } 721 } 722 723 func (s *localSuite) TestFilterOverlapRange(c *C) { 724 cases := [][]string{ 725 // both empty input 726 {}, 727 {}, 728 {}, 729 730 // ranges are empty 731 {}, 732 {"0", "1"}, 733 {}, 734 735 // finished ranges are empty 736 {"0", "1", "2", "3"}, 737 {}, 738 {"0", "1", "2", "3"}, 739 740 // single big finished range 741 {"00", "10", "20", "30", "40", "50", "60", "70"}, 742 {"25", "65"}, 743 {"00", "10", "20", "25", "65", "70"}, 744 745 // single big input 746 {"10", "99"}, 747 {"00", "10", "15", "30", "45", "60"}, 748 {"10", "15", "30", "45", "60", "99"}, 749 750 // multi input and finished 751 {"00", "05", "05", "10", "10", "20", "30", "45", "50", "70", "70", "90"}, 752 {"07", "12", "14", "16", "17", "30", "45", "70"}, 753 {"00", "05", "05", "07", "12", "14", "16", "17", "30", "45", "70", "90"}, 754 } 755 756 for i := 0; i < len(cases)-2; i += 3 { 757 input := makeRanges(cases[i]) 758 finished := makeRanges(cases[i+1]) 759 output := makeRanges(cases[i+2]) 760 761 c.Assert(filterOverlapRange(input, finished), DeepEquals, output) 762 } 763 }