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 }