github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/processor/sinkmanager/redo_log_advancer_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 sinkmanager 15 16 import ( 17 "context" 18 "sync" 19 "testing" 20 "time" 21 22 "github.com/pingcap/tiflow/cdc/model" 23 "github.com/pingcap/tiflow/cdc/processor/memquota" 24 "github.com/pingcap/tiflow/cdc/processor/sourcemanager/sorter" 25 "github.com/pingcap/tiflow/cdc/processor/tablepb" 26 "github.com/pingcap/tiflow/cdc/redo" 27 "github.com/pingcap/tiflow/pkg/spanz" 28 "github.com/stretchr/testify/require" 29 "github.com/stretchr/testify/suite" 30 ) 31 32 var _ redo.DMLManager = &mockRedoDMLManager{} 33 34 type mockRedoDMLManager struct { 35 mu sync.Mutex 36 events map[int64][]*model.RowChangedEvent 37 resolvedTss map[int64]model.Ts 38 releaseRowsMemories map[int64]func() 39 } 40 41 func newMockRedoDMLManager() *mockRedoDMLManager { 42 return &mockRedoDMLManager{ 43 events: make(map[int64][]*model.RowChangedEvent), 44 resolvedTss: make(map[int64]model.Ts), 45 releaseRowsMemories: make(map[int64]func()), 46 } 47 } 48 49 func (m *mockRedoDMLManager) Enabled() bool { 50 panic("unreachable") 51 } 52 53 func (m *mockRedoDMLManager) Run(ctx context.Context, _ ...chan<- error) error { 54 panic("unreachable") 55 } 56 57 func (m *mockRedoDMLManager) WaitForReady(_ context.Context) { 58 panic("unreachable") 59 } 60 61 func (m *mockRedoDMLManager) Close() { 62 panic("unreachable") 63 } 64 65 func (m *mockRedoDMLManager) AddTable(span tablepb.Span, startTs uint64) { 66 panic("unreachable") 67 } 68 69 func (m *mockRedoDMLManager) RemoveTable(span tablepb.Span) { 70 panic("unreachable") 71 } 72 73 func (m *mockRedoDMLManager) UpdateResolvedTs(ctx context.Context, 74 span tablepb.Span, resolvedTs uint64, 75 ) error { 76 m.mu.Lock() 77 defer m.mu.Unlock() 78 m.resolvedTss[span.TableID] = resolvedTs 79 return nil 80 } 81 82 func (m *mockRedoDMLManager) StartTable(span tablepb.Span, resolvedTs uint64) { 83 m.mu.Lock() 84 defer m.mu.Unlock() 85 m.resolvedTss[span.TableID] = resolvedTs 86 } 87 88 func (m *mockRedoDMLManager) GetResolvedTs(span tablepb.Span) model.Ts { 89 m.mu.Lock() 90 defer m.mu.Unlock() 91 return m.resolvedTss[span.TableID] 92 } 93 94 func (m *mockRedoDMLManager) EmitRowChangedEvents(ctx context.Context, 95 span tablepb.Span, releaseRowsMemory func(), rows ...*model.RowChangedEvent, 96 ) error { 97 m.mu.Lock() 98 defer m.mu.Unlock() 99 if _, ok := m.events[span.TableID]; !ok { 100 m.events[span.TableID] = make([]*model.RowChangedEvent, 0) 101 } 102 m.events[span.TableID] = append(m.events[span.TableID], rows...) 103 m.releaseRowsMemories[span.TableID] = releaseRowsMemory 104 105 return nil 106 } 107 108 func (m *mockRedoDMLManager) getEvents(span tablepb.Span) []*model.RowChangedEvent { 109 m.mu.Lock() 110 defer m.mu.Unlock() 111 return m.events[span.TableID] 112 } 113 114 func (m *mockRedoDMLManager) releaseRowsMemory(span tablepb.Span) { 115 m.mu.Lock() 116 defer m.mu.Unlock() 117 m.releaseRowsMemories[span.TableID]() 118 } 119 120 type redoLogAdvancerSuite struct { 121 suite.Suite 122 testChangefeedID model.ChangeFeedID 123 testSpan tablepb.Span 124 defaultTestMemQuota uint64 125 } 126 127 func (suite *redoLogAdvancerSuite) SetupSuite() { 128 requestMemSize = 256 129 maxUpdateIntervalSize = 512 130 suite.testChangefeedID = model.DefaultChangeFeedID("1") 131 suite.testSpan = spanz.TableIDToComparableSpan(1) 132 suite.defaultTestMemQuota = 1024 133 } 134 135 func (suite *redoLogAdvancerSuite) TearDownSuite() { 136 requestMemSize = defaultRequestMemSize 137 maxUpdateIntervalSize = defaultMaxUpdateIntervalSize 138 } 139 140 func TestRedoLogAdvancerSuite(t *testing.T) { 141 suite.Run(t, new(redoLogAdvancerSuite)) 142 } 143 144 func (suite *redoLogAdvancerSuite) genRedoTaskAndRedoDMLManager() (*redoTask, *mockRedoDMLManager) { 145 redoDMLManager := newMockRedoDMLManager() 146 wrapper, _ := createTableSinkWrapper(suite.testChangefeedID, suite.testSpan) 147 148 task := &redoTask{ 149 span: suite.testSpan, 150 tableSink: wrapper, 151 } 152 return task, redoDMLManager 153 } 154 155 func (suite *redoLogAdvancerSuite) genMemQuota(initMemQuota uint64) *memquota.MemQuota { 156 memoryQuota := memquota.NewMemQuota(suite.testChangefeedID, suite.defaultTestMemQuota, "sink") 157 memoryQuota.ForceAcquire(initMemQuota) 158 memoryQuota.AddTable(suite.testSpan) 159 return memoryQuota 160 } 161 162 func (suite *redoLogAdvancerSuite) TestNewRedoLogAdvancer() { 163 task, manager := suite.genRedoTaskAndRedoDMLManager() 164 memoryQuota := suite.genMemQuota(512) 165 defer memoryQuota.Close() 166 advancer := newRedoLogAdvancer(task, memoryQuota, 512, manager) 167 require.NotNil(suite.T(), advancer) 168 require.Equal(suite.T(), uint64(512), advancer.availableMem) 169 } 170 171 func (suite *redoLogAdvancerSuite) TestHasEnoughMem() { 172 memoryQuota := suite.genMemQuota(512) 173 defer memoryQuota.Close() 174 task, manager := suite.genRedoTaskAndRedoDMLManager() 175 advancer := newRedoLogAdvancer(task, memoryQuota, 512, manager) 176 require.NotNil(suite.T(), advancer) 177 require.True(suite.T(), advancer.hasEnoughMem()) 178 for i := 0; i < 6; i++ { 179 // 6 * 256 = 1536 > 1024 180 advancer.appendEvents([]*model.RowChangedEvent{{}}, 256) 181 } 182 require.False(suite.T(), advancer.hasEnoughMem(), 183 "hasEnoughMem should return false when usedMem > availableMem") 184 } 185 186 func (suite *redoLogAdvancerSuite) TestCleanup() { 187 memoryQuota := suite.genMemQuota(512) 188 defer memoryQuota.Close() 189 task, manager := suite.genRedoTaskAndRedoDMLManager() 190 advancer := newRedoLogAdvancer(task, memoryQuota, 512, manager) 191 require.NotNil(suite.T(), advancer) 192 require.Equal(suite.T(), uint64(512), advancer.availableMem) 193 require.Equal(suite.T(), uint64(0), advancer.usedMem) 194 require.Equal(suite.T(), uint64(512), memoryQuota.GetUsedBytes()) 195 advancer.cleanup() 196 require.Equal(suite.T(), uint64(0), memoryQuota.GetUsedBytes(), 197 "memory quota should be released after cleanup") 198 } 199 200 func (suite *redoLogAdvancerSuite) TestAppendEvents() { 201 memoryQuota := suite.genMemQuota(512) 202 defer memoryQuota.Close() 203 task, manager := suite.genRedoTaskAndRedoDMLManager() 204 advancer := newRedoLogAdvancer(task, memoryQuota, 512, manager) 205 require.NotNil(suite.T(), advancer) 206 require.True(suite.T(), advancer.hasEnoughMem()) 207 for i := 0; i < 2; i++ { 208 advancer.appendEvents([]*model.RowChangedEvent{{}}, 256) 209 } 210 require.Equal(suite.T(), uint64(512), advancer.pendingTxnSize) 211 require.Equal(suite.T(), uint64(512), advancer.usedMem) 212 require.False(suite.T(), advancer.hasEnoughMem()) 213 require.Len(suite.T(), advancer.events, 2) 214 } 215 216 func (suite *redoLogAdvancerSuite) TestTryMoveMoveToNextTxn() { 217 memoryQuota := suite.genMemQuota(512) 218 defer memoryQuota.Close() 219 task, manager := suite.genRedoTaskAndRedoDMLManager() 220 advancer := newRedoLogAdvancer(task, memoryQuota, 512, manager) 221 require.NotNil(suite.T(), advancer) 222 223 // Initial state. 224 require.Equal(suite.T(), uint64(0), advancer.lastTxnCommitTs) 225 require.Equal(suite.T(), uint64(0), advancer.currTxnCommitTs) 226 pos := sorter.Position{StartTs: 1, CommitTs: 3} 227 // Append 1 event with commit ts 1 228 advancer.appendEvents([]*model.RowChangedEvent{ 229 {CommitTs: 1}, 230 }, 256) 231 require.Equal(suite.T(), uint64(256), advancer.usedMem) 232 advancer.tryMoveToNextTxn(1, pos) 233 require.Equal(suite.T(), uint64(0), advancer.lastTxnCommitTs) 234 require.Equal(suite.T(), uint64(1), advancer.currTxnCommitTs) 235 236 // Append 2 events with commit ts 2 237 for i := 0; i < 2; i++ { 238 advancer.appendEvents([]*model.RowChangedEvent{ 239 {CommitTs: 2}, 240 }, 256) 241 } 242 require.Equal(suite.T(), uint64(768), advancer.usedMem) 243 require.Equal(suite.T(), uint64(0), advancer.lastTxnCommitTs) 244 require.Equal(suite.T(), uint64(1), advancer.currTxnCommitTs) 245 246 // Try to move to next txn. 247 advancer.tryMoveToNextTxn(2, pos) 248 require.Equal(suite.T(), uint64(1), advancer.lastTxnCommitTs) 249 require.Equal(suite.T(), uint64(2), advancer.currTxnCommitTs) 250 251 // Set pos to a commit fence 252 pos = sorter.Position{ 253 StartTs: 2, 254 CommitTs: 3, 255 } 256 // Try to move to next txn. 257 advancer.tryMoveToNextTxn(2, pos) 258 require.Equal(suite.T(), uint64(2), advancer.lastTxnCommitTs) 259 require.Equal(suite.T(), uint64(2), advancer.currTxnCommitTs) 260 } 261 262 func (suite *redoLogAdvancerSuite) TestAdvance() { 263 ctx, cancel := context.WithCancel(context.Background()) 264 defer cancel() 265 memoryQuota := suite.genMemQuota(768) 266 defer memoryQuota.Close() 267 task, manager := suite.genRedoTaskAndRedoDMLManager() 268 advancer := newRedoLogAdvancer(task, memoryQuota, 768, manager) 269 require.NotNil(suite.T(), advancer) 270 271 pos := sorter.Position{StartTs: 1, CommitTs: 3} 272 // 1. append 1 event with commit ts 1 273 advancer.appendEvents([]*model.RowChangedEvent{ 274 {CommitTs: 1}, 275 }, 256) 276 require.Equal(suite.T(), uint64(256), advancer.usedMem) 277 advancer.tryMoveToNextTxn(1, pos) 278 279 // 2. append 2 events with commit ts 2 280 for i := 0; i < 2; i++ { 281 advancer.appendEvents([]*model.RowChangedEvent{ 282 {CommitTs: 2}, 283 }, 256) 284 } 285 advancer.tryMoveToNextTxn(2, pos) 286 287 require.Equal(suite.T(), uint64(768), advancer.pendingTxnSize) 288 err := advancer.advance(ctx) 289 require.NoError(suite.T(), err) 290 291 require.Len(suite.T(), manager.getEvents(suite.testSpan), 3) 292 require.Equal(suite.T(), uint64(1), manager.GetResolvedTs(suite.testSpan)) 293 require.Equal(suite.T(), uint64(0), advancer.pendingTxnSize) 294 require.Equal(suite.T(), uint64(768), memoryQuota.GetUsedBytes()) 295 manager.releaseRowsMemory(suite.testSpan) 296 require.Equal(suite.T(), uint64(0), memoryQuota.GetUsedBytes(), 297 "memory quota should be released after releaseRowsMemory is called") 298 } 299 300 func (suite *redoLogAdvancerSuite) TestTryAdvanceWhenExceedAvailableMem() { 301 ctx, cancel := context.WithCancel(context.Background()) 302 defer cancel() 303 memoryQuota := suite.genMemQuota(768) 304 defer memoryQuota.Close() 305 task, manager := suite.genRedoTaskAndRedoDMLManager() 306 advancer := newRedoLogAdvancer(task, memoryQuota, 768, manager) 307 require.NotNil(suite.T(), advancer) 308 309 pos := sorter.Position{StartTs: 1, CommitTs: 2} 310 // 1. append 1 event with commit ts 2 311 advancer.appendEvents([]*model.RowChangedEvent{ 312 {CommitTs: 2}, 313 }, 256) 314 require.Equal(suite.T(), uint64(256), advancer.usedMem) 315 advancer.tryMoveToNextTxn(2, pos) 316 317 // 2. append 3 events with commit ts 3 318 for i := 0; i < 3; i++ { 319 advancer.appendEvents([]*model.RowChangedEvent{ 320 {CommitTs: 3}, 321 }, 256) 322 } 323 require.Equal(suite.T(), uint64(1024), advancer.usedMem) 324 pos = sorter.Position{StartTs: 2, CommitTs: 3} 325 advancer.tryMoveToNextTxn(3, pos) 326 327 require.Equal(suite.T(), uint64(1024), advancer.pendingTxnSize) 328 require.Equal(suite.T(), uint64(768), memoryQuota.GetUsedBytes()) 329 // 3. Try advance with txn is finished. 330 err := advancer.tryAdvanceAndAcquireMem( 331 ctx, 332 false, 333 true, 334 ) 335 require.NoError(suite.T(), err) 336 require.Equal(suite.T(), uint64(1024), memoryQuota.GetUsedBytes(), 337 "Memory quota should be force acquired when exceed available memory.", 338 ) 339 340 require.Len(suite.T(), manager.getEvents(suite.testSpan), 4) 341 require.Equal(suite.T(), uint64(3), manager.GetResolvedTs(suite.testSpan)) 342 require.Equal(suite.T(), uint64(0), advancer.pendingTxnSize) 343 manager.releaseRowsMemory(suite.testSpan) 344 require.Equal(suite.T(), uint64(0), memoryQuota.GetUsedBytes(), 345 "memory quota should be released after releaseRowsMemory is called") 346 } 347 348 func (suite *redoLogAdvancerSuite) TestTryAdvanceWhenReachTheMaxUpdateIntSizeAndTxnNotFinished() { 349 ctx, cancel := context.WithCancel(context.Background()) 350 defer cancel() 351 memoryQuota := suite.genMemQuota(768) 352 defer memoryQuota.Close() 353 task, manager := suite.genRedoTaskAndRedoDMLManager() 354 advancer := newRedoLogAdvancer(task, memoryQuota, 768, manager) 355 require.NotNil(suite.T(), advancer) 356 357 pos := sorter.Position{StartTs: 1, CommitTs: 2} 358 // 1. append 1 event with commit ts 2 359 advancer.appendEvents([]*model.RowChangedEvent{ 360 {CommitTs: 2}, 361 }, 256) 362 require.Equal(suite.T(), uint64(256), advancer.usedMem) 363 advancer.tryMoveToNextTxn(2, pos) 364 365 // 2. append 2 events with commit ts 3 366 for i := 0; i < 2; i++ { 367 advancer.appendEvents([]*model.RowChangedEvent{ 368 {CommitTs: 3}, 369 }, 256) 370 } 371 require.Equal(suite.T(), uint64(768), advancer.usedMem) 372 pos = sorter.Position{StartTs: 1, CommitTs: 3} 373 advancer.tryMoveToNextTxn(3, pos) 374 375 // 3. Try advance with txn is not finished. 376 err := advancer.tryAdvanceAndAcquireMem( 377 ctx, 378 false, 379 false, 380 ) 381 require.NoError(suite.T(), err) 382 require.Len(suite.T(), manager.getEvents(suite.testSpan), 3) 383 require.Equal(suite.T(), uint64(2), manager.GetResolvedTs(suite.testSpan)) 384 require.Equal(suite.T(), uint64(0), advancer.pendingTxnSize) 385 manager.releaseRowsMemory(suite.testSpan) 386 require.Equal(suite.T(), uint64(256), memoryQuota.GetUsedBytes(), 387 "memory quota should be released after releaseRowsMemory is called") 388 } 389 390 func (suite *redoLogAdvancerSuite) TestFinish() { 391 ctx, cancel := context.WithCancel(context.Background()) 392 defer cancel() 393 memoryQuota := suite.genMemQuota(768) 394 defer memoryQuota.Close() 395 task, manager := suite.genRedoTaskAndRedoDMLManager() 396 advancer := newRedoLogAdvancer(task, memoryQuota, 768, manager) 397 require.NotNil(suite.T(), advancer) 398 399 pos := sorter.Position{StartTs: 1, CommitTs: 2} 400 // 1. append 1 event with commit ts 2 401 advancer.appendEvents([]*model.RowChangedEvent{ 402 {CommitTs: 2}, 403 }, 256) 404 require.Equal(suite.T(), uint64(256), advancer.usedMem) 405 advancer.tryMoveToNextTxn(2, pos) 406 407 // 2. append 2 events with commit ts 3 408 for i := 0; i < 2; i++ { 409 advancer.appendEvents([]*model.RowChangedEvent{ 410 {CommitTs: 3}, 411 }, 256) 412 } 413 require.Equal(suite.T(), uint64(768), advancer.usedMem) 414 pos = sorter.Position{StartTs: 1, CommitTs: 3} 415 advancer.tryMoveToNextTxn(3, pos) 416 417 require.Equal(suite.T(), uint64(2), advancer.lastTxnCommitTs) 418 require.Equal(suite.T(), uint64(3), advancer.currTxnCommitTs) 419 // 3. Try finish. 420 err := advancer.finish( 421 ctx, 422 pos, 423 ) 424 require.NoError(suite.T(), err) 425 426 // All events should be flushed and the last pos should be updated. 427 require.Equal(suite.T(), pos, advancer.lastPos) 428 require.Equal(suite.T(), uint64(3), advancer.lastTxnCommitTs) 429 require.Equal(suite.T(), uint64(3), advancer.currTxnCommitTs) 430 431 require.Len(suite.T(), manager.getEvents(suite.testSpan), 3) 432 require.Equal(suite.T(), uint64(3), manager.GetResolvedTs(suite.testSpan)) 433 require.Equal(suite.T(), uint64(0), advancer.pendingTxnSize) 434 manager.releaseRowsMemory(suite.testSpan) 435 require.Equal(suite.T(), uint64(0), memoryQuota.GetUsedBytes(), 436 "memory quota should be released after releaseRowsMemory is called") 437 } 438 439 func (suite *redoLogAdvancerSuite) TestTryAdvanceAndBlockAcquireWithSplitTxn() { 440 ctx, cancel := context.WithCancel(context.Background()) 441 defer cancel() 442 memoryQuota := suite.genMemQuota(768) 443 defer memoryQuota.Close() 444 task, manager := suite.genRedoTaskAndRedoDMLManager() 445 advancer := newRedoLogAdvancer(task, memoryQuota, 768, manager) 446 require.NotNil(suite.T(), advancer) 447 448 pos := sorter.Position{StartTs: 1, CommitTs: 2} 449 // 1. append 1 event with commit ts 2 450 advancer.appendEvents([]*model.RowChangedEvent{ 451 {CommitTs: 2}, 452 }, 256) 453 require.Equal(suite.T(), uint64(256), advancer.usedMem) 454 advancer.tryMoveToNextTxn(2, pos) 455 456 // 2. append 3 events with commit ts 3, this will exceed the memory quota. 457 for i := 0; i < 3; i++ { 458 advancer.appendEvents([]*model.RowChangedEvent{ 459 {CommitTs: 3}, 460 }, 256) 461 } 462 require.Equal(suite.T(), uint64(1024), advancer.usedMem) 463 pos = sorter.Position{StartTs: 1, CommitTs: 3} 464 advancer.tryMoveToNextTxn(3, pos) 465 466 // 3. Last pos is a commit fence. 467 advancer.lastPos = sorter.Position{ 468 StartTs: 2, 469 CommitTs: 3, 470 } 471 472 down := make(chan struct{}) 473 go func() { 474 // 4. Try advance and block acquire. 475 err := advancer.tryAdvanceAndAcquireMem( 476 ctx, 477 false, 478 false, 479 ) 480 require.ErrorIs(suite.T(), err, context.Canceled) 481 down <- struct{}{} 482 }() 483 484 // Wait all events are flushed. 485 require.Eventually(suite.T(), func() bool { 486 return len(manager.getEvents(suite.testSpan)) == 4 487 }, 5*time.Second, 10*time.Millisecond) 488 // After ack, abort the blocked acquire. 489 memoryQuota.Close() 490 <-down 491 }