github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/redo/meta_manager_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 redo
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"strconv"
    20  	"strings"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/pingcap/tiflow/cdc/model"
    25  	"github.com/pingcap/tiflow/cdc/redo/common"
    26  	"github.com/pingcap/tiflow/pkg/config"
    27  	"github.com/pingcap/tiflow/pkg/redo"
    28  	"github.com/pingcap/tiflow/pkg/util"
    29  	"github.com/pingcap/tiflow/pkg/uuid"
    30  	"github.com/stretchr/testify/require"
    31  	"golang.org/x/sync/errgroup"
    32  )
    33  
    34  func TestInitAndWriteMeta(t *testing.T) {
    35  	t.Parallel()
    36  
    37  	ctx, cancel := context.WithCancel(context.Background())
    38  	defer cancel()
    39  	captureID := "test-capture"
    40  	nConfig := config.GetGlobalServerConfig().Clone()
    41  	config.StoreGlobalServerConfig(nConfig)
    42  	changefeedID := model.DefaultChangeFeedID("test-changefeed")
    43  
    44  	extStorage, uri, err := util.GetTestExtStorage(ctx, t.TempDir())
    45  	require.NoError(t, err)
    46  
    47  	// write some meta and log files
    48  	metas := []common.LogMeta{
    49  		{CheckpointTs: 1, ResolvedTs: 2},
    50  		{CheckpointTs: 8, ResolvedTs: 9},
    51  		{CheckpointTs: 9, ResolvedTs: 11},
    52  	}
    53  
    54  	var toRemoveFiles []string
    55  	for _, meta := range metas {
    56  		data, err := meta.MarshalMsg(nil)
    57  		require.NoError(t, err)
    58  		metaName := getMetafileName(captureID, changefeedID, uuid.NewGenerator())
    59  		err = extStorage.WriteFile(ctx, metaName, data)
    60  		require.NoError(t, err)
    61  		toRemoveFiles = append(toRemoveFiles, metaName)
    62  	}
    63  
    64  	var notRemoveFiles []string
    65  	require.NoError(t, err)
    66  	for i := 0; i < 10; i++ {
    67  		fileName := "dummy" + getChangefeedMatcher(changefeedID) + strconv.Itoa(i)
    68  		err = extStorage.WriteFile(ctx, fileName, []byte{})
    69  		require.NoError(t, err)
    70  		notRemoveFiles = append(notRemoveFiles, fileName)
    71  	}
    72  
    73  	startTs := uint64(10)
    74  	cfg := &config.ConsistentConfig{
    75  		Level:                 string(redo.ConsistentLevelEventual),
    76  		MaxLogSize:            redo.DefaultMaxLogSize,
    77  		Storage:               uri.String(),
    78  		FlushIntervalInMs:     redo.MinFlushIntervalInMs,
    79  		MetaFlushIntervalInMs: redo.MinFlushIntervalInMs,
    80  		EncodingWorkerNum:     redo.DefaultEncodingWorkerNum,
    81  		FlushWorkerNum:        redo.DefaultFlushWorkerNum,
    82  	}
    83  	m := NewMetaManager(changefeedID, cfg, startTs)
    84  
    85  	var eg errgroup.Group
    86  	eg.Go(func() error {
    87  		return m.Run(ctx)
    88  	})
    89  
    90  	require.Eventually(t, func() bool {
    91  		return startTs == m.metaCheckpointTs.getFlushed()
    92  	}, time.Second, 50*time.Millisecond)
    93  
    94  	require.Eventually(t, func() bool {
    95  		return uint64(11) == m.metaResolvedTs.getFlushed()
    96  	}, time.Second, 50*time.Millisecond)
    97  
    98  	for _, fileName := range toRemoveFiles {
    99  		ret, err := extStorage.FileExists(ctx, fileName)
   100  		require.NoError(t, err)
   101  		require.False(t, ret, "file %s should be removed", fileName)
   102  	}
   103  	for _, fileName := range notRemoveFiles {
   104  		ret, err := extStorage.FileExists(ctx, fileName)
   105  		require.NoError(t, err)
   106  		require.True(t, ret, "file %s should not be removed", fileName)
   107  	}
   108  
   109  	testWriteMeta(ctx, t, m)
   110  
   111  	cancel()
   112  	require.ErrorIs(t, eg.Wait(), context.Canceled)
   113  }
   114  
   115  func TestPreCleanupAndWriteMeta(t *testing.T) {
   116  	t.Parallel()
   117  
   118  	ctx, cancel := context.WithCancel(context.Background())
   119  	defer cancel()
   120  	captureID := "test-capture"
   121  	nConfig := config.GetGlobalServerConfig().Clone()
   122  	config.StoreGlobalServerConfig(nConfig)
   123  	changefeedID := model.DefaultChangeFeedID("test-changefeed")
   124  
   125  	extStorage, uri, err := util.GetTestExtStorage(ctx, t.TempDir())
   126  	require.NoError(t, err)
   127  
   128  	// write some meta and log files
   129  	metas := []common.LogMeta{
   130  		{CheckpointTs: 1, ResolvedTs: 2},
   131  		{CheckpointTs: 8, ResolvedTs: 9},
   132  		{CheckpointTs: 9, ResolvedTs: 11},
   133  		{CheckpointTs: 11, ResolvedTs: 12},
   134  	}
   135  	var toRemoveFiles []string
   136  	for _, meta := range metas {
   137  		data, err := meta.MarshalMsg(nil)
   138  		require.NoError(t, err)
   139  		metaName := getMetafileName(captureID, changefeedID, uuid.NewGenerator())
   140  		err = extStorage.WriteFile(ctx, metaName, data)
   141  		require.NoError(t, err)
   142  		toRemoveFiles = append(toRemoveFiles, metaName)
   143  	}
   144  	// write delete mark to simulate a deleted changefeed
   145  	err = extStorage.WriteFile(ctx, getDeletedChangefeedMarker(changefeedID), []byte{})
   146  	require.NoError(t, err)
   147  	for i := 0; i < 10; i++ {
   148  		fileName := "dummy" + getChangefeedMatcher(changefeedID) + strconv.Itoa(i)
   149  		err = extStorage.WriteFile(ctx, fileName, []byte{})
   150  		require.NoError(t, err)
   151  		toRemoveFiles = append(toRemoveFiles, fileName)
   152  	}
   153  
   154  	startTs := uint64(10)
   155  	cfg := &config.ConsistentConfig{
   156  		Level:                 string(redo.ConsistentLevelEventual),
   157  		MaxLogSize:            redo.DefaultMaxLogSize,
   158  		Storage:               uri.String(),
   159  		FlushIntervalInMs:     redo.MinFlushIntervalInMs,
   160  		MetaFlushIntervalInMs: redo.MinFlushIntervalInMs,
   161  		EncodingWorkerNum:     redo.DefaultEncodingWorkerNum,
   162  		FlushWorkerNum:        redo.DefaultFlushWorkerNum,
   163  	}
   164  	m := NewMetaManager(changefeedID, cfg, startTs)
   165  
   166  	var eg errgroup.Group
   167  	eg.Go(func() error {
   168  		return m.Run(ctx)
   169  	})
   170  
   171  	require.Eventually(t, func() bool {
   172  		return startTs == m.metaCheckpointTs.getFlushed()
   173  	}, time.Second, 50*time.Millisecond)
   174  
   175  	require.Eventually(t, func() bool {
   176  		return startTs == m.metaResolvedTs.getFlushed()
   177  	}, time.Second, 50*time.Millisecond)
   178  
   179  	for _, fileName := range toRemoveFiles {
   180  		ret, err := extStorage.FileExists(ctx, fileName)
   181  		require.NoError(t, err)
   182  		require.False(t, ret, "file %s should be removed", fileName)
   183  	}
   184  	testWriteMeta(ctx, t, m)
   185  
   186  	cancel()
   187  	require.ErrorIs(t, eg.Wait(), context.Canceled)
   188  }
   189  
   190  func testWriteMeta(ctx context.Context, t *testing.T, m *metaManager) {
   191  	checkMeta := func(targetCheckpointTs, targetResolvedTs uint64) {
   192  		var checkpointTs, resolvedTs uint64
   193  		var metas []*common.LogMeta
   194  		cnt := 0
   195  		m.extStorage.WalkDir(ctx, nil, func(path string, size int64) error {
   196  			if !strings.HasSuffix(path, redo.MetaEXT) {
   197  				return nil
   198  			}
   199  			cnt++
   200  			data, err := m.extStorage.ReadFile(ctx, path)
   201  			require.NoError(t, err)
   202  			meta := &common.LogMeta{}
   203  			_, err = meta.UnmarshalMsg(data)
   204  			require.NoError(t, err)
   205  			metas = append(metas, meta)
   206  			return nil
   207  		})
   208  		require.Equal(t, 1, cnt)
   209  		common.ParseMeta(metas, &checkpointTs, &resolvedTs)
   210  		require.Equal(t, targetCheckpointTs, checkpointTs)
   211  		require.Equal(t, targetResolvedTs, resolvedTs)
   212  	}
   213  
   214  	// test both regressed
   215  	meta := m.GetFlushedMeta()
   216  	m.UpdateMeta(1, 2)
   217  	checkMeta(meta.CheckpointTs, meta.ResolvedTs)
   218  
   219  	// test checkpoint regressed
   220  	m.UpdateMeta(3, 20)
   221  	require.Eventually(t, func() bool {
   222  		return m.metaResolvedTs.getFlushed() == 20
   223  	}, time.Second, 50*time.Millisecond)
   224  	checkMeta(meta.CheckpointTs, 20)
   225  
   226  	// test resolved regressed
   227  	m.UpdateMeta(15, 18)
   228  	require.Eventually(t, func() bool {
   229  		return m.metaCheckpointTs.getFlushed() == 15
   230  	}, time.Second, 50*time.Millisecond)
   231  	checkMeta(15, 20)
   232  
   233  	// test both advanced
   234  	m.UpdateMeta(16, 21)
   235  	require.Eventually(t, func() bool {
   236  		return m.metaCheckpointTs.getFlushed() == 16
   237  	}, time.Second, 50*time.Millisecond)
   238  	checkMeta(16, 21)
   239  }
   240  
   241  func TestGCAndCleanup(t *testing.T) {
   242  	t.Parallel()
   243  
   244  	ctx, cancel := context.WithCancel(context.Background())
   245  	defer cancel()
   246  	captureID := "test-capture"
   247  	nConfig := config.GetGlobalServerConfig().Clone()
   248  	config.StoreGlobalServerConfig(nConfig)
   249  	changefeedID := model.DefaultChangeFeedID("test-changefeed")
   250  
   251  	extStorage, uri, err := util.GetTestExtStorage(ctx, t.TempDir())
   252  	require.NoError(t, err)
   253  
   254  	checkGC := func(checkpointTs uint64) {
   255  		time.Sleep(time.Duration(redo.DefaultGCIntervalInMs) * time.Millisecond * 20)
   256  		checkEqual := false
   257  		extStorage.WalkDir(ctx, nil, func(path string, size int64) error {
   258  			if strings.HasSuffix(path, redo.MetaEXT) {
   259  				return nil
   260  			}
   261  			commitTs, _, err := redo.ParseLogFileName(path)
   262  			require.NoError(t, err)
   263  			require.LessOrEqual(t, checkpointTs, commitTs)
   264  			if checkpointTs == commitTs {
   265  				checkEqual = true
   266  			}
   267  			return nil
   268  		})
   269  		require.True(t, checkEqual)
   270  	}
   271  
   272  	// write some log files
   273  	maxCommitTs := 20
   274  	for i := 1; i <= maxCommitTs; i++ {
   275  		for _, logType := range []string{redo.RedoRowLogFileType, redo.RedoDDLLogFileType} {
   276  			// fileName with different captureID and maxCommitTs
   277  			curCaptureID := fmt.Sprintf("%s%d", captureID, i%10)
   278  			maxCommitTs := i
   279  			fileName := fmt.Sprintf(redo.RedoLogFileFormatV1, curCaptureID, changefeedID.ID,
   280  				logType, maxCommitTs, uuid.NewGenerator().NewString(), redo.LogEXT)
   281  			err := extStorage.WriteFile(ctx, fileName, []byte{})
   282  			require.NoError(t, err)
   283  		}
   284  	}
   285  
   286  	startTs := uint64(3)
   287  	cfg := &config.ConsistentConfig{
   288  		Level:                 string(redo.ConsistentLevelEventual),
   289  		MaxLogSize:            redo.DefaultMaxLogSize,
   290  		Storage:               uri.String(),
   291  		FlushIntervalInMs:     redo.MinFlushIntervalInMs,
   292  		MetaFlushIntervalInMs: redo.MinFlushIntervalInMs,
   293  		EncodingWorkerNum:     redo.DefaultEncodingWorkerNum,
   294  		FlushWorkerNum:        redo.DefaultFlushWorkerNum,
   295  	}
   296  
   297  	m := NewMetaManager(changefeedID, cfg, startTs)
   298  
   299  	var eg errgroup.Group
   300  	eg.Go(func() error {
   301  		return m.Run(ctx)
   302  	})
   303  
   304  	require.Eventually(t, func() bool {
   305  		return startTs == m.metaCheckpointTs.getFlushed()
   306  	}, time.Second, 50*time.Millisecond)
   307  
   308  	require.Eventually(t, func() bool {
   309  		return startTs == m.metaResolvedTs.getFlushed()
   310  	}, time.Second, 50*time.Millisecond)
   311  
   312  	checkGC(startTs)
   313  
   314  	for i := startTs; i <= uint64(maxCommitTs); i++ {
   315  		m.UpdateMeta(i, 100)
   316  		checkGC(i)
   317  	}
   318  
   319  	cancel()
   320  	require.ErrorIs(t, eg.Wait(), context.Canceled)
   321  
   322  	cleanupCtx, cleanupCancel := context.WithCancel(context.Background())
   323  	defer cleanupCancel()
   324  	m.Cleanup(cleanupCtx)
   325  	ret, err := extStorage.FileExists(cleanupCtx, getDeletedChangefeedMarker(changefeedID))
   326  	require.NoError(t, err)
   327  	require.True(t, ret)
   328  	cnt := 0
   329  	extStorage.WalkDir(cleanupCtx, nil, func(path string, size int64) error {
   330  		cnt++
   331  		return nil
   332  	})
   333  	require.Equal(t, 1, cnt)
   334  }