github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/sink/tablesink/table_sink_impl_test.go (about)

     1  // Copyright 2022 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 tablesink
    15  
    16  import (
    17  	"sort"
    18  	"sync"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/pingcap/tiflow/cdc/model"
    23  	"github.com/pingcap/tiflow/cdc/sink/dmlsink"
    24  	"github.com/pingcap/tiflow/cdc/sink/tablesink/state"
    25  	"github.com/pingcap/tiflow/pkg/pdutil"
    26  	"github.com/pingcap/tiflow/pkg/sink"
    27  	"github.com/pingcap/tiflow/pkg/spanz"
    28  	"github.com/prometheus/client_golang/prometheus"
    29  	"github.com/stretchr/testify/require"
    30  )
    31  
    32  // Assert EventSink implementation
    33  var _ dmlsink.EventSink[*model.SingleTableTxn] = (*mockEventSink)(nil)
    34  
    35  type mockEventSink struct {
    36  	dead   chan struct{}
    37  	events []*dmlsink.TxnCallbackableEvent
    38  }
    39  
    40  func (m *mockEventSink) WriteEvents(rows ...*dmlsink.TxnCallbackableEvent) error {
    41  	m.events = append(m.events, rows...)
    42  	return nil
    43  }
    44  
    45  func (m *mockEventSink) Close() {
    46  	close(m.dead)
    47  }
    48  
    49  func (m *mockEventSink) Dead() <-chan struct{} {
    50  	return m.dead
    51  }
    52  
    53  func (m *mockEventSink) Scheme() string {
    54  	return sink.BlackHoleScheme
    55  }
    56  
    57  // acknowledge the txn events by call the callback function.
    58  func (m *mockEventSink) acknowledge(commitTs uint64) []*dmlsink.TxnCallbackableEvent {
    59  	var droppedEvents []*dmlsink.TxnCallbackableEvent
    60  	i := sort.Search(len(m.events), func(i int) bool {
    61  		return m.events[i].Event.GetCommitTs() > commitTs
    62  	})
    63  	if i == 0 {
    64  		return droppedEvents
    65  	}
    66  	ackedEvents := m.events[:i]
    67  	for _, event := range ackedEvents {
    68  		if event.GetTableSinkState() == state.TableSinkSinking {
    69  			event.Callback()
    70  		} else {
    71  			event.Callback()
    72  			droppedEvents = append(droppedEvents, event)
    73  		}
    74  	}
    75  
    76  	// Remove the acked events from the event buffer.
    77  	m.events = append(
    78  		make([]*dmlsink.TxnCallbackableEvent,
    79  			0,
    80  			len(m.events[i:])),
    81  		m.events[i:]...,
    82  	)
    83  
    84  	return droppedEvents
    85  }
    86  
    87  func getTestRows() []*model.RowChangedEvent {
    88  	tableInfo := &model.TableInfo{
    89  		TableName: model.TableName{
    90  			Schema:      "test",
    91  			Table:       "t1",
    92  			TableID:     1,
    93  			IsPartition: false,
    94  		},
    95  	}
    96  
    97  	return []*model.RowChangedEvent{
    98  		{
    99  			TableInfo: tableInfo,
   100  			CommitTs:  101,
   101  			StartTs:   98,
   102  		},
   103  		{
   104  			TableInfo: tableInfo,
   105  			CommitTs:  102,
   106  			StartTs:   99,
   107  		},
   108  		{
   109  			TableInfo: tableInfo,
   110  			CommitTs:  102,
   111  			StartTs:   100,
   112  		},
   113  		{
   114  			TableInfo: tableInfo,
   115  			CommitTs:  102,
   116  			StartTs:   100,
   117  		},
   118  		{
   119  			TableInfo: tableInfo,
   120  			CommitTs:  103,
   121  			StartTs:   101,
   122  		},
   123  		{
   124  			TableInfo: tableInfo,
   125  			CommitTs:  103,
   126  			StartTs:   101,
   127  		},
   128  		{
   129  			TableInfo: tableInfo,
   130  			CommitTs:  104,
   131  			StartTs:   102,
   132  		},
   133  		{
   134  			TableInfo: tableInfo,
   135  			CommitTs:  105,
   136  			StartTs:   103,
   137  			// Batch1
   138  			SplitTxn: true,
   139  		},
   140  		{
   141  			TableInfo: tableInfo,
   142  			CommitTs:  105,
   143  			StartTs:   103,
   144  		},
   145  		{
   146  			TableInfo: tableInfo,
   147  			CommitTs:  105,
   148  			StartTs:   103,
   149  		},
   150  		{
   151  			TableInfo: tableInfo,
   152  			CommitTs:  105,
   153  			StartTs:   103,
   154  			// Batch2
   155  			SplitTxn: true,
   156  		},
   157  		{
   158  			TableInfo: tableInfo,
   159  			CommitTs:  105,
   160  			StartTs:   103,
   161  		},
   162  	}
   163  }
   164  
   165  func TestNewEventTableSink(t *testing.T) {
   166  	t.Parallel()
   167  
   168  	sink := &mockEventSink{dead: make(chan struct{})}
   169  	tb := New[*model.SingleTableTxn](
   170  		model.DefaultChangeFeedID("1"), spanz.TableIDToComparableSpan(1), model.Ts(0),
   171  		sink, &dmlsink.TxnEventAppender{},
   172  		pdutil.NewClock4Test(),
   173  		prometheus.NewCounter(prometheus.CounterOpts{}),
   174  		prometheus.NewHistogram(prometheus.HistogramOpts{}))
   175  
   176  	require.Equal(t, model.NewResolvedTs(0), tb.maxResolvedTs, "maxResolvedTs should start from 0")
   177  	require.NotNil(t, sink, tb.backendSink, "backendSink should be set")
   178  	require.NotNil(t, tb.progressTracker, "progressTracker should be set")
   179  	require.NotNil(t, tb.eventAppender, "eventAppender should be set")
   180  	require.Equal(t, 0, len(tb.eventBuffer), "eventBuffer should be empty")
   181  	require.Equal(t, state.TableSinkSinking, tb.state, "table sink should be sinking")
   182  }
   183  
   184  func TestAppendRowChangedEvents(t *testing.T) {
   185  	t.Parallel()
   186  
   187  	sink := &mockEventSink{dead: make(chan struct{})}
   188  	tb := New[*model.SingleTableTxn](
   189  		model.DefaultChangeFeedID("1"), spanz.TableIDToComparableSpan(1), model.Ts(0),
   190  		sink, &dmlsink.TxnEventAppender{},
   191  		pdutil.NewClock4Test(),
   192  		prometheus.NewCounter(prometheus.CounterOpts{}),
   193  		prometheus.NewHistogram(prometheus.HistogramOpts{}))
   194  
   195  	tb.AppendRowChangedEvents(getTestRows()...)
   196  	require.Len(t, tb.eventBuffer, 7, "txn event buffer should have 7 txns")
   197  }
   198  
   199  func TestUpdateResolvedTs(t *testing.T) {
   200  	t.Parallel()
   201  
   202  	sink := &mockEventSink{dead: make(chan struct{})}
   203  	tb := New[*model.SingleTableTxn](
   204  		model.DefaultChangeFeedID("1"), spanz.TableIDToComparableSpan(1), model.Ts(0),
   205  		sink, &dmlsink.TxnEventAppender{},
   206  		pdutil.NewClock4Test(),
   207  		prometheus.NewCounter(prometheus.CounterOpts{}),
   208  		prometheus.NewHistogram(prometheus.HistogramOpts{}))
   209  
   210  	tb.AppendRowChangedEvents(getTestRows()...)
   211  	// No event will be flushed.
   212  	err := tb.UpdateResolvedTs(model.NewResolvedTs(100))
   213  	require.Nil(t, err)
   214  	require.Equal(t, model.NewResolvedTs(100), tb.maxResolvedTs, "maxResolvedTs should be updated")
   215  	require.Len(t, tb.eventBuffer, 7, "txn event buffer should have 7 txns")
   216  	require.Len(t, sink.events, 0, "no event should not be flushed")
   217  
   218  	// One event will be flushed.
   219  	err = tb.UpdateResolvedTs(model.NewResolvedTs(101))
   220  	require.Nil(t, err)
   221  	require.Equal(t, model.NewResolvedTs(101), tb.maxResolvedTs, "maxResolvedTs should be updated")
   222  	require.Len(t, tb.eventBuffer, 6, "txn event buffer should have 6 txns")
   223  	require.Len(t, sink.events, 1, "one event should be flushed")
   224  
   225  	// Two events will be flushed.
   226  	err = tb.UpdateResolvedTs(model.NewResolvedTs(102))
   227  	require.Nil(t, err)
   228  	require.Equal(t, model.NewResolvedTs(102), tb.maxResolvedTs, "maxResolvedTs should be updated")
   229  	require.Len(t, tb.eventBuffer, 4, "txn event buffer should have 4 txns")
   230  	require.Len(t, sink.events, 3, "two events should be flushed")
   231  
   232  	// Same resolved ts will not be flushed.
   233  	err = tb.UpdateResolvedTs(model.NewResolvedTs(102))
   234  	require.Nil(t, err)
   235  	require.Equal(
   236  		t,
   237  		model.NewResolvedTs(102),
   238  		tb.maxResolvedTs,
   239  		"maxResolvedTs should not be updated",
   240  	)
   241  	require.Len(t, tb.eventBuffer, 4, "txn event buffer should still have 4 txns")
   242  	require.Len(t, sink.events, 3, "no event should be flushed")
   243  
   244  	// All events will be flushed.
   245  	err = tb.UpdateResolvedTs(model.NewResolvedTs(105))
   246  	require.Nil(t, err)
   247  	require.Equal(t, model.NewResolvedTs(105), tb.maxResolvedTs, "maxResolvedTs should be updated")
   248  	require.Len(t, tb.eventBuffer, 0, "txn event buffer should be empty")
   249  	require.Len(t, sink.events, 7, "all events should be flushed")
   250  }
   251  
   252  func TestGetCheckpointTs(t *testing.T) {
   253  	t.Parallel()
   254  
   255  	sink := &mockEventSink{dead: make(chan struct{})}
   256  	tb := New[*model.SingleTableTxn](
   257  		model.DefaultChangeFeedID("1"), spanz.TableIDToComparableSpan(1), model.Ts(0),
   258  		sink, &dmlsink.TxnEventAppender{},
   259  		pdutil.NewClock4Test(),
   260  		prometheus.NewCounter(prometheus.CounterOpts{}),
   261  		prometheus.NewHistogram(prometheus.HistogramOpts{}))
   262  
   263  	tb.AppendRowChangedEvents(getTestRows()...)
   264  	require.Equal(t, model.NewResolvedTs(0), tb.GetCheckpointTs(), "checkpointTs should be 0")
   265  	require.Equal(t, tb.lastSyncedTs.getLastSyncedTs(), uint64(0), "lastSyncedTs should be not updated")
   266  
   267  	// One event will be flushed.
   268  	err := tb.UpdateResolvedTs(model.NewResolvedTs(101))
   269  	require.Nil(t, err)
   270  	require.Equal(t, model.NewResolvedTs(0), tb.GetCheckpointTs(), "checkpointTs should be 0")
   271  	sink.acknowledge(101)
   272  	require.Equal(t, model.NewResolvedTs(101), tb.GetCheckpointTs(), "checkpointTs should be 101")
   273  	require.Equal(t, tb.lastSyncedTs.getLastSyncedTs(), uint64(101), "lastSyncedTs should be the same as the flushed event")
   274  
   275  	// Flush all events.
   276  	err = tb.UpdateResolvedTs(model.NewResolvedTs(105))
   277  	require.Nil(t, err)
   278  	require.Equal(t, model.NewResolvedTs(101), tb.GetCheckpointTs(), "checkpointTs should be 101")
   279  	require.Equal(t, tb.lastSyncedTs.getLastSyncedTs(), uint64(101), "lastSyncedTs should be not updated")
   280  
   281  	// Only acknowledge some events.
   282  	sink.acknowledge(102)
   283  	require.Equal(
   284  		t,
   285  		model.NewResolvedTs(101),
   286  		tb.GetCheckpointTs(),
   287  		"checkpointTs should still be 101",
   288  	)
   289  	require.Equal(t, tb.lastSyncedTs.getLastSyncedTs(), uint64(102), "lastSyncedTs should be updated")
   290  
   291  	// Ack all events.
   292  	sink.acknowledge(105)
   293  	require.Equal(t, model.NewResolvedTs(105), tb.GetCheckpointTs(), "checkpointTs should be 105")
   294  	require.Equal(t, tb.lastSyncedTs.getLastSyncedTs(), uint64(105), "lastSyncedTs should be updated")
   295  }
   296  
   297  func TestClose(t *testing.T) {
   298  	t.Parallel()
   299  
   300  	sink := &mockEventSink{dead: make(chan struct{})}
   301  	tb := New[*model.SingleTableTxn](
   302  		model.DefaultChangeFeedID("1"), spanz.TableIDToComparableSpan(1), model.Ts(0),
   303  		sink, &dmlsink.TxnEventAppender{},
   304  		pdutil.NewClock4Test(),
   305  		prometheus.NewCounter(prometheus.CounterOpts{}),
   306  		prometheus.NewHistogram(prometheus.HistogramOpts{}))
   307  
   308  	tb.AppendRowChangedEvents(getTestRows()...)
   309  	err := tb.UpdateResolvedTs(model.NewResolvedTs(105))
   310  	require.Nil(t, err)
   311  	require.Len(t, sink.events, 7, "all events should be flushed")
   312  	var wg sync.WaitGroup
   313  	wg.Add(1)
   314  	go func() {
   315  		tb.Close()
   316  		wg.Done()
   317  	}()
   318  	require.Eventually(t, func() bool {
   319  		return state.TableSinkStopping == tb.state.Load()
   320  	}, time.Second, time.Millisecond*10, "table should be stopping")
   321  	droppedEvents := sink.acknowledge(105)
   322  	require.Len(t, droppedEvents, 7, "all events should be dropped")
   323  	wg.Wait()
   324  	require.Eventually(t, func() bool {
   325  		return state.TableSinkStopped == tb.state.Load()
   326  	}, time.Second, time.Millisecond*10, "table should be stopped")
   327  }
   328  
   329  func TestOperationsAfterClose(t *testing.T) {
   330  	t.Parallel()
   331  
   332  	sink := &mockEventSink{dead: make(chan struct{})}
   333  	tb := New[*model.SingleTableTxn](
   334  		model.DefaultChangeFeedID("1"), spanz.TableIDToComparableSpan(1), model.Ts(0),
   335  		sink, &dmlsink.TxnEventAppender{},
   336  		pdutil.NewClock4Test(),
   337  		prometheus.NewCounter(prometheus.CounterOpts{}),
   338  		prometheus.NewHistogram(prometheus.HistogramOpts{}))
   339  
   340  	require.True(t, tb.AsyncClose())
   341  
   342  	tb.AppendRowChangedEvents(getTestRows()...)
   343  	err := tb.UpdateResolvedTs(model.NewResolvedTs(105))
   344  	require.Nil(t, err)
   345  }
   346  
   347  func TestCloseCancellable(t *testing.T) {
   348  	t.Parallel()
   349  
   350  	sink := &mockEventSink{dead: make(chan struct{})}
   351  	tb := New[*model.SingleTableTxn](
   352  		model.DefaultChangeFeedID("1"), spanz.TableIDToComparableSpan(1), model.Ts(0),
   353  		sink, &dmlsink.TxnEventAppender{},
   354  		pdutil.NewClock4Test(),
   355  		prometheus.NewCounter(prometheus.CounterOpts{}),
   356  		prometheus.NewHistogram(prometheus.HistogramOpts{}))
   357  
   358  	tb.AppendRowChangedEvents(getTestRows()...)
   359  	err := tb.UpdateResolvedTs(model.NewResolvedTs(105))
   360  	require.Nil(t, err)
   361  	require.Len(t, sink.events, 7, "all events should be flushed")
   362  
   363  	go func() {
   364  		time.Sleep(time.Millisecond * 10)
   365  		sink.Close()
   366  	}()
   367  
   368  	var wg sync.WaitGroup
   369  	wg.Add(1)
   370  	go func() {
   371  		tb.Close()
   372  		wg.Done()
   373  	}()
   374  	wg.Wait()
   375  	require.Eventually(t, func() bool {
   376  		return state.TableSinkStopped == tb.state.Load()
   377  	}, time.Second, time.Millisecond*10, "table should be stopped")
   378  }
   379  
   380  func TestCloseReentrant(t *testing.T) {
   381  	t.Parallel()
   382  
   383  	sink := &mockEventSink{dead: make(chan struct{})}
   384  	tb := New[*model.SingleTableTxn](
   385  		model.DefaultChangeFeedID("1"), spanz.TableIDToComparableSpan(1), model.Ts(0),
   386  		sink, &dmlsink.TxnEventAppender{},
   387  		pdutil.NewClock4Test(),
   388  		prometheus.NewCounter(prometheus.CounterOpts{}),
   389  		prometheus.NewHistogram(prometheus.HistogramOpts{}))
   390  
   391  	tb.AppendRowChangedEvents(getTestRows()...)
   392  	err := tb.UpdateResolvedTs(model.NewResolvedTs(105))
   393  	require.Nil(t, err)
   394  	require.Len(t, sink.events, 7, "all events should be flushed")
   395  
   396  	go func() {
   397  		time.Sleep(time.Millisecond * 10)
   398  		sink.Close()
   399  	}()
   400  
   401  	var wg sync.WaitGroup
   402  	wg.Add(1)
   403  	go func() {
   404  		tb.Close()
   405  		wg.Done()
   406  	}()
   407  	wg.Wait()
   408  	require.Eventually(t, func() bool {
   409  		return state.TableSinkStopped == tb.state.Load()
   410  	}, 5*time.Second, time.Millisecond*10, "table should be stopped")
   411  	tb.Close()
   412  }
   413  
   414  // TestCheckpointTsFrozenWhenStopping make sure wo do not update checkpoint
   415  // ts when it is stopping.
   416  func TestCheckpointTsFrozenWhenStopping(t *testing.T) {
   417  	t.Parallel()
   418  
   419  	sink := &mockEventSink{dead: make(chan struct{})}
   420  	tb := New[*model.SingleTableTxn](
   421  		model.DefaultChangeFeedID("1"), spanz.TableIDToComparableSpan(1), model.Ts(0),
   422  		sink, &dmlsink.TxnEventAppender{},
   423  		pdutil.NewClock4Test(),
   424  		prometheus.NewCounter(prometheus.CounterOpts{}),
   425  		prometheus.NewHistogram(prometheus.HistogramOpts{}))
   426  
   427  	tb.AppendRowChangedEvents(getTestRows()...)
   428  	err := tb.UpdateResolvedTs(model.NewResolvedTs(105))
   429  	require.Nil(t, err)
   430  	require.Len(t, sink.events, 7, "all events should be flushed")
   431  
   432  	// Table sink close should return even if callbacks are not called,
   433  	// because the backend sink is closed.
   434  	sink.Close()
   435  	tb.Close()
   436  
   437  	require.Equal(t, state.TableSinkStopped, tb.state.Load())
   438  
   439  	currentTs := tb.GetCheckpointTs()
   440  	sink.acknowledge(105)
   441  	require.Equal(t, currentTs, tb.GetCheckpointTs(), "checkpointTs should not be updated")
   442  	require.Equal(t, tb.lastSyncedTs.getLastSyncedTs(), uint64(105), "lastSyncedTs should not change")
   443  }