github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cdc/kv/region_worker_test.go (about)

     1  // Copyright 2021 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 kv
    15  
    16  import (
    17  	"context"
    18  	"math/rand"
    19  	"runtime"
    20  	"sync"
    21  	"testing"
    22  
    23  	"github.com/pingcap/check"
    24  	"github.com/pingcap/kvproto/pkg/cdcpb"
    25  	"github.com/pingcap/ticdc/cdc/model"
    26  	"github.com/pingcap/ticdc/pkg/config"
    27  	"github.com/pingcap/ticdc/pkg/regionspan"
    28  	"github.com/pingcap/ticdc/pkg/util/testleak"
    29  	"github.com/pingcap/tidb/store/tikv"
    30  	"github.com/stretchr/testify/require"
    31  )
    32  
    33  type regionWorkerSuite struct{}
    34  
    35  var _ = check.Suite(&regionWorkerSuite{})
    36  
    37  func (s *regionWorkerSuite) TestRegionStateManager(c *check.C) {
    38  	defer testleak.AfterTest(c)()
    39  	rsm := newRegionStateManager(4)
    40  
    41  	regionID := uint64(1000)
    42  	_, ok := rsm.getState(regionID)
    43  	c.Assert(ok, check.IsFalse)
    44  
    45  	rsm.setState(regionID, &regionFeedState{requestID: 2})
    46  	state, ok := rsm.getState(regionID)
    47  	c.Assert(ok, check.IsTrue)
    48  	c.Assert(state.requestID, check.Equals, uint64(2))
    49  }
    50  
    51  func (s *regionWorkerSuite) TestRegionStateManagerThreadSafe(c *check.C) {
    52  	defer testleak.AfterTest(c)()
    53  	rsm := newRegionStateManager(4)
    54  	regionCount := 100
    55  	regionIDs := make([]uint64, regionCount)
    56  	for i := 0; i < regionCount; i++ {
    57  		regionID := uint64(1000 + i)
    58  		regionIDs[i] = regionID
    59  		rsm.setState(regionID, &regionFeedState{requestID: uint64(i + 1), lastResolvedTs: uint64(1000)})
    60  	}
    61  
    62  	var wg sync.WaitGroup
    63  	concurrency := 20
    64  	wg.Add(concurrency * 2)
    65  	for j := 0; j < concurrency; j++ {
    66  		go func() {
    67  			defer wg.Done()
    68  			for i := 0; i < 10000; i++ {
    69  				idx := rand.Intn(regionCount)
    70  				regionID := regionIDs[idx]
    71  				s, ok := rsm.getState(regionID)
    72  				c.Assert(ok, check.IsTrue)
    73  				s.lock.RLock()
    74  				c.Assert(s.requestID, check.Equals, uint64(idx+1))
    75  				s.lock.RUnlock()
    76  			}
    77  		}()
    78  	}
    79  	for j := 0; j < concurrency; j++ {
    80  		go func() {
    81  			defer wg.Done()
    82  			for i := 0; i < 10000; i++ {
    83  				// simulate write less than read
    84  				if i%5 != 0 {
    85  					continue
    86  				}
    87  				regionID := regionIDs[rand.Intn(regionCount)]
    88  				s, ok := rsm.getState(regionID)
    89  				c.Assert(ok, check.IsTrue)
    90  				s.lock.Lock()
    91  				s.lastResolvedTs += 10
    92  				s.lock.Unlock()
    93  				rsm.setState(regionID, s)
    94  			}
    95  		}()
    96  	}
    97  	wg.Wait()
    98  
    99  	totalResolvedTs := uint64(0)
   100  	for _, regionID := range regionIDs {
   101  		s, ok := rsm.getState(regionID)
   102  		c.Assert(ok, check.IsTrue)
   103  		c.Assert(s.lastResolvedTs, check.Greater, uint64(1000))
   104  		totalResolvedTs += s.lastResolvedTs
   105  	}
   106  	// 100 regions, initial resolved ts 1000;
   107  	// 2000 * resolved ts forward, increased by 10 each time, routine number is `concurrency`.
   108  	c.Assert(totalResolvedTs, check.Equals, uint64(100*1000+2000*10*concurrency))
   109  }
   110  
   111  func (s *regionWorkerSuite) TestRegionStateManagerBucket(c *check.C) {
   112  	defer testleak.AfterTest(c)()
   113  	rsm := newRegionStateManager(-1)
   114  	c.Assert(rsm.bucket, check.GreaterEqual, minRegionStateBucket)
   115  	c.Assert(rsm.bucket, check.LessEqual, maxRegionStateBucket)
   116  
   117  	bucket := rsm.bucket * 2
   118  	rsm = newRegionStateManager(bucket)
   119  	c.Assert(rsm.bucket, check.Equals, bucket)
   120  }
   121  
   122  func (s *regionWorkerSuite) TestRegionWorkerPoolSize(c *check.C) {
   123  	defer testleak.AfterTest(c)()
   124  
   125  	conf := config.GetDefaultServerConfig()
   126  	conf.KVClient.WorkerPoolSize = 0
   127  	config.StoreGlobalServerConfig(conf)
   128  	size := getWorkerPoolSize()
   129  	min := func(a, b int) int {
   130  		if a < b {
   131  			return a
   132  		}
   133  		return b
   134  	}
   135  	c.Assert(size, check.Equals, min(runtime.NumCPU()*2, maxWorkerPoolSize))
   136  
   137  	conf.KVClient.WorkerPoolSize = 5
   138  	size = getWorkerPoolSize()
   139  	c.Assert(size, check.Equals, 5)
   140  
   141  	conf.KVClient.WorkerPoolSize = maxWorkerPoolSize + 1
   142  	size = getWorkerPoolSize()
   143  	c.Assert(size, check.Equals, maxWorkerPoolSize)
   144  }
   145  
   146  type mockRouter struct {
   147  	LimitRegionRouter
   148  }
   149  
   150  func (*mockRouter) Release(id string) {}
   151  
   152  func TestRegionWokerHandleEventEntryEventOutOfOrder(t *testing.T) {
   153  	// For UPDATE SQL, its prewrite event has both value and old value.
   154  	// It is possible that TiDB prewrites multiple times for the same row when
   155  	// there are other transactions it conflicts with. For this case,
   156  	// if the value is not "short", only the first prewrite contains the value.
   157  	//
   158  	// TiKV may output events for the UPDATE SQL as following:
   159  	//
   160  	// TiDB: [Prwrite1]    [Prewrite2]      [Commit]
   161  	//       v             v                v                                   Time
   162  	// ---------------------------------------------------------------------------->
   163  	//         ^            ^    ^           ^     ^       ^     ^          ^     ^
   164  	// TiKV:   [Scan Start] [Send Prewrite2] [Send Commit] [Send Prewrite1] [Send Init]
   165  	// TiCDC:                    [Recv Prewrite2]  [Recv Commit] [Recv Prewrite1] [Recv Init]
   166  
   167  	ctx, cancel := context.WithCancel(context.Background())
   168  	defer cancel()
   169  	eventCh := make(chan model.RegionFeedEvent, 2)
   170  	s := &eventFeedSession{
   171  		regionRouter: &mockRouter{},
   172  		eventCh:      eventCh,
   173  	}
   174  	span := regionspan.Span{}.Hack()
   175  	state := newRegionFeedState(newSingleRegionInfo(
   176  		tikv.RegionVerID{},
   177  		regionspan.ToComparableSpan(span),
   178  		0, &tikv.RPCContext{}), 0)
   179  	state.start()
   180  	worker := newRegionWorker(s, nil, "")
   181  	worker.enableOldValue = true
   182  	worker.initMetrics(ctx)
   183  	require.Equal(t, 2, cap(worker.outputCh))
   184  
   185  	// Receive prewrite2 with empty value.
   186  	events := &cdcpb.Event_Entries_{
   187  		Entries: &cdcpb.Event_Entries{
   188  			Entries: []*cdcpb.Event_Row{{
   189  				StartTs:  1,
   190  				Type:     cdcpb.Event_PREWRITE,
   191  				OpType:   cdcpb.Event_Row_PUT,
   192  				Key:      []byte("key"),
   193  				Value:    nil,
   194  				OldValue: []byte("oldvalue"),
   195  			}},
   196  		},
   197  	}
   198  	err := worker.handleEventEntry(ctx, events, state)
   199  	require.Nil(t, err)
   200  
   201  	// Receive commit.
   202  	events = &cdcpb.Event_Entries_{
   203  		Entries: &cdcpb.Event_Entries{
   204  			Entries: []*cdcpb.Event_Row{{
   205  				StartTs:  1,
   206  				CommitTs: 2,
   207  				Type:     cdcpb.Event_COMMIT,
   208  				OpType:   cdcpb.Event_Row_PUT,
   209  				Key:      []byte("key"),
   210  			}},
   211  		},
   212  	}
   213  	err = worker.handleEventEntry(context.Background(), events, state)
   214  	require.Nil(t, err)
   215  
   216  	// Must not output event.
   217  	var event model.RegionFeedEvent
   218  	var ok bool
   219  	select {
   220  	case event, ok = <-eventCh:
   221  	default:
   222  	}
   223  	require.Falsef(t, ok, "%v", event)
   224  	require.EqualValuesf(t, model.RegionFeedEvent{}, event, "%v", event)
   225  
   226  	// Receive prewrite1 with actual value.
   227  	events = &cdcpb.Event_Entries_{
   228  		Entries: &cdcpb.Event_Entries{
   229  			Entries: []*cdcpb.Event_Row{{
   230  				StartTs:  1,
   231  				Type:     cdcpb.Event_PREWRITE,
   232  				OpType:   cdcpb.Event_Row_PUT,
   233  				Key:      []byte("key"),
   234  				Value:    []byte("value"),
   235  				OldValue: []byte("oldvalue"),
   236  			}},
   237  		},
   238  	}
   239  	err = worker.handleEventEntry(ctx, events, state)
   240  	require.Nil(t, err)
   241  
   242  	// Must not output event.
   243  	select {
   244  	case event, ok = <-eventCh:
   245  	default:
   246  	}
   247  	require.Falsef(t, ok, "%v", event)
   248  	require.EqualValuesf(t, model.RegionFeedEvent{}, event, "%v", event)
   249  
   250  	// Receive prewrite1 with actual value.
   251  	events = &cdcpb.Event_Entries_{
   252  		Entries: &cdcpb.Event_Entries{
   253  			Entries: []*cdcpb.Event_Row{
   254  				{
   255  					Type: cdcpb.Event_INITIALIZED,
   256  				},
   257  			},
   258  		},
   259  	}
   260  	err = worker.handleEventEntry(ctx, events, state)
   261  	require.Nil(t, err)
   262  
   263  	// Must output event.
   264  	select {
   265  	case event, ok = <-eventCh:
   266  	default:
   267  	}
   268  	require.Truef(t, ok, "%v", event)
   269  	require.EqualValuesf(t, model.RegionFeedEvent{
   270  		RegionID: 0,
   271  		Val: &model.RawKVEntry{
   272  			OpType:   model.OpTypePut,
   273  			Key:      []byte("key"),
   274  			Value:    []byte("value"),
   275  			StartTs:  1,
   276  			CRTs:     2,
   277  			RegionID: 0,
   278  			OldValue: []byte("oldvalue"),
   279  		},
   280  	}, event, "%v", event)
   281  }