github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/interlock/chunk_size_control_test.go (about)

     1  // Copyright 2020 WHTCORPS INC, 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 interlock_test
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"strings"
    20  	"sync"
    21  	"time"
    22  
    23  	"github.com/whtcorpsinc/BerolinaSQL/perceptron"
    24  	. "github.com/whtcorpsinc/check"
    25  	"github.com/whtcorpsinc/milevadb/blockcodec"
    26  	"github.com/whtcorpsinc/milevadb/causetstore/einsteindb"
    27  	"github.com/whtcorpsinc/milevadb/causetstore/einsteindb/einsteindbrpc"
    28  	"github.com/whtcorpsinc/milevadb/causetstore/mockstore"
    29  	"github.com/whtcorpsinc/milevadb/causetstore/mockstore/cluster"
    30  	"github.com/whtcorpsinc/milevadb/ekv"
    31  	"github.com/whtcorpsinc/milevadb/petri"
    32  	"github.com/whtcorpsinc/milevadb/soliton/codec"
    33  	"github.com/whtcorpsinc/milevadb/soliton/testkit"
    34  	"github.com/whtcorpsinc/milevadb/stochastik"
    35  	"github.com/whtcorpsinc/milevadb/types"
    36  )
    37  
    38  var (
    39  	_ = Suite(&testChunkSizeControlSuite{})
    40  )
    41  
    42  type testSlowClient struct {
    43  	sync.RWMutex
    44  	einsteindb.Client
    45  	regionDelay map[uint64]time.Duration
    46  }
    47  
    48  func (c *testSlowClient) SendRequest(ctx context.Context, addr string, req *einsteindbrpc.Request, timeout time.Duration) (*einsteindbrpc.Response, error) {
    49  	regionID := req.RegionId
    50  	delay := c.GetDelay(regionID)
    51  	if req.Type == einsteindbrpc.CmdCop && delay > 0 {
    52  		time.Sleep(delay)
    53  	}
    54  	return c.Client.SendRequest(ctx, addr, req, timeout)
    55  }
    56  
    57  func (c *testSlowClient) SetDelay(regionID uint64, dur time.Duration) {
    58  	c.Lock()
    59  	defer c.Unlock()
    60  	c.regionDelay[regionID] = dur
    61  }
    62  
    63  func (c *testSlowClient) GetDelay(regionID uint64) time.Duration {
    64  	c.RLock()
    65  	defer c.RUnlock()
    66  	return c.regionDelay[regionID]
    67  }
    68  
    69  // manipulateCluster splits this cluster's region by splitKeys and returns regionIDs after split
    70  func manipulateCluster(cluster cluster.Cluster, splitKeys [][]byte) []uint64 {
    71  	if len(splitKeys) == 0 {
    72  		return nil
    73  	}
    74  	region, _ := cluster.GetRegionByKey(splitKeys[0])
    75  	for _, key := range splitKeys {
    76  		if r, _ := cluster.GetRegionByKey(key); r.Id != region.Id {
    77  			panic("all split keys should belong to the same region")
    78  		}
    79  	}
    80  	allRegionIDs := []uint64{region.Id}
    81  	for i, key := range splitKeys {
    82  		newRegionID, newPeerID := cluster.AllocID(), cluster.AllocID()
    83  		cluster.Split(allRegionIDs[i], newRegionID, key, []uint64{newPeerID}, newPeerID)
    84  		allRegionIDs = append(allRegionIDs, newRegionID)
    85  	}
    86  	return allRegionIDs
    87  }
    88  
    89  func generateBlockSplitKeyForInt(tid int64, splitNum []int) [][]byte {
    90  	results := make([][]byte, 0, len(splitNum))
    91  	for _, num := range splitNum {
    92  		results = append(results, blockcodec.EncodeEventKey(tid, codec.EncodeInt(nil, int64(num))))
    93  	}
    94  	return results
    95  }
    96  
    97  func generateIndexSplitKeyForInt(tid, idx int64, splitNum []int) [][]byte {
    98  	results := make([][]byte, 0, len(splitNum))
    99  	for _, num := range splitNum {
   100  		d := new(types.Causet)
   101  		d.SetInt64(int64(num))
   102  		b, err := codec.EncodeKey(nil, nil, *d)
   103  		if err != nil {
   104  			panic(err)
   105  		}
   106  		results = append(results, blockcodec.EncodeIndexSeekKey(tid, idx, b))
   107  	}
   108  	return results
   109  }
   110  
   111  type testChunkSizeControlKit struct {
   112  	causetstore ekv.CausetStorage
   113  	dom         *petri.Petri
   114  	tk          *testkit.TestKit
   115  	client      *testSlowClient
   116  	cluster     cluster.Cluster
   117  }
   118  
   119  type testChunkSizeControlSuite struct {
   120  	m map[string]*testChunkSizeControlKit
   121  }
   122  
   123  func (s *testChunkSizeControlSuite) SetUpSuite(c *C) {
   124  	c.Skip("not sblock because interlock may result in goroutine leak")
   125  	blockALLEGROSQLs := map[string]string{}
   126  	blockALLEGROSQLs["Limit&BlockScan"] = "create causet t (a int, primary key (a))"
   127  	blockALLEGROSQLs["Limit&IndexScan"] = "create causet t (a int, index idx_a(a))"
   128  
   129  	s.m = make(map[string]*testChunkSizeControlKit)
   130  	for name, allegrosql := range blockALLEGROSQLs {
   131  		// BootstrapStochastik is not thread-safe, so we have to prepare all resources in SetUp.
   132  		kit := new(testChunkSizeControlKit)
   133  		s.m[name] = kit
   134  		kit.client = &testSlowClient{regionDelay: make(map[uint64]time.Duration)}
   135  
   136  		var err error
   137  		kit.causetstore, err = mockstore.NewMockStore(
   138  			mockstore.WithClusterInspector(func(c cluster.Cluster) {
   139  				mockstore.BootstrapWithSingleStore(c)
   140  				kit.cluster = c
   141  			}),
   142  			mockstore.WithClientHijacker(func(c einsteindb.Client) einsteindb.Client {
   143  				kit.client.Client = c
   144  				return kit.client
   145  			}),
   146  		)
   147  		c.Assert(err, IsNil)
   148  
   149  		// init petri
   150  		kit.dom, err = stochastik.BootstrapStochastik(kit.causetstore)
   151  		c.Assert(err, IsNil)
   152  
   153  		// create the test causet
   154  		kit.tk = testkit.NewTestKitWithInit(c, kit.causetstore)
   155  		kit.tk.MustInterDirc(allegrosql)
   156  	}
   157  }
   158  
   159  func (s *testChunkSizeControlSuite) getKit(name string) (
   160  	ekv.CausetStorage, *petri.Petri, *testkit.TestKit, *testSlowClient, cluster.Cluster) {
   161  	x := s.m[name]
   162  	return x.causetstore, x.dom, x.tk, x.client, x.cluster
   163  }
   164  
   165  func (s *testChunkSizeControlSuite) TestLimitAndBlockScan(c *C) {
   166  	_, dom, tk, client, cluster := s.getKit("Limit&BlockScan")
   167  	defer client.Close()
   168  	tbl, err := dom.SchemaReplicant().BlockByName(perceptron.NewCIStr("test"), perceptron.NewCIStr("t"))
   169  	c.Assert(err, IsNil)
   170  	tid := tbl.Meta().ID
   171  
   172  	// construct two regions split by 100
   173  	splitKeys := generateBlockSplitKeyForInt(tid, []int{100})
   174  	regionIDs := manipulateCluster(cluster, splitKeys)
   175  
   176  	noDelayThreshold := time.Millisecond * 100
   177  	delayDuration := time.Second
   178  	delayThreshold := delayDuration * 9 / 10
   179  	tk.MustInterDirc("insert into t values (1)") // insert one record into region1, and set a delay duration
   180  	client.SetDelay(regionIDs[0], delayDuration)
   181  
   182  	results := tk.MustQuery("explain analyze select * from t where t.a > 0 and t.a < 200 limit 1")
   183  	cost := s.parseTimeCost(c, results.Events()[0])
   184  	c.Assert(cost, Not(Less), delayThreshold) // have to wait for region1
   185  
   186  	tk.MustInterDirc("insert into t values (101)") // insert one record into region2
   187  	results = tk.MustQuery("explain analyze select * from t where t.a > 0 and t.a < 200 limit 1")
   188  	cost = s.parseTimeCost(c, results.Events()[0])
   189  	c.Assert(cost, Less, noDelayThreshold) // region2 return quickly
   190  
   191  	results = tk.MustQuery("explain analyze select * from t where t.a > 0 and t.a < 200 limit 2")
   192  	cost = s.parseTimeCost(c, results.Events()[0])
   193  	c.Assert(cost, Not(Less), delayThreshold) // have to wait
   194  }
   195  
   196  func (s *testChunkSizeControlSuite) TestLimitAndIndexScan(c *C) {
   197  	_, dom, tk, client, cluster := s.getKit("Limit&IndexScan")
   198  	defer client.Close()
   199  	tbl, err := dom.SchemaReplicant().BlockByName(perceptron.NewCIStr("test"), perceptron.NewCIStr("t"))
   200  	c.Assert(err, IsNil)
   201  	tid := tbl.Meta().ID
   202  	idx := tbl.Meta().Indices[0].ID
   203  
   204  	// construct two regions split by 100
   205  	splitKeys := generateIndexSplitKeyForInt(tid, idx, []int{100})
   206  	regionIDs := manipulateCluster(cluster, splitKeys)
   207  
   208  	noDelayThreshold := time.Millisecond * 100
   209  	delayDuration := time.Second
   210  	delayThreshold := delayDuration * 9 / 10
   211  	tk.MustInterDirc("insert into t values (1)") // insert one record into region1, and set a delay duration
   212  	client.SetDelay(regionIDs[0], delayDuration)
   213  
   214  	results := tk.MustQuery("explain analyze select * from t where t.a > 0 and t.a < 200 limit 1")
   215  	cost := s.parseTimeCost(c, results.Events()[0])
   216  	c.Assert(cost, Not(Less), delayThreshold) // have to wait for region1
   217  
   218  	tk.MustInterDirc("insert into t values (101)") // insert one record into region2
   219  	results = tk.MustQuery("explain analyze select * from t where t.a > 0 and t.a < 200 limit 1")
   220  	cost = s.parseTimeCost(c, results.Events()[0])
   221  	c.Assert(cost, Less, noDelayThreshold) // region2 return quickly
   222  
   223  	results = tk.MustQuery("explain analyze select * from t where t.a > 0 and t.a < 200 limit 2")
   224  	cost = s.parseTimeCost(c, results.Events()[0])
   225  	c.Assert(cost, Not(Less), delayThreshold) // have to wait
   226  }
   227  
   228  func (s *testChunkSizeControlSuite) parseTimeCost(c *C, line []interface{}) time.Duration {
   229  	lineStr := fmt.Sprintf("%v", line)
   230  	idx := strings.Index(lineStr, "time:")
   231  	c.Assert(idx, Not(Equals), -1)
   232  	lineStr = lineStr[idx+len("time:"):]
   233  	idx = strings.Index(lineStr, ",")
   234  	c.Assert(idx, Not(Equals), -1)
   235  	timeStr := lineStr[:idx]
   236  	d, err := time.ParseDuration(timeStr)
   237  	c.Assert(err, IsNil)
   238  	return d
   239  }