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  }