github.com/pingcap/tidb-lightning@v5.0.0-rc.0.20210428090220-84b649866577+incompatible/lightning/backend/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 backend 15 16 import ( 17 "bytes" 18 "context" 19 "encoding/binary" 20 "math" 21 "math/rand" 22 "os" 23 "path/filepath" 24 "sort" 25 26 "github.com/cockroachdb/pebble" 27 "github.com/pingcap/br/pkg/restore" 28 . "github.com/pingcap/check" 29 "github.com/pingcap/kvproto/pkg/errorpb" 30 sst "github.com/pingcap/kvproto/pkg/import_sstpb" 31 "github.com/pingcap/kvproto/pkg/metapb" 32 "github.com/pingcap/tidb/kv" 33 "github.com/pingcap/tidb/sessionctx/stmtctx" 34 "github.com/pingcap/tidb/tablecodec" 35 "github.com/pingcap/tidb/types" 36 "github.com/pingcap/tidb/util/codec" 37 "github.com/pingcap/tidb/util/hack" 38 39 "github.com/pingcap/tidb-lightning/lightning/common" 40 ) 41 42 type localSuite struct{} 43 44 var _ = Suite(&localSuite{}) 45 46 func (s *localSuite) TestNextKey(c *C) { 47 c.Assert(nextKey([]byte{}), DeepEquals, []byte{}) 48 49 cases := [][]byte{ 50 {0}, 51 {255}, 52 {1, 255}, 53 } 54 for _, b := range cases { 55 next := nextKey(b) 56 c.Assert(next, DeepEquals, append(b, 0)) 57 } 58 59 // in the old logic, this should return []byte{} which is not the actually smallest eky 60 next := nextKey([]byte{1, 255}) 61 c.Assert(bytes.Compare(next, []byte{2}), Equals, -1) 62 63 // another test case, nextkey()'s return should be smaller than key with a prefix of the origin key 64 next = nextKey([]byte{1, 255}) 65 c.Assert(bytes.Compare(next, []byte{1, 255, 0, 1, 2}), Equals, -1) 66 67 // test recode key 68 // key with int handle 69 for _, handleId := range []int64{1, 255, math.MaxInt32} { 70 key := tablecodec.EncodeRowKeyWithHandle(1, kv.IntHandle(handleId)) 71 c.Assert(nextKey(key), DeepEquals, []byte(tablecodec.EncodeRowKeyWithHandle(1, kv.IntHandle(handleId+1)))) 72 } 73 74 testDatums := [][]types.Datum{ 75 {types.NewIntDatum(1), types.NewIntDatum(2)}, 76 {types.NewIntDatum(255), types.NewIntDatum(256)}, 77 {types.NewIntDatum(math.MaxInt32), types.NewIntDatum(math.MaxInt32 + 1)}, 78 {types.NewStringDatum("test"), types.NewStringDatum("test\000")}, 79 {types.NewStringDatum("test\255"), types.NewStringDatum("test\255\000")}, 80 } 81 82 stmtCtx := new(stmtctx.StatementContext) 83 for _, datums := range testDatums { 84 keyBytes, err := codec.EncodeKey(stmtCtx, nil, types.NewIntDatum(123), datums[0]) 85 c.Assert(err, IsNil) 86 h, err := kv.NewCommonHandle(keyBytes) 87 c.Assert(err, IsNil) 88 key := tablecodec.EncodeRowKeyWithHandle(1, h) 89 nextKeyBytes, err := codec.EncodeKey(stmtCtx, nil, types.NewIntDatum(123), datums[1]) 90 c.Assert(err, IsNil) 91 nextHdl, err := kv.NewCommonHandle(nextKeyBytes) 92 c.Assert(err, IsNil) 93 expectNextKey := []byte(tablecodec.EncodeRowKeyWithHandle(1, nextHdl)) 94 c.Assert(nextKey(key), DeepEquals, expectNextKey) 95 } 96 97 // dIAAAAAAAAD/PV9pgAAAAAD/AAABA4AAAAD/AAAAAQOAAAD/AAAAAAEAAAD8 98 // a index key with: table: 61, index: 1, int64: 1, int64: 1 99 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} 100 c.Assert(nextKey(a), DeepEquals, append(a, 0)) 101 } 102 103 // The first half of this test is same as the test in tikv: 104 // https://github.com/tikv/tikv/blob/dbfe7730dd0fddb34cb8c3a7f8a079a1349d2d41/components/engine_rocks/src/properties.rs#L572 105 func (s *localSuite) TestRangeProperties(c *C) { 106 type testCase struct { 107 key []byte 108 vLen int 109 count int 110 } 111 cases := []testCase{ 112 // handle "a": size(size = 1, offset = 1),keys(1,1) 113 {[]byte("a"), 0, 1}, 114 {[]byte("b"), defaultPropSizeIndexDistance / 8, 1}, 115 {[]byte("c"), defaultPropSizeIndexDistance / 4, 1}, 116 {[]byte("d"), defaultPropSizeIndexDistance / 2, 1}, 117 {[]byte("e"), defaultPropSizeIndexDistance / 8, 1}, 118 // handle "e": size(size = DISTANCE + 4, offset = DISTANCE + 5),keys(4,5) 119 {[]byte("f"), defaultPropSizeIndexDistance / 4, 1}, 120 {[]byte("g"), defaultPropSizeIndexDistance / 2, 1}, 121 {[]byte("h"), defaultPropSizeIndexDistance / 8, 1}, 122 {[]byte("i"), defaultPropSizeIndexDistance / 4, 1}, 123 // handle "i": size(size = DISTANCE / 8 * 9 + 4, offset = DISTANCE / 8 * 17 + 9),keys(4,5) 124 {[]byte("j"), defaultPropSizeIndexDistance / 2, 1}, 125 {[]byte("k"), defaultPropSizeIndexDistance / 2, 1}, 126 // handle "k": size(size = DISTANCE + 2, offset = DISTANCE / 8 * 25 + 11),keys(2,11) 127 {[]byte("l"), 0, defaultPropKeysIndexDistance / 2}, 128 {[]byte("m"), 0, defaultPropKeysIndexDistance / 2}, 129 //handle "m": keys = DEFAULT_PROP_KEYS_INDEX_DISTANCE,offset = 11+DEFAULT_PROP_KEYS_INDEX_DISTANCE 130 {[]byte("n"), 1, defaultPropKeysIndexDistance}, 131 //handle "n": keys = DEFAULT_PROP_KEYS_INDEX_DISTANCE, offset = 11+2*DEFAULT_PROP_KEYS_INDEX_DISTANCE 132 {[]byte("o"), 1, 1}, 133 // handle "o": keys = 1, offset = 12 + 2*DEFAULT_PROP_KEYS_INDEX_DISTANCE 134 } 135 136 collector := newRangePropertiesCollector() 137 for _, p := range cases { 138 v := make([]byte, p.vLen) 139 for i := 0; i < p.count; i++ { 140 _ = collector.Add(pebble.InternalKey{UserKey: p.key}, v) 141 } 142 } 143 144 userProperties := make(map[string]string, 1) 145 _ = collector.Finish(userProperties) 146 147 props, err := decodeRangeProperties(hack.Slice(userProperties[propRangeIndex])) 148 c.Assert(err, IsNil) 149 150 // Smallest key in props. 151 c.Assert(props[0].Key, DeepEquals, cases[0].key) 152 // Largest key in props. 153 c.Assert(props[len(props)-1].Key, DeepEquals, cases[len(cases)-1].key) 154 c.Assert(len(props), Equals, 7) 155 156 a := props.get([]byte("a")) 157 c.Assert(a.Size, Equals, uint64(1)) 158 e := props.get([]byte("e")) 159 c.Assert(e.Size, Equals, uint64(defaultPropSizeIndexDistance+5)) 160 i := props.get([]byte("i")) 161 c.Assert(i.Size, Equals, uint64(defaultPropSizeIndexDistance/8*17+9)) 162 k := props.get([]byte("k")) 163 c.Assert(k.Size, Equals, uint64(defaultPropSizeIndexDistance/8*25+11)) 164 m := props.get([]byte("m")) 165 c.Assert(m.Keys, Equals, uint64(defaultPropKeysIndexDistance+11)) 166 n := props.get([]byte("n")) 167 c.Assert(n.Keys, Equals, uint64(defaultPropKeysIndexDistance*2+11)) 168 o := props.get([]byte("o")) 169 c.Assert(o.Keys, Equals, uint64(defaultPropKeysIndexDistance*2+12)) 170 171 props2 := rangeProperties([]rangeProperty{ 172 {[]byte("b"), rangeOffsets{defaultPropSizeIndexDistance + 10, defaultPropKeysIndexDistance / 2}}, 173 {[]byte("h"), rangeOffsets{defaultPropSizeIndexDistance * 3 / 2, defaultPropKeysIndexDistance * 3 / 2}}, 174 {[]byte("k"), rangeOffsets{defaultPropSizeIndexDistance * 3, defaultPropKeysIndexDistance * 7 / 4}}, 175 {[]byte("mm"), rangeOffsets{defaultPropSizeIndexDistance * 5, defaultPropKeysIndexDistance * 2}}, 176 {[]byte("q"), rangeOffsets{defaultPropSizeIndexDistance * 7, defaultPropKeysIndexDistance*9/4 + 10}}, 177 {[]byte("y"), rangeOffsets{defaultPropSizeIndexDistance*7 + 100, defaultPropKeysIndexDistance*9/4 + 1010}}, 178 }) 179 180 sizeProps := newSizeProperties() 181 sizeProps.addAll(props) 182 sizeProps.addAll(props2) 183 184 res := []*rangeProperty{ 185 {[]byte("a"), rangeOffsets{1, 1}}, 186 {[]byte("b"), rangeOffsets{defaultPropSizeIndexDistance + 10, defaultPropKeysIndexDistance / 2}}, 187 {[]byte("e"), rangeOffsets{defaultPropSizeIndexDistance + 4, 4}}, 188 {[]byte("h"), rangeOffsets{defaultPropSizeIndexDistance/2 - 10, defaultPropKeysIndexDistance}}, 189 {[]byte("i"), rangeOffsets{defaultPropSizeIndexDistance*9/8 + 4, 4}}, 190 {[]byte("k"), rangeOffsets{defaultPropSizeIndexDistance*5/2 + 2, defaultPropKeysIndexDistance/4 + 2}}, 191 {[]byte("m"), rangeOffsets{defaultPropKeysIndexDistance, defaultPropKeysIndexDistance}}, 192 {[]byte("mm"), rangeOffsets{defaultPropSizeIndexDistance * 2, defaultPropKeysIndexDistance / 4}}, 193 {[]byte("n"), rangeOffsets{defaultPropKeysIndexDistance * 2, defaultPropKeysIndexDistance}}, 194 {[]byte("o"), rangeOffsets{2, 1}}, 195 {[]byte("q"), rangeOffsets{defaultPropSizeIndexDistance * 2, defaultPropKeysIndexDistance/4 + 10}}, 196 {[]byte("y"), rangeOffsets{100, 1000}}, 197 } 198 199 c.Assert(sizeProps.indexHandles.Len(), Equals, 12) 200 idx := 0 201 sizeProps.iter(func(p *rangeProperty) bool { 202 c.Assert(p, DeepEquals, res[idx]) 203 idx++ 204 return true 205 }) 206 207 fullRange := Range{start: []byte("a"), end: []byte("z")} 208 ranges := splitRangeBySizeProps(fullRange, sizeProps, 2*defaultPropSizeIndexDistance, defaultPropKeysIndexDistance*5/2) 209 210 c.Assert(ranges, DeepEquals, []Range{ 211 {start: []byte("a"), end: []byte("e")}, 212 {start: []byte("e"), end: []byte("k")}, 213 {start: []byte("k"), end: []byte("mm")}, 214 {start: []byte("mm"), end: []byte("q")}, 215 {start: []byte("q"), end: []byte("z")}, 216 }) 217 218 ranges = splitRangeBySizeProps(fullRange, sizeProps, 2*defaultPropSizeIndexDistance, defaultPropKeysIndexDistance) 219 c.Assert(ranges, DeepEquals, []Range{ 220 {start: []byte("a"), end: []byte("e")}, 221 {start: []byte("e"), end: []byte("h")}, 222 {start: []byte("h"), end: []byte("k")}, 223 {start: []byte("k"), end: []byte("m")}, 224 {start: []byte("m"), end: []byte("mm")}, 225 {start: []byte("mm"), end: []byte("n")}, 226 {start: []byte("n"), end: []byte("q")}, 227 {start: []byte("q"), end: []byte("z")}, 228 }) 229 } 230 231 func (s *localSuite) TestRangePropertiesWithPebble(c *C) { 232 dir := c.MkDir() 233 234 sizeDistance := uint64(500) 235 keysDistance := uint64(20) 236 opt := &pebble.Options{ 237 MemTableSize: LocalMemoryTableSize, 238 MaxConcurrentCompactions: 16, 239 L0CompactionThreshold: math.MaxInt32, // set to max try to disable compaction 240 L0StopWritesThreshold: math.MaxInt32, // set to max try to disable compaction 241 MaxOpenFiles: 10000, 242 DisableWAL: true, 243 ReadOnly: false, 244 TablePropertyCollectors: []func() pebble.TablePropertyCollector{ 245 func() pebble.TablePropertyCollector { 246 return &RangePropertiesCollector{ 247 props: make([]rangeProperty, 0, 1024), 248 propSizeIdxDistance: sizeDistance, 249 propKeysIdxDistance: keysDistance, 250 } 251 }, 252 }, 253 } 254 db, err := pebble.Open(filepath.Join(dir, "test"), opt) 255 c.Assert(err, IsNil) 256 defer db.Close() 257 258 // local collector 259 collector := &RangePropertiesCollector{ 260 props: make([]rangeProperty, 0, 1024), 261 propSizeIdxDistance: sizeDistance, 262 propKeysIdxDistance: keysDistance, 263 } 264 writeOpt := &pebble.WriteOptions{Sync: false} 265 value := make([]byte, 100) 266 for i := 0; i < 10; i++ { 267 wb := db.NewBatch() 268 for j := 0; j < 100; j++ { 269 key := make([]byte, 8) 270 valueLen := rand.Intn(50) 271 binary.BigEndian.PutUint64(key, uint64(i*100+j)) 272 err = wb.Set(key, value[:valueLen], writeOpt) 273 c.Assert(err, IsNil) 274 err = collector.Add(pebble.InternalKey{UserKey: key}, value[:valueLen]) 275 c.Assert(err, IsNil) 276 } 277 c.Assert(wb.Commit(writeOpt), IsNil) 278 } 279 // flush one sst 280 c.Assert(db.Flush(), IsNil) 281 282 props := make(map[string]string, 1) 283 c.Assert(collector.Finish(props), IsNil) 284 285 sstMetas, err := db.SSTables(pebble.WithProperties()) 286 c.Assert(err, IsNil) 287 for i, level := range sstMetas { 288 if i == 0 { 289 c.Assert(len(level), Equals, 1) 290 } else { 291 c.Assert(len(level), Equals, 0) 292 } 293 } 294 295 c.Assert(sstMetas[0][0].Properties.UserProperties, DeepEquals, props) 296 } 297 298 func testLocalWriter(c *C, needSort bool, partitialSort bool) { 299 dir := c.MkDir() 300 opt := &pebble.Options{ 301 MemTableSize: 1024 * 1024, 302 MaxConcurrentCompactions: 16, 303 L0CompactionThreshold: math.MaxInt32, // set to max try to disable compaction 304 L0StopWritesThreshold: math.MaxInt32, // set to max try to disable compaction 305 DisableWAL: true, 306 ReadOnly: false, 307 } 308 db, err := pebble.Open(filepath.Join(dir, "test"), opt) 309 c.Assert(err, IsNil) 310 tmpPath := filepath.Join(dir, "tmp") 311 err = os.Mkdir(tmpPath, 0755) 312 c.Assert(err, IsNil) 313 meta := localFileMeta{} 314 _, engineUUID := MakeUUID("ww", 0) 315 f := LocalFile{localFileMeta: meta, db: db, Uuid: engineUUID} 316 w := openLocalWriter(&f, tmpPath, 1024*1024) 317 318 ctx := context.Background() 319 //kvs := make(kvPairs, 1000) 320 var kvs kvPairs 321 value := make([]byte, 128) 322 for i := 0; i < 16; i++ { 323 binary.BigEndian.PutUint64(value[i*8:], uint64(i)) 324 } 325 var keys [][]byte 326 for i := 1; i <= 20000; i++ { 327 var kv common.KvPair 328 kv.Key = make([]byte, 16) 329 kv.Val = make([]byte, 128) 330 copy(kv.Val, value) 331 key := rand.Intn(1000) 332 binary.BigEndian.PutUint64(kv.Key, uint64(key)) 333 binary.BigEndian.PutUint64(kv.Key[8:], uint64(i)) 334 kvs = append(kvs, kv) 335 keys = append(keys, kv.Key) 336 } 337 var rows1 kvPairs 338 var rows2 kvPairs 339 var rows3 kvPairs 340 rows4 := kvs[:12000] 341 if partitialSort { 342 sort.Slice(rows4, func(i, j int) bool { 343 return bytes.Compare(rows4[i].Key, rows4[j].Key) < 0 344 }) 345 rows1 = rows4[:6000] 346 rows3 = rows4[6000:] 347 rows2 = kvs[12000:] 348 } else { 349 if needSort { 350 sort.Slice(kvs, func(i, j int) bool { 351 return bytes.Compare(kvs[i].Key, kvs[j].Key) < 0 352 }) 353 } 354 rows1 = kvs[:6000] 355 rows2 = kvs[6000:12000] 356 rows3 = kvs[12000:] 357 } 358 err = w.AppendRows(ctx, "", []string{}, 1, rows1) 359 c.Assert(err, IsNil) 360 err = w.AppendRows(ctx, "", []string{}, 1, rows2) 361 c.Assert(err, IsNil) 362 err = w.AppendRows(ctx, "", []string{}, 1, rows3) 363 c.Assert(err, IsNil) 364 err = w.Close() 365 c.Assert(err, IsNil) 366 err = db.Flush() 367 c.Assert(err, IsNil) 368 o := &pebble.IterOptions{} 369 it := db.NewIter(o) 370 371 sort.Slice(keys, func(i, j int) bool { 372 return bytes.Compare(keys[i], keys[j]) < 0 373 }) 374 c.Assert(int(f.Length.Load()), Equals, 20000) 375 c.Assert(int(f.TotalSize.Load()), Equals, 144*20000) 376 valid := it.SeekGE(keys[0]) 377 c.Assert(valid, IsTrue) 378 for _, k := range keys { 379 c.Assert(it.Key(), DeepEquals, k) 380 it.Next() 381 } 382 } 383 384 func (s *localSuite) TestLocalWriterWithSort(c *C) { 385 testLocalWriter(c, false, false) 386 } 387 388 func (s *localSuite) TestLocalWriterWithIngest(c *C) { 389 testLocalWriter(c, true, false) 390 } 391 392 func (s *localSuite) TestLocalWriterWithIngestUnsort(c *C) { 393 testLocalWriter(c, true, true) 394 } 395 396 type mockSplitClient struct { 397 restore.SplitClient 398 } 399 400 func (c *mockSplitClient) GetRegion(ctx context.Context, key []byte) (*restore.RegionInfo, error) { 401 return &restore.RegionInfo{ 402 Leader: &metapb.Peer{Id: 1}, 403 Region: &metapb.Region{ 404 Id: 1, 405 StartKey: key, 406 }, 407 }, nil 408 } 409 410 func (s *localSuite) TestIsIngestRetryable(c *C) { 411 local := &local{ 412 splitCli: &mockSplitClient{}, 413 } 414 415 resp := &sst.IngestResponse{ 416 Error: &errorpb.Error{ 417 NotLeader: &errorpb.NotLeader{ 418 Leader: &metapb.Peer{Id: 2}, 419 }, 420 }, 421 } 422 ctx := context.Background() 423 region := &restore.RegionInfo{ 424 Leader: &metapb.Peer{Id: 1}, 425 Region: &metapb.Region{ 426 Id: 1, 427 StartKey: []byte{1}, 428 EndKey: []byte{3}, 429 RegionEpoch: &metapb.RegionEpoch{ 430 ConfVer: 1, 431 Version: 1, 432 }, 433 }, 434 } 435 meta := &sst.SSTMeta{ 436 Range: &sst.Range{ 437 Start: []byte{1}, 438 End: []byte{2}, 439 }, 440 } 441 retryType, newRegion, err := local.isIngestRetryable(ctx, resp, region, meta) 442 c.Assert(retryType, Equals, retryWrite) 443 c.Assert(newRegion.Leader.Id, Equals, uint64(2)) 444 c.Assert(err, NotNil) 445 446 resp.Error = &errorpb.Error{ 447 EpochNotMatch: &errorpb.EpochNotMatch{ 448 CurrentRegions: []*metapb.Region{ 449 { 450 Id: 1, 451 StartKey: []byte{1}, 452 EndKey: []byte{3}, 453 RegionEpoch: &metapb.RegionEpoch{ 454 ConfVer: 1, 455 Version: 2, 456 }, 457 Peers: []*metapb.Peer{{Id: 1}}, 458 }, 459 }, 460 }, 461 } 462 retryType, newRegion, err = local.isIngestRetryable(ctx, resp, region, meta) 463 c.Assert(retryType, Equals, retryWrite) 464 c.Assert(newRegion.Region.RegionEpoch.Version, Equals, uint64(2)) 465 c.Assert(err, NotNil) 466 467 resp.Error = &errorpb.Error{Message: "raft: proposal dropped"} 468 retryType, newRegion, err = local.isIngestRetryable(ctx, resp, region, meta) 469 c.Assert(retryType, Equals, retryWrite) 470 471 resp.Error = &errorpb.Error{Message: "unknown error"} 472 retryType, newRegion, err = local.isIngestRetryable(ctx, resp, region, meta) 473 c.Assert(retryType, Equals, retryNone) 474 c.Assert(err, ErrorMatches, "non-retryable error: unknown error") 475 }