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  }