github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/processor/sinkmanager/manager_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 sinkmanager
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"math"
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/pingcap/failpoint"
    24  	"github.com/pingcap/log"
    25  	"github.com/pingcap/tiflow/cdc/model"
    26  	"github.com/pingcap/tiflow/cdc/processor/sourcemanager/sorter"
    27  	"github.com/pingcap/tiflow/cdc/processor/tablepb"
    28  	"github.com/pingcap/tiflow/pkg/config"
    29  	"github.com/pingcap/tiflow/pkg/spanz"
    30  	"github.com/stretchr/testify/require"
    31  	"go.uber.org/zap"
    32  )
    33  
    34  func getChangefeedInfo() *model.ChangeFeedInfo {
    35  	replicaConfig := config.GetDefaultReplicaConfig()
    36  	replicaConfig.Consistent.MemoryUsage.MemoryQuotaPercentage = 75
    37  	return &model.ChangeFeedInfo{
    38  		Error:   nil,
    39  		SinkURI: "blackhole://",
    40  		Config:  replicaConfig,
    41  	}
    42  }
    43  
    44  // nolint:unparam
    45  // It is ok to use the same tableID in test.
    46  func addTableAndAddEventsToSortEngine(
    47  	_ *testing.T,
    48  	engine sorter.SortEngine,
    49  	span tablepb.Span,
    50  ) uint64 {
    51  	engine.AddTable(span, 0)
    52  	events := []*model.PolymorphicEvent{
    53  		{
    54  			StartTs: 1,
    55  			CRTs:    1,
    56  			RawKV: &model.RawKVEntry{
    57  				OpType:  model.OpTypePut,
    58  				StartTs: 1,
    59  				CRTs:    1,
    60  			},
    61  			Row: genRowChangedEvent(1, 1, span),
    62  		},
    63  		{
    64  			StartTs: 1,
    65  			CRTs:    2,
    66  			RawKV: &model.RawKVEntry{
    67  				OpType:  model.OpTypePut,
    68  				StartTs: 1,
    69  				CRTs:    2,
    70  			},
    71  			Row: genRowChangedEvent(1, 2, span),
    72  		},
    73  		{
    74  			StartTs: 1,
    75  			CRTs:    3,
    76  			RawKV: &model.RawKVEntry{
    77  				OpType:  model.OpTypePut,
    78  				StartTs: 1,
    79  				CRTs:    3,
    80  			},
    81  			Row: genRowChangedEvent(1, 3, span),
    82  		},
    83  		{
    84  			StartTs: 2,
    85  			CRTs:    4,
    86  			RawKV: &model.RawKVEntry{
    87  				OpType:  model.OpTypePut,
    88  				StartTs: 2,
    89  				CRTs:    4,
    90  			},
    91  			Row: genRowChangedEvent(2, 4, span),
    92  		},
    93  		{
    94  			CRTs: 4,
    95  			RawKV: &model.RawKVEntry{
    96  				OpType: model.OpTypeResolved,
    97  				CRTs:   4,
    98  			},
    99  		},
   100  		{
   101  			CRTs: 6,
   102  			RawKV: &model.RawKVEntry{
   103  				OpType: model.OpTypeResolved,
   104  				CRTs:   6,
   105  			},
   106  		},
   107  	}
   108  	size := uint64(0)
   109  	for _, event := range events {
   110  		if event.Row != nil {
   111  			size += uint64(event.Row.ApproximateBytes())
   112  		}
   113  		engine.Add(span, event)
   114  	}
   115  	return size
   116  }
   117  
   118  func TestAddTable(t *testing.T) {
   119  	t.Parallel()
   120  
   121  	ctx, cancel := context.WithCancel(context.Background())
   122  	changefeedInfo := getChangefeedInfo()
   123  	manager, _, _ := CreateManagerWithMemEngine(t, ctx, model.DefaultChangeFeedID("1"),
   124  		changefeedInfo, make(chan error, 1))
   125  	defer func() {
   126  		cancel()
   127  		manager.Close()
   128  	}()
   129  
   130  	span := spanz.TableIDToComparableSpan(1)
   131  	manager.AddTable(span, 1, 100)
   132  	tableSink, ok := manager.tableSinks.Load(span)
   133  	require.True(t, ok)
   134  	require.NotNil(t, tableSink)
   135  	require.Equal(t, 0, manager.sinkProgressHeap.len(), "Not started table shout not in progress heap")
   136  	err := manager.StartTable(span, 1)
   137  	require.NoError(t, err)
   138  	require.Equal(t, uint64(0x7ffffffffffbffff), tableSink.(*tableSinkWrapper).replicateTs)
   139  
   140  	progress := manager.sinkProgressHeap.pop()
   141  	require.Equal(t, span, progress.span)
   142  	require.Equal(t, uint64(0), progress.nextLowerBoundPos.StartTs)
   143  	require.Equal(t, uint64(2), progress.nextLowerBoundPos.CommitTs)
   144  }
   145  
   146  func TestRemoveTable(t *testing.T) {
   147  	t.Parallel()
   148  
   149  	ctx, cancel := context.WithCancel(context.Background())
   150  	changefeedInfo := getChangefeedInfo()
   151  	manager, _, e := CreateManagerWithMemEngine(t, ctx, model.DefaultChangeFeedID("1"),
   152  		changefeedInfo, make(chan error, 1))
   153  	defer func() {
   154  		cancel()
   155  		manager.Close()
   156  	}()
   157  
   158  	span := spanz.TableIDToComparableSpan(1)
   159  	manager.AddTable(span, 1, 100)
   160  	tableSink, ok := manager.tableSinks.Load(span)
   161  	require.True(t, ok)
   162  	require.NotNil(t, tableSink)
   163  	err := manager.StartTable(span, 0)
   164  	require.NoError(t, err)
   165  	totalEventSize := addTableAndAddEventsToSortEngine(t, e, span)
   166  	manager.UpdateBarrierTs(4, nil)
   167  	manager.UpdateReceivedSorterResolvedTs(span, 5)
   168  	manager.schemaStorage.AdvanceResolvedTs(5)
   169  	// Check all the events are sent to sink and record the memory usage.
   170  	require.Eventually(t, func() bool {
   171  		return manager.sinkMemQuota.GetUsedBytes() == totalEventSize
   172  	}, 5*time.Second, 10*time.Millisecond)
   173  
   174  	// Call this function times to test the idempotence.
   175  	manager.AsyncStopTable(span)
   176  	manager.AsyncStopTable(span)
   177  	manager.AsyncStopTable(span)
   178  	manager.AsyncStopTable(span)
   179  	require.Eventually(t, func() bool {
   180  		state, ok := manager.GetTableState(span)
   181  		require.True(t, ok)
   182  		return state == tablepb.TableStateStopped
   183  	}, 5*time.Second, 10*time.Millisecond)
   184  
   185  	manager.RemoveTable(span)
   186  
   187  	_, ok = manager.tableSinks.Load(span)
   188  	require.False(t, ok)
   189  	require.Equal(t, uint64(0), manager.sinkMemQuota.GetUsedBytes(), "After remove table, the memory usage should be 0.")
   190  }
   191  
   192  func TestGenerateTableSinkTaskWithBarrierTs(t *testing.T) {
   193  	ctx, cancel := context.WithCancel(context.Background())
   194  	changefeedInfo := getChangefeedInfo()
   195  	manager, _, e := CreateManagerWithMemEngine(t, ctx, model.DefaultChangeFeedID("1"),
   196  		changefeedInfo, make(chan error, 1))
   197  	defer func() {
   198  		cancel()
   199  		manager.Close()
   200  	}()
   201  
   202  	span := spanz.TableIDToComparableSpan(1)
   203  	manager.AddTable(span, 1, 100)
   204  	addTableAndAddEventsToSortEngine(t, e, span)
   205  	manager.UpdateBarrierTs(4, nil)
   206  	manager.UpdateReceivedSorterResolvedTs(span, 5)
   207  	manager.schemaStorage.AdvanceResolvedTs(5)
   208  	err := manager.StartTable(span, 0)
   209  	require.NoError(t, err)
   210  
   211  	require.Eventually(t, func() bool {
   212  		s := manager.GetTableStats(span)
   213  		return s.CheckpointTs == 4 && s.LastSyncedTs == 4
   214  	}, 5*time.Second, 10*time.Millisecond)
   215  
   216  	manager.UpdateBarrierTs(6, nil)
   217  	manager.UpdateReceivedSorterResolvedTs(span, 6)
   218  	manager.schemaStorage.AdvanceResolvedTs(6)
   219  	require.Eventually(t, func() bool {
   220  		s := manager.GetTableStats(span)
   221  		log.Info("checkpoint ts", zap.Uint64("checkpointTs", s.CheckpointTs), zap.Uint64("lastSyncedTs", s.LastSyncedTs))
   222  		fmt.Printf("debug checkpoint ts %d, lastSyncedTs %d\n", s.CheckpointTs, s.LastSyncedTs)
   223  		return s.CheckpointTs == 6 && s.LastSyncedTs == 4
   224  	}, 5*time.Second, 10*time.Millisecond)
   225  }
   226  
   227  func TestGenerateTableSinkTaskWithResolvedTs(t *testing.T) {
   228  	t.Parallel()
   229  
   230  	ctx, cancel := context.WithCancel(context.Background())
   231  	changefeedInfo := getChangefeedInfo()
   232  	manager, _, e := CreateManagerWithMemEngine(t, ctx, model.DefaultChangeFeedID("1"),
   233  		changefeedInfo, make(chan error, 1))
   234  	defer func() {
   235  		cancel()
   236  		manager.Close()
   237  	}()
   238  
   239  	span := spanz.TableIDToComparableSpan(1)
   240  	manager.AddTable(span, 1, 100)
   241  	addTableAndAddEventsToSortEngine(t, e, span)
   242  	// This would happen when the table just added to this node and redo log is enabled.
   243  	// So there is possibility that the resolved ts is smaller than the global barrier ts.
   244  	manager.UpdateBarrierTs(4, nil)
   245  	manager.UpdateReceivedSorterResolvedTs(span, 3)
   246  	manager.schemaStorage.AdvanceResolvedTs(4)
   247  	err := manager.StartTable(span, 0)
   248  	require.NoError(t, err)
   249  
   250  	require.Eventually(t, func() bool {
   251  		s := manager.GetTableStats(span)
   252  		return s.CheckpointTs == 3 && s.LastSyncedTs == 3
   253  	}, 5*time.Second, 10*time.Millisecond)
   254  }
   255  
   256  func TestGetTableStatsToReleaseMemQuota(t *testing.T) {
   257  	t.Parallel()
   258  
   259  	ctx, cancel := context.WithCancel(context.Background())
   260  	changefeedInfo := getChangefeedInfo()
   261  	manager, _, e := CreateManagerWithMemEngine(t, ctx, model.DefaultChangeFeedID("1"),
   262  		changefeedInfo, make(chan error, 1))
   263  	defer func() {
   264  		cancel()
   265  		manager.Close()
   266  	}()
   267  
   268  	span := spanz.TableIDToComparableSpan(1)
   269  	manager.AddTable(span, 1, 100)
   270  	addTableAndAddEventsToSortEngine(t, e, span)
   271  
   272  	manager.UpdateBarrierTs(4, nil)
   273  	manager.UpdateReceivedSorterResolvedTs(span, 5)
   274  	manager.schemaStorage.AdvanceResolvedTs(5)
   275  	err := manager.StartTable(span, 0)
   276  	require.NoError(t, err)
   277  
   278  	require.Eventually(t, func() bool {
   279  		s := manager.GetTableStats(span)
   280  		return manager.sinkMemQuota.GetUsedBytes() == 0 && s.CheckpointTs == 4 && s.LastSyncedTs == 4
   281  	}, 5*time.Second, 10*time.Millisecond)
   282  }
   283  
   284  func TestDoNotGenerateTableSinkTaskWhenTableIsNotReplicating(t *testing.T) {
   285  	t.Parallel()
   286  
   287  	ctx, cancel := context.WithCancel(context.Background())
   288  	changefeedInfo := getChangefeedInfo()
   289  	manager, _, e := CreateManagerWithMemEngine(t, ctx, model.DefaultChangeFeedID("1"),
   290  		changefeedInfo, make(chan error, 1))
   291  	defer func() {
   292  		cancel()
   293  		manager.Close()
   294  	}()
   295  
   296  	span := spanz.TableIDToComparableSpan(1)
   297  	manager.AddTable(span, 1, 100)
   298  	addTableAndAddEventsToSortEngine(t, e, span)
   299  	manager.UpdateBarrierTs(4, nil)
   300  	manager.UpdateReceivedSorterResolvedTs(span, 5)
   301  
   302  	require.Equal(t, uint64(0), manager.sinkMemQuota.GetUsedBytes())
   303  	tableSink, ok := manager.tableSinks.Load(span)
   304  	require.True(t, ok)
   305  	require.NotNil(t, tableSink)
   306  	checkpointTS := tableSink.(*tableSinkWrapper).getCheckpointTs()
   307  	require.Equal(t, uint64(1), checkpointTS.Ts)
   308  }
   309  
   310  func TestClose(t *testing.T) {
   311  	t.Parallel()
   312  
   313  	ctx, cancel := context.WithCancel(context.Background())
   314  	defer cancel()
   315  
   316  	changefeedInfo := getChangefeedInfo()
   317  	manager, _, _ := CreateManagerWithMemEngine(t, ctx, model.DefaultChangeFeedID("1"),
   318  		changefeedInfo, make(chan error, 1))
   319  
   320  	cancel()
   321  	manager.Close()
   322  }
   323  
   324  // This could happen when closing the sink manager and source manager.
   325  // We close the sink manager first, and then close the source manager.
   326  // So probably the source manager calls the sink manager to update the resolved ts to a removed table.
   327  func TestUpdateReceivedSorterResolvedTsOfNonExistTable(t *testing.T) {
   328  	t.Parallel()
   329  
   330  	ctx, cancel := context.WithCancel(context.Background())
   331  	changefeedInfo := getChangefeedInfo()
   332  	manager, _, _ := CreateManagerWithMemEngine(t, ctx, model.DefaultChangeFeedID("1"),
   333  		changefeedInfo, make(chan error, 1))
   334  	defer func() {
   335  		cancel()
   336  		manager.Close()
   337  	}()
   338  
   339  	manager.UpdateReceivedSorterResolvedTs(spanz.TableIDToComparableSpan(1), 1)
   340  }
   341  
   342  // Sink worker errors should cancel the sink manager correctly.
   343  func TestSinkManagerRunWithErrors(t *testing.T) {
   344  	t.Parallel()
   345  
   346  	ctx, cancel := context.WithCancel(context.Background())
   347  	errCh := make(chan error, 16)
   348  	changefeedInfo := getChangefeedInfo()
   349  	manager, source, _ := CreateManagerWithMemEngine(t, ctx, model.DefaultChangeFeedID("1"), changefeedInfo, errCh)
   350  	defer func() {
   351  		cancel()
   352  		manager.Close()
   353  	}()
   354  
   355  	_ = failpoint.Enable("github.com/pingcap/tiflow/cdc/processor/sinkmanager/SinkWorkerTaskError", "return")
   356  	defer func() {
   357  		_ = failpoint.Disable("github.com/pingcap/tiflow/cdc/processor/sinkmanager/SinkWorkerTaskError")
   358  	}()
   359  
   360  	span := spanz.TableIDToComparableSpan(1)
   361  
   362  	source.AddTable(span, "test", 100)
   363  	manager.AddTable(span, 100, math.MaxUint64)
   364  	manager.StartTable(span, 100)
   365  	source.Add(span, model.NewResolvedPolymorphicEvent(0, 101))
   366  	manager.UpdateReceivedSorterResolvedTs(span, 101)
   367  	manager.UpdateBarrierTs(101, nil)
   368  
   369  	timer := time.NewTimer(5 * time.Second)
   370  	select {
   371  	case <-errCh:
   372  		if !timer.Stop() {
   373  			<-timer.C
   374  		}
   375  		return
   376  	case <-timer.C:
   377  		log.Panic("must get an error instead of a timeout")
   378  	}
   379  }
   380  
   381  func TestSinkManagerNeedsStuckCheck(t *testing.T) {
   382  	t.Parallel()
   383  
   384  	ctx, cancel := context.WithCancel(context.Background())
   385  	errCh := make(chan error, 16)
   386  	changefeedInfo := getChangefeedInfo()
   387  	manager, _, _ := CreateManagerWithMemEngine(t, ctx, model.DefaultChangeFeedID("1"), changefeedInfo, errCh)
   388  	defer func() {
   389  		cancel()
   390  		manager.Close()
   391  	}()
   392  
   393  	require.False(t, manager.needsStuckCheck())
   394  }
   395  
   396  func TestSinkManagerRestartTableSinks(t *testing.T) {
   397  	failpoint.Enable("github.com/pingcap/tiflow/cdc/processor/sinkmanager/SinkWorkerTaskHandlePause", "return")
   398  	defer failpoint.Disable("github.com/pingcap/tiflow/cdc/processor/sinkmanager/SinkWorkerTaskHandlePause")
   399  
   400  	ctx, cancel := context.WithCancel(context.Background())
   401  	errCh := make(chan error, 16)
   402  	changefeedInfo := getChangefeedInfo()
   403  	manager, _, _ := CreateManagerWithMemEngine(t, ctx, model.ChangeFeedID{}, changefeedInfo, errCh)
   404  	defer func() {
   405  		cancel()
   406  		manager.Close()
   407  	}()
   408  
   409  	span := tablepb.Span{TableID: 1}
   410  	manager.AddTable(span, 1, 100)
   411  	require.Nil(t, manager.StartTable(span, 2))
   412  	table, exists := manager.tableSinks.Load(span)
   413  	require.True(t, exists)
   414  
   415  	table.(*tableSinkWrapper).updateReceivedSorterResolvedTs(4)
   416  	table.(*tableSinkWrapper).updateBarrierTs(4)
   417  	select {
   418  	case task := <-manager.sinkTaskChan:
   419  		require.Equal(t, sorter.Position{StartTs: 0, CommitTs: 3}, task.lowerBound)
   420  		task.callback(sorter.Position{StartTs: 3, CommitTs: 4})
   421  	case <-time.After(2 * time.Second):
   422  		panic("should always get a sink task")
   423  	}
   424  
   425  	// With the failpoint blackhole/WriteEventsFail enabled, sink manager should restarts
   426  	// the table sink at its checkpoint.
   427  	failpoint.Enable("github.com/pingcap/tiflow/cdc/sink/dmlsink/blackhole/WriteEventsFail", "1*return")
   428  	defer failpoint.Disable("github.com/pingcap/tiflow/cdc/sink/dmlsink/blackhole/WriteEventsFail")
   429  	select {
   430  	case task := <-manager.sinkTaskChan:
   431  		require.Equal(t, sorter.Position{StartTs: 2, CommitTs: 2}, task.lowerBound)
   432  		task.callback(sorter.Position{StartTs: 3, CommitTs: 4})
   433  	case <-time.After(2 * time.Second):
   434  		panic("should always get a sink task")
   435  	}
   436  }