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 }