github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/processor/sinkmanager/redo_log_advancer_test.go (about)

     1  // Copyright 2023 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 sinkmanager
    15  
    16  import (
    17  	"context"
    18  	"sync"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/pingcap/tiflow/cdc/model"
    23  	"github.com/pingcap/tiflow/cdc/processor/memquota"
    24  	"github.com/pingcap/tiflow/cdc/processor/sourcemanager/sorter"
    25  	"github.com/pingcap/tiflow/cdc/processor/tablepb"
    26  	"github.com/pingcap/tiflow/cdc/redo"
    27  	"github.com/pingcap/tiflow/pkg/spanz"
    28  	"github.com/stretchr/testify/require"
    29  	"github.com/stretchr/testify/suite"
    30  )
    31  
    32  var _ redo.DMLManager = &mockRedoDMLManager{}
    33  
    34  type mockRedoDMLManager struct {
    35  	mu                  sync.Mutex
    36  	events              map[int64][]*model.RowChangedEvent
    37  	resolvedTss         map[int64]model.Ts
    38  	releaseRowsMemories map[int64]func()
    39  }
    40  
    41  func newMockRedoDMLManager() *mockRedoDMLManager {
    42  	return &mockRedoDMLManager{
    43  		events:              make(map[int64][]*model.RowChangedEvent),
    44  		resolvedTss:         make(map[int64]model.Ts),
    45  		releaseRowsMemories: make(map[int64]func()),
    46  	}
    47  }
    48  
    49  func (m *mockRedoDMLManager) Enabled() bool {
    50  	panic("unreachable")
    51  }
    52  
    53  func (m *mockRedoDMLManager) Run(ctx context.Context, _ ...chan<- error) error {
    54  	panic("unreachable")
    55  }
    56  
    57  func (m *mockRedoDMLManager) WaitForReady(_ context.Context) {
    58  	panic("unreachable")
    59  }
    60  
    61  func (m *mockRedoDMLManager) Close() {
    62  	panic("unreachable")
    63  }
    64  
    65  func (m *mockRedoDMLManager) AddTable(span tablepb.Span, startTs uint64) {
    66  	panic("unreachable")
    67  }
    68  
    69  func (m *mockRedoDMLManager) RemoveTable(span tablepb.Span) {
    70  	panic("unreachable")
    71  }
    72  
    73  func (m *mockRedoDMLManager) UpdateResolvedTs(ctx context.Context,
    74  	span tablepb.Span, resolvedTs uint64,
    75  ) error {
    76  	m.mu.Lock()
    77  	defer m.mu.Unlock()
    78  	m.resolvedTss[span.TableID] = resolvedTs
    79  	return nil
    80  }
    81  
    82  func (m *mockRedoDMLManager) StartTable(span tablepb.Span, resolvedTs uint64) {
    83  	m.mu.Lock()
    84  	defer m.mu.Unlock()
    85  	m.resolvedTss[span.TableID] = resolvedTs
    86  }
    87  
    88  func (m *mockRedoDMLManager) GetResolvedTs(span tablepb.Span) model.Ts {
    89  	m.mu.Lock()
    90  	defer m.mu.Unlock()
    91  	return m.resolvedTss[span.TableID]
    92  }
    93  
    94  func (m *mockRedoDMLManager) EmitRowChangedEvents(ctx context.Context,
    95  	span tablepb.Span, releaseRowsMemory func(), rows ...*model.RowChangedEvent,
    96  ) error {
    97  	m.mu.Lock()
    98  	defer m.mu.Unlock()
    99  	if _, ok := m.events[span.TableID]; !ok {
   100  		m.events[span.TableID] = make([]*model.RowChangedEvent, 0)
   101  	}
   102  	m.events[span.TableID] = append(m.events[span.TableID], rows...)
   103  	m.releaseRowsMemories[span.TableID] = releaseRowsMemory
   104  
   105  	return nil
   106  }
   107  
   108  func (m *mockRedoDMLManager) getEvents(span tablepb.Span) []*model.RowChangedEvent {
   109  	m.mu.Lock()
   110  	defer m.mu.Unlock()
   111  	return m.events[span.TableID]
   112  }
   113  
   114  func (m *mockRedoDMLManager) releaseRowsMemory(span tablepb.Span) {
   115  	m.mu.Lock()
   116  	defer m.mu.Unlock()
   117  	m.releaseRowsMemories[span.TableID]()
   118  }
   119  
   120  type redoLogAdvancerSuite struct {
   121  	suite.Suite
   122  	testChangefeedID    model.ChangeFeedID
   123  	testSpan            tablepb.Span
   124  	defaultTestMemQuota uint64
   125  }
   126  
   127  func (suite *redoLogAdvancerSuite) SetupSuite() {
   128  	requestMemSize = 256
   129  	maxUpdateIntervalSize = 512
   130  	suite.testChangefeedID = model.DefaultChangeFeedID("1")
   131  	suite.testSpan = spanz.TableIDToComparableSpan(1)
   132  	suite.defaultTestMemQuota = 1024
   133  }
   134  
   135  func (suite *redoLogAdvancerSuite) TearDownSuite() {
   136  	requestMemSize = defaultRequestMemSize
   137  	maxUpdateIntervalSize = defaultMaxUpdateIntervalSize
   138  }
   139  
   140  func TestRedoLogAdvancerSuite(t *testing.T) {
   141  	suite.Run(t, new(redoLogAdvancerSuite))
   142  }
   143  
   144  func (suite *redoLogAdvancerSuite) genRedoTaskAndRedoDMLManager() (*redoTask, *mockRedoDMLManager) {
   145  	redoDMLManager := newMockRedoDMLManager()
   146  	wrapper, _ := createTableSinkWrapper(suite.testChangefeedID, suite.testSpan)
   147  
   148  	task := &redoTask{
   149  		span:      suite.testSpan,
   150  		tableSink: wrapper,
   151  	}
   152  	return task, redoDMLManager
   153  }
   154  
   155  func (suite *redoLogAdvancerSuite) genMemQuota(initMemQuota uint64) *memquota.MemQuota {
   156  	memoryQuota := memquota.NewMemQuota(suite.testChangefeedID, suite.defaultTestMemQuota, "sink")
   157  	memoryQuota.ForceAcquire(initMemQuota)
   158  	memoryQuota.AddTable(suite.testSpan)
   159  	return memoryQuota
   160  }
   161  
   162  func (suite *redoLogAdvancerSuite) TestNewRedoLogAdvancer() {
   163  	task, manager := suite.genRedoTaskAndRedoDMLManager()
   164  	memoryQuota := suite.genMemQuota(512)
   165  	defer memoryQuota.Close()
   166  	advancer := newRedoLogAdvancer(task, memoryQuota, 512, manager)
   167  	require.NotNil(suite.T(), advancer)
   168  	require.Equal(suite.T(), uint64(512), advancer.availableMem)
   169  }
   170  
   171  func (suite *redoLogAdvancerSuite) TestHasEnoughMem() {
   172  	memoryQuota := suite.genMemQuota(512)
   173  	defer memoryQuota.Close()
   174  	task, manager := suite.genRedoTaskAndRedoDMLManager()
   175  	advancer := newRedoLogAdvancer(task, memoryQuota, 512, manager)
   176  	require.NotNil(suite.T(), advancer)
   177  	require.True(suite.T(), advancer.hasEnoughMem())
   178  	for i := 0; i < 6; i++ {
   179  		// 6 * 256 = 1536 > 1024
   180  		advancer.appendEvents([]*model.RowChangedEvent{{}}, 256)
   181  	}
   182  	require.False(suite.T(), advancer.hasEnoughMem(),
   183  		"hasEnoughMem should return false when usedMem > availableMem")
   184  }
   185  
   186  func (suite *redoLogAdvancerSuite) TestCleanup() {
   187  	memoryQuota := suite.genMemQuota(512)
   188  	defer memoryQuota.Close()
   189  	task, manager := suite.genRedoTaskAndRedoDMLManager()
   190  	advancer := newRedoLogAdvancer(task, memoryQuota, 512, manager)
   191  	require.NotNil(suite.T(), advancer)
   192  	require.Equal(suite.T(), uint64(512), advancer.availableMem)
   193  	require.Equal(suite.T(), uint64(0), advancer.usedMem)
   194  	require.Equal(suite.T(), uint64(512), memoryQuota.GetUsedBytes())
   195  	advancer.cleanup()
   196  	require.Equal(suite.T(), uint64(0), memoryQuota.GetUsedBytes(),
   197  		"memory quota should be released after cleanup")
   198  }
   199  
   200  func (suite *redoLogAdvancerSuite) TestAppendEvents() {
   201  	memoryQuota := suite.genMemQuota(512)
   202  	defer memoryQuota.Close()
   203  	task, manager := suite.genRedoTaskAndRedoDMLManager()
   204  	advancer := newRedoLogAdvancer(task, memoryQuota, 512, manager)
   205  	require.NotNil(suite.T(), advancer)
   206  	require.True(suite.T(), advancer.hasEnoughMem())
   207  	for i := 0; i < 2; i++ {
   208  		advancer.appendEvents([]*model.RowChangedEvent{{}}, 256)
   209  	}
   210  	require.Equal(suite.T(), uint64(512), advancer.pendingTxnSize)
   211  	require.Equal(suite.T(), uint64(512), advancer.usedMem)
   212  	require.False(suite.T(), advancer.hasEnoughMem())
   213  	require.Len(suite.T(), advancer.events, 2)
   214  }
   215  
   216  func (suite *redoLogAdvancerSuite) TestTryMoveMoveToNextTxn() {
   217  	memoryQuota := suite.genMemQuota(512)
   218  	defer memoryQuota.Close()
   219  	task, manager := suite.genRedoTaskAndRedoDMLManager()
   220  	advancer := newRedoLogAdvancer(task, memoryQuota, 512, manager)
   221  	require.NotNil(suite.T(), advancer)
   222  
   223  	// Initial state.
   224  	require.Equal(suite.T(), uint64(0), advancer.lastTxnCommitTs)
   225  	require.Equal(suite.T(), uint64(0), advancer.currTxnCommitTs)
   226  	pos := sorter.Position{StartTs: 1, CommitTs: 3}
   227  	// Append 1 event with commit ts 1
   228  	advancer.appendEvents([]*model.RowChangedEvent{
   229  		{CommitTs: 1},
   230  	}, 256)
   231  	require.Equal(suite.T(), uint64(256), advancer.usedMem)
   232  	advancer.tryMoveToNextTxn(1, pos)
   233  	require.Equal(suite.T(), uint64(0), advancer.lastTxnCommitTs)
   234  	require.Equal(suite.T(), uint64(1), advancer.currTxnCommitTs)
   235  
   236  	// Append 2 events with commit ts 2
   237  	for i := 0; i < 2; i++ {
   238  		advancer.appendEvents([]*model.RowChangedEvent{
   239  			{CommitTs: 2},
   240  		}, 256)
   241  	}
   242  	require.Equal(suite.T(), uint64(768), advancer.usedMem)
   243  	require.Equal(suite.T(), uint64(0), advancer.lastTxnCommitTs)
   244  	require.Equal(suite.T(), uint64(1), advancer.currTxnCommitTs)
   245  
   246  	// Try to move to next txn.
   247  	advancer.tryMoveToNextTxn(2, pos)
   248  	require.Equal(suite.T(), uint64(1), advancer.lastTxnCommitTs)
   249  	require.Equal(suite.T(), uint64(2), advancer.currTxnCommitTs)
   250  
   251  	// Set pos to a commit fence
   252  	pos = sorter.Position{
   253  		StartTs:  2,
   254  		CommitTs: 3,
   255  	}
   256  	// Try to move to next txn.
   257  	advancer.tryMoveToNextTxn(2, pos)
   258  	require.Equal(suite.T(), uint64(2), advancer.lastTxnCommitTs)
   259  	require.Equal(suite.T(), uint64(2), advancer.currTxnCommitTs)
   260  }
   261  
   262  func (suite *redoLogAdvancerSuite) TestAdvance() {
   263  	ctx, cancel := context.WithCancel(context.Background())
   264  	defer cancel()
   265  	memoryQuota := suite.genMemQuota(768)
   266  	defer memoryQuota.Close()
   267  	task, manager := suite.genRedoTaskAndRedoDMLManager()
   268  	advancer := newRedoLogAdvancer(task, memoryQuota, 768, manager)
   269  	require.NotNil(suite.T(), advancer)
   270  
   271  	pos := sorter.Position{StartTs: 1, CommitTs: 3}
   272  	// 1. append 1 event with commit ts 1
   273  	advancer.appendEvents([]*model.RowChangedEvent{
   274  		{CommitTs: 1},
   275  	}, 256)
   276  	require.Equal(suite.T(), uint64(256), advancer.usedMem)
   277  	advancer.tryMoveToNextTxn(1, pos)
   278  
   279  	// 2. append 2 events with commit ts 2
   280  	for i := 0; i < 2; i++ {
   281  		advancer.appendEvents([]*model.RowChangedEvent{
   282  			{CommitTs: 2},
   283  		}, 256)
   284  	}
   285  	advancer.tryMoveToNextTxn(2, pos)
   286  
   287  	require.Equal(suite.T(), uint64(768), advancer.pendingTxnSize)
   288  	err := advancer.advance(ctx)
   289  	require.NoError(suite.T(), err)
   290  
   291  	require.Len(suite.T(), manager.getEvents(suite.testSpan), 3)
   292  	require.Equal(suite.T(), uint64(1), manager.GetResolvedTs(suite.testSpan))
   293  	require.Equal(suite.T(), uint64(0), advancer.pendingTxnSize)
   294  	require.Equal(suite.T(), uint64(768), memoryQuota.GetUsedBytes())
   295  	manager.releaseRowsMemory(suite.testSpan)
   296  	require.Equal(suite.T(), uint64(0), memoryQuota.GetUsedBytes(),
   297  		"memory quota should be released after releaseRowsMemory is called")
   298  }
   299  
   300  func (suite *redoLogAdvancerSuite) TestTryAdvanceWhenExceedAvailableMem() {
   301  	ctx, cancel := context.WithCancel(context.Background())
   302  	defer cancel()
   303  	memoryQuota := suite.genMemQuota(768)
   304  	defer memoryQuota.Close()
   305  	task, manager := suite.genRedoTaskAndRedoDMLManager()
   306  	advancer := newRedoLogAdvancer(task, memoryQuota, 768, manager)
   307  	require.NotNil(suite.T(), advancer)
   308  
   309  	pos := sorter.Position{StartTs: 1, CommitTs: 2}
   310  	// 1. append 1 event with commit ts 2
   311  	advancer.appendEvents([]*model.RowChangedEvent{
   312  		{CommitTs: 2},
   313  	}, 256)
   314  	require.Equal(suite.T(), uint64(256), advancer.usedMem)
   315  	advancer.tryMoveToNextTxn(2, pos)
   316  
   317  	// 2. append 3 events with commit ts 3
   318  	for i := 0; i < 3; i++ {
   319  		advancer.appendEvents([]*model.RowChangedEvent{
   320  			{CommitTs: 3},
   321  		}, 256)
   322  	}
   323  	require.Equal(suite.T(), uint64(1024), advancer.usedMem)
   324  	pos = sorter.Position{StartTs: 2, CommitTs: 3}
   325  	advancer.tryMoveToNextTxn(3, pos)
   326  
   327  	require.Equal(suite.T(), uint64(1024), advancer.pendingTxnSize)
   328  	require.Equal(suite.T(), uint64(768), memoryQuota.GetUsedBytes())
   329  	// 3. Try advance with txn is finished.
   330  	err := advancer.tryAdvanceAndAcquireMem(
   331  		ctx,
   332  		false,
   333  		true,
   334  	)
   335  	require.NoError(suite.T(), err)
   336  	require.Equal(suite.T(), uint64(1024), memoryQuota.GetUsedBytes(),
   337  		"Memory quota should be force acquired when exceed available memory.",
   338  	)
   339  
   340  	require.Len(suite.T(), manager.getEvents(suite.testSpan), 4)
   341  	require.Equal(suite.T(), uint64(3), manager.GetResolvedTs(suite.testSpan))
   342  	require.Equal(suite.T(), uint64(0), advancer.pendingTxnSize)
   343  	manager.releaseRowsMemory(suite.testSpan)
   344  	require.Equal(suite.T(), uint64(0), memoryQuota.GetUsedBytes(),
   345  		"memory quota should be released after releaseRowsMemory is called")
   346  }
   347  
   348  func (suite *redoLogAdvancerSuite) TestTryAdvanceWhenReachTheMaxUpdateIntSizeAndTxnNotFinished() {
   349  	ctx, cancel := context.WithCancel(context.Background())
   350  	defer cancel()
   351  	memoryQuota := suite.genMemQuota(768)
   352  	defer memoryQuota.Close()
   353  	task, manager := suite.genRedoTaskAndRedoDMLManager()
   354  	advancer := newRedoLogAdvancer(task, memoryQuota, 768, manager)
   355  	require.NotNil(suite.T(), advancer)
   356  
   357  	pos := sorter.Position{StartTs: 1, CommitTs: 2}
   358  	// 1. append 1 event with commit ts 2
   359  	advancer.appendEvents([]*model.RowChangedEvent{
   360  		{CommitTs: 2},
   361  	}, 256)
   362  	require.Equal(suite.T(), uint64(256), advancer.usedMem)
   363  	advancer.tryMoveToNextTxn(2, pos)
   364  
   365  	// 2. append 2 events with commit ts 3
   366  	for i := 0; i < 2; i++ {
   367  		advancer.appendEvents([]*model.RowChangedEvent{
   368  			{CommitTs: 3},
   369  		}, 256)
   370  	}
   371  	require.Equal(suite.T(), uint64(768), advancer.usedMem)
   372  	pos = sorter.Position{StartTs: 1, CommitTs: 3}
   373  	advancer.tryMoveToNextTxn(3, pos)
   374  
   375  	// 3. Try advance with txn is not finished.
   376  	err := advancer.tryAdvanceAndAcquireMem(
   377  		ctx,
   378  		false,
   379  		false,
   380  	)
   381  	require.NoError(suite.T(), err)
   382  	require.Len(suite.T(), manager.getEvents(suite.testSpan), 3)
   383  	require.Equal(suite.T(), uint64(2), manager.GetResolvedTs(suite.testSpan))
   384  	require.Equal(suite.T(), uint64(0), advancer.pendingTxnSize)
   385  	manager.releaseRowsMemory(suite.testSpan)
   386  	require.Equal(suite.T(), uint64(256), memoryQuota.GetUsedBytes(),
   387  		"memory quota should be released after releaseRowsMemory is called")
   388  }
   389  
   390  func (suite *redoLogAdvancerSuite) TestFinish() {
   391  	ctx, cancel := context.WithCancel(context.Background())
   392  	defer cancel()
   393  	memoryQuota := suite.genMemQuota(768)
   394  	defer memoryQuota.Close()
   395  	task, manager := suite.genRedoTaskAndRedoDMLManager()
   396  	advancer := newRedoLogAdvancer(task, memoryQuota, 768, manager)
   397  	require.NotNil(suite.T(), advancer)
   398  
   399  	pos := sorter.Position{StartTs: 1, CommitTs: 2}
   400  	// 1. append 1 event with commit ts 2
   401  	advancer.appendEvents([]*model.RowChangedEvent{
   402  		{CommitTs: 2},
   403  	}, 256)
   404  	require.Equal(suite.T(), uint64(256), advancer.usedMem)
   405  	advancer.tryMoveToNextTxn(2, pos)
   406  
   407  	// 2. append 2 events with commit ts 3
   408  	for i := 0; i < 2; i++ {
   409  		advancer.appendEvents([]*model.RowChangedEvent{
   410  			{CommitTs: 3},
   411  		}, 256)
   412  	}
   413  	require.Equal(suite.T(), uint64(768), advancer.usedMem)
   414  	pos = sorter.Position{StartTs: 1, CommitTs: 3}
   415  	advancer.tryMoveToNextTxn(3, pos)
   416  
   417  	require.Equal(suite.T(), uint64(2), advancer.lastTxnCommitTs)
   418  	require.Equal(suite.T(), uint64(3), advancer.currTxnCommitTs)
   419  	// 3. Try finish.
   420  	err := advancer.finish(
   421  		ctx,
   422  		pos,
   423  	)
   424  	require.NoError(suite.T(), err)
   425  
   426  	// All events should be flushed and the last pos should be updated.
   427  	require.Equal(suite.T(), pos, advancer.lastPos)
   428  	require.Equal(suite.T(), uint64(3), advancer.lastTxnCommitTs)
   429  	require.Equal(suite.T(), uint64(3), advancer.currTxnCommitTs)
   430  
   431  	require.Len(suite.T(), manager.getEvents(suite.testSpan), 3)
   432  	require.Equal(suite.T(), uint64(3), manager.GetResolvedTs(suite.testSpan))
   433  	require.Equal(suite.T(), uint64(0), advancer.pendingTxnSize)
   434  	manager.releaseRowsMemory(suite.testSpan)
   435  	require.Equal(suite.T(), uint64(0), memoryQuota.GetUsedBytes(),
   436  		"memory quota should be released after releaseRowsMemory is called")
   437  }
   438  
   439  func (suite *redoLogAdvancerSuite) TestTryAdvanceAndBlockAcquireWithSplitTxn() {
   440  	ctx, cancel := context.WithCancel(context.Background())
   441  	defer cancel()
   442  	memoryQuota := suite.genMemQuota(768)
   443  	defer memoryQuota.Close()
   444  	task, manager := suite.genRedoTaskAndRedoDMLManager()
   445  	advancer := newRedoLogAdvancer(task, memoryQuota, 768, manager)
   446  	require.NotNil(suite.T(), advancer)
   447  
   448  	pos := sorter.Position{StartTs: 1, CommitTs: 2}
   449  	// 1. append 1 event with commit ts 2
   450  	advancer.appendEvents([]*model.RowChangedEvent{
   451  		{CommitTs: 2},
   452  	}, 256)
   453  	require.Equal(suite.T(), uint64(256), advancer.usedMem)
   454  	advancer.tryMoveToNextTxn(2, pos)
   455  
   456  	// 2. append 3 events with commit ts 3, this will exceed the memory quota.
   457  	for i := 0; i < 3; i++ {
   458  		advancer.appendEvents([]*model.RowChangedEvent{
   459  			{CommitTs: 3},
   460  		}, 256)
   461  	}
   462  	require.Equal(suite.T(), uint64(1024), advancer.usedMem)
   463  	pos = sorter.Position{StartTs: 1, CommitTs: 3}
   464  	advancer.tryMoveToNextTxn(3, pos)
   465  
   466  	// 3. Last pos is a commit fence.
   467  	advancer.lastPos = sorter.Position{
   468  		StartTs:  2,
   469  		CommitTs: 3,
   470  	}
   471  
   472  	down := make(chan struct{})
   473  	go func() {
   474  		// 4. Try advance and block acquire.
   475  		err := advancer.tryAdvanceAndAcquireMem(
   476  			ctx,
   477  			false,
   478  			false,
   479  		)
   480  		require.ErrorIs(suite.T(), err, context.Canceled)
   481  		down <- struct{}{}
   482  	}()
   483  
   484  	// Wait all events are flushed.
   485  	require.Eventually(suite.T(), func() bool {
   486  		return len(manager.getEvents(suite.testSpan)) == 4
   487  	}, 5*time.Second, 10*time.Millisecond)
   488  	// After ack, abort the blocked acquire.
   489  	memoryQuota.Close()
   490  	<-down
   491  }