github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/sink/tablesink/table_sink_impl_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 tablesink 15 16 import ( 17 "sort" 18 "sync" 19 "testing" 20 "time" 21 22 "github.com/pingcap/tiflow/cdc/model" 23 "github.com/pingcap/tiflow/cdc/sink/dmlsink" 24 "github.com/pingcap/tiflow/cdc/sink/tablesink/state" 25 "github.com/pingcap/tiflow/pkg/pdutil" 26 "github.com/pingcap/tiflow/pkg/sink" 27 "github.com/pingcap/tiflow/pkg/spanz" 28 "github.com/prometheus/client_golang/prometheus" 29 "github.com/stretchr/testify/require" 30 ) 31 32 // Assert EventSink implementation 33 var _ dmlsink.EventSink[*model.SingleTableTxn] = (*mockEventSink)(nil) 34 35 type mockEventSink struct { 36 dead chan struct{} 37 events []*dmlsink.TxnCallbackableEvent 38 } 39 40 func (m *mockEventSink) WriteEvents(rows ...*dmlsink.TxnCallbackableEvent) error { 41 m.events = append(m.events, rows...) 42 return nil 43 } 44 45 func (m *mockEventSink) Close() { 46 close(m.dead) 47 } 48 49 func (m *mockEventSink) Dead() <-chan struct{} { 50 return m.dead 51 } 52 53 func (m *mockEventSink) Scheme() string { 54 return sink.BlackHoleScheme 55 } 56 57 // acknowledge the txn events by call the callback function. 58 func (m *mockEventSink) acknowledge(commitTs uint64) []*dmlsink.TxnCallbackableEvent { 59 var droppedEvents []*dmlsink.TxnCallbackableEvent 60 i := sort.Search(len(m.events), func(i int) bool { 61 return m.events[i].Event.GetCommitTs() > commitTs 62 }) 63 if i == 0 { 64 return droppedEvents 65 } 66 ackedEvents := m.events[:i] 67 for _, event := range ackedEvents { 68 if event.GetTableSinkState() == state.TableSinkSinking { 69 event.Callback() 70 } else { 71 event.Callback() 72 droppedEvents = append(droppedEvents, event) 73 } 74 } 75 76 // Remove the acked events from the event buffer. 77 m.events = append( 78 make([]*dmlsink.TxnCallbackableEvent, 79 0, 80 len(m.events[i:])), 81 m.events[i:]..., 82 ) 83 84 return droppedEvents 85 } 86 87 func getTestRows() []*model.RowChangedEvent { 88 tableInfo := &model.TableInfo{ 89 TableName: model.TableName{ 90 Schema: "test", 91 Table: "t1", 92 TableID: 1, 93 IsPartition: false, 94 }, 95 } 96 97 return []*model.RowChangedEvent{ 98 { 99 TableInfo: tableInfo, 100 CommitTs: 101, 101 StartTs: 98, 102 }, 103 { 104 TableInfo: tableInfo, 105 CommitTs: 102, 106 StartTs: 99, 107 }, 108 { 109 TableInfo: tableInfo, 110 CommitTs: 102, 111 StartTs: 100, 112 }, 113 { 114 TableInfo: tableInfo, 115 CommitTs: 102, 116 StartTs: 100, 117 }, 118 { 119 TableInfo: tableInfo, 120 CommitTs: 103, 121 StartTs: 101, 122 }, 123 { 124 TableInfo: tableInfo, 125 CommitTs: 103, 126 StartTs: 101, 127 }, 128 { 129 TableInfo: tableInfo, 130 CommitTs: 104, 131 StartTs: 102, 132 }, 133 { 134 TableInfo: tableInfo, 135 CommitTs: 105, 136 StartTs: 103, 137 // Batch1 138 SplitTxn: true, 139 }, 140 { 141 TableInfo: tableInfo, 142 CommitTs: 105, 143 StartTs: 103, 144 }, 145 { 146 TableInfo: tableInfo, 147 CommitTs: 105, 148 StartTs: 103, 149 }, 150 { 151 TableInfo: tableInfo, 152 CommitTs: 105, 153 StartTs: 103, 154 // Batch2 155 SplitTxn: true, 156 }, 157 { 158 TableInfo: tableInfo, 159 CommitTs: 105, 160 StartTs: 103, 161 }, 162 } 163 } 164 165 func TestNewEventTableSink(t *testing.T) { 166 t.Parallel() 167 168 sink := &mockEventSink{dead: make(chan struct{})} 169 tb := New[*model.SingleTableTxn]( 170 model.DefaultChangeFeedID("1"), spanz.TableIDToComparableSpan(1), model.Ts(0), 171 sink, &dmlsink.TxnEventAppender{}, 172 pdutil.NewClock4Test(), 173 prometheus.NewCounter(prometheus.CounterOpts{}), 174 prometheus.NewHistogram(prometheus.HistogramOpts{})) 175 176 require.Equal(t, model.NewResolvedTs(0), tb.maxResolvedTs, "maxResolvedTs should start from 0") 177 require.NotNil(t, sink, tb.backendSink, "backendSink should be set") 178 require.NotNil(t, tb.progressTracker, "progressTracker should be set") 179 require.NotNil(t, tb.eventAppender, "eventAppender should be set") 180 require.Equal(t, 0, len(tb.eventBuffer), "eventBuffer should be empty") 181 require.Equal(t, state.TableSinkSinking, tb.state, "table sink should be sinking") 182 } 183 184 func TestAppendRowChangedEvents(t *testing.T) { 185 t.Parallel() 186 187 sink := &mockEventSink{dead: make(chan struct{})} 188 tb := New[*model.SingleTableTxn]( 189 model.DefaultChangeFeedID("1"), spanz.TableIDToComparableSpan(1), model.Ts(0), 190 sink, &dmlsink.TxnEventAppender{}, 191 pdutil.NewClock4Test(), 192 prometheus.NewCounter(prometheus.CounterOpts{}), 193 prometheus.NewHistogram(prometheus.HistogramOpts{})) 194 195 tb.AppendRowChangedEvents(getTestRows()...) 196 require.Len(t, tb.eventBuffer, 7, "txn event buffer should have 7 txns") 197 } 198 199 func TestUpdateResolvedTs(t *testing.T) { 200 t.Parallel() 201 202 sink := &mockEventSink{dead: make(chan struct{})} 203 tb := New[*model.SingleTableTxn]( 204 model.DefaultChangeFeedID("1"), spanz.TableIDToComparableSpan(1), model.Ts(0), 205 sink, &dmlsink.TxnEventAppender{}, 206 pdutil.NewClock4Test(), 207 prometheus.NewCounter(prometheus.CounterOpts{}), 208 prometheus.NewHistogram(prometheus.HistogramOpts{})) 209 210 tb.AppendRowChangedEvents(getTestRows()...) 211 // No event will be flushed. 212 err := tb.UpdateResolvedTs(model.NewResolvedTs(100)) 213 require.Nil(t, err) 214 require.Equal(t, model.NewResolvedTs(100), tb.maxResolvedTs, "maxResolvedTs should be updated") 215 require.Len(t, tb.eventBuffer, 7, "txn event buffer should have 7 txns") 216 require.Len(t, sink.events, 0, "no event should not be flushed") 217 218 // One event will be flushed. 219 err = tb.UpdateResolvedTs(model.NewResolvedTs(101)) 220 require.Nil(t, err) 221 require.Equal(t, model.NewResolvedTs(101), tb.maxResolvedTs, "maxResolvedTs should be updated") 222 require.Len(t, tb.eventBuffer, 6, "txn event buffer should have 6 txns") 223 require.Len(t, sink.events, 1, "one event should be flushed") 224 225 // Two events will be flushed. 226 err = tb.UpdateResolvedTs(model.NewResolvedTs(102)) 227 require.Nil(t, err) 228 require.Equal(t, model.NewResolvedTs(102), tb.maxResolvedTs, "maxResolvedTs should be updated") 229 require.Len(t, tb.eventBuffer, 4, "txn event buffer should have 4 txns") 230 require.Len(t, sink.events, 3, "two events should be flushed") 231 232 // Same resolved ts will not be flushed. 233 err = tb.UpdateResolvedTs(model.NewResolvedTs(102)) 234 require.Nil(t, err) 235 require.Equal( 236 t, 237 model.NewResolvedTs(102), 238 tb.maxResolvedTs, 239 "maxResolvedTs should not be updated", 240 ) 241 require.Len(t, tb.eventBuffer, 4, "txn event buffer should still have 4 txns") 242 require.Len(t, sink.events, 3, "no event should be flushed") 243 244 // All events will be flushed. 245 err = tb.UpdateResolvedTs(model.NewResolvedTs(105)) 246 require.Nil(t, err) 247 require.Equal(t, model.NewResolvedTs(105), tb.maxResolvedTs, "maxResolvedTs should be updated") 248 require.Len(t, tb.eventBuffer, 0, "txn event buffer should be empty") 249 require.Len(t, sink.events, 7, "all events should be flushed") 250 } 251 252 func TestGetCheckpointTs(t *testing.T) { 253 t.Parallel() 254 255 sink := &mockEventSink{dead: make(chan struct{})} 256 tb := New[*model.SingleTableTxn]( 257 model.DefaultChangeFeedID("1"), spanz.TableIDToComparableSpan(1), model.Ts(0), 258 sink, &dmlsink.TxnEventAppender{}, 259 pdutil.NewClock4Test(), 260 prometheus.NewCounter(prometheus.CounterOpts{}), 261 prometheus.NewHistogram(prometheus.HistogramOpts{})) 262 263 tb.AppendRowChangedEvents(getTestRows()...) 264 require.Equal(t, model.NewResolvedTs(0), tb.GetCheckpointTs(), "checkpointTs should be 0") 265 require.Equal(t, tb.lastSyncedTs.getLastSyncedTs(), uint64(0), "lastSyncedTs should be not updated") 266 267 // One event will be flushed. 268 err := tb.UpdateResolvedTs(model.NewResolvedTs(101)) 269 require.Nil(t, err) 270 require.Equal(t, model.NewResolvedTs(0), tb.GetCheckpointTs(), "checkpointTs should be 0") 271 sink.acknowledge(101) 272 require.Equal(t, model.NewResolvedTs(101), tb.GetCheckpointTs(), "checkpointTs should be 101") 273 require.Equal(t, tb.lastSyncedTs.getLastSyncedTs(), uint64(101), "lastSyncedTs should be the same as the flushed event") 274 275 // Flush all events. 276 err = tb.UpdateResolvedTs(model.NewResolvedTs(105)) 277 require.Nil(t, err) 278 require.Equal(t, model.NewResolvedTs(101), tb.GetCheckpointTs(), "checkpointTs should be 101") 279 require.Equal(t, tb.lastSyncedTs.getLastSyncedTs(), uint64(101), "lastSyncedTs should be not updated") 280 281 // Only acknowledge some events. 282 sink.acknowledge(102) 283 require.Equal( 284 t, 285 model.NewResolvedTs(101), 286 tb.GetCheckpointTs(), 287 "checkpointTs should still be 101", 288 ) 289 require.Equal(t, tb.lastSyncedTs.getLastSyncedTs(), uint64(102), "lastSyncedTs should be updated") 290 291 // Ack all events. 292 sink.acknowledge(105) 293 require.Equal(t, model.NewResolvedTs(105), tb.GetCheckpointTs(), "checkpointTs should be 105") 294 require.Equal(t, tb.lastSyncedTs.getLastSyncedTs(), uint64(105), "lastSyncedTs should be updated") 295 } 296 297 func TestClose(t *testing.T) { 298 t.Parallel() 299 300 sink := &mockEventSink{dead: make(chan struct{})} 301 tb := New[*model.SingleTableTxn]( 302 model.DefaultChangeFeedID("1"), spanz.TableIDToComparableSpan(1), model.Ts(0), 303 sink, &dmlsink.TxnEventAppender{}, 304 pdutil.NewClock4Test(), 305 prometheus.NewCounter(prometheus.CounterOpts{}), 306 prometheus.NewHistogram(prometheus.HistogramOpts{})) 307 308 tb.AppendRowChangedEvents(getTestRows()...) 309 err := tb.UpdateResolvedTs(model.NewResolvedTs(105)) 310 require.Nil(t, err) 311 require.Len(t, sink.events, 7, "all events should be flushed") 312 var wg sync.WaitGroup 313 wg.Add(1) 314 go func() { 315 tb.Close() 316 wg.Done() 317 }() 318 require.Eventually(t, func() bool { 319 return state.TableSinkStopping == tb.state.Load() 320 }, time.Second, time.Millisecond*10, "table should be stopping") 321 droppedEvents := sink.acknowledge(105) 322 require.Len(t, droppedEvents, 7, "all events should be dropped") 323 wg.Wait() 324 require.Eventually(t, func() bool { 325 return state.TableSinkStopped == tb.state.Load() 326 }, time.Second, time.Millisecond*10, "table should be stopped") 327 } 328 329 func TestOperationsAfterClose(t *testing.T) { 330 t.Parallel() 331 332 sink := &mockEventSink{dead: make(chan struct{})} 333 tb := New[*model.SingleTableTxn]( 334 model.DefaultChangeFeedID("1"), spanz.TableIDToComparableSpan(1), model.Ts(0), 335 sink, &dmlsink.TxnEventAppender{}, 336 pdutil.NewClock4Test(), 337 prometheus.NewCounter(prometheus.CounterOpts{}), 338 prometheus.NewHistogram(prometheus.HistogramOpts{})) 339 340 require.True(t, tb.AsyncClose()) 341 342 tb.AppendRowChangedEvents(getTestRows()...) 343 err := tb.UpdateResolvedTs(model.NewResolvedTs(105)) 344 require.Nil(t, err) 345 } 346 347 func TestCloseCancellable(t *testing.T) { 348 t.Parallel() 349 350 sink := &mockEventSink{dead: make(chan struct{})} 351 tb := New[*model.SingleTableTxn]( 352 model.DefaultChangeFeedID("1"), spanz.TableIDToComparableSpan(1), model.Ts(0), 353 sink, &dmlsink.TxnEventAppender{}, 354 pdutil.NewClock4Test(), 355 prometheus.NewCounter(prometheus.CounterOpts{}), 356 prometheus.NewHistogram(prometheus.HistogramOpts{})) 357 358 tb.AppendRowChangedEvents(getTestRows()...) 359 err := tb.UpdateResolvedTs(model.NewResolvedTs(105)) 360 require.Nil(t, err) 361 require.Len(t, sink.events, 7, "all events should be flushed") 362 363 go func() { 364 time.Sleep(time.Millisecond * 10) 365 sink.Close() 366 }() 367 368 var wg sync.WaitGroup 369 wg.Add(1) 370 go func() { 371 tb.Close() 372 wg.Done() 373 }() 374 wg.Wait() 375 require.Eventually(t, func() bool { 376 return state.TableSinkStopped == tb.state.Load() 377 }, time.Second, time.Millisecond*10, "table should be stopped") 378 } 379 380 func TestCloseReentrant(t *testing.T) { 381 t.Parallel() 382 383 sink := &mockEventSink{dead: make(chan struct{})} 384 tb := New[*model.SingleTableTxn]( 385 model.DefaultChangeFeedID("1"), spanz.TableIDToComparableSpan(1), model.Ts(0), 386 sink, &dmlsink.TxnEventAppender{}, 387 pdutil.NewClock4Test(), 388 prometheus.NewCounter(prometheus.CounterOpts{}), 389 prometheus.NewHistogram(prometheus.HistogramOpts{})) 390 391 tb.AppendRowChangedEvents(getTestRows()...) 392 err := tb.UpdateResolvedTs(model.NewResolvedTs(105)) 393 require.Nil(t, err) 394 require.Len(t, sink.events, 7, "all events should be flushed") 395 396 go func() { 397 time.Sleep(time.Millisecond * 10) 398 sink.Close() 399 }() 400 401 var wg sync.WaitGroup 402 wg.Add(1) 403 go func() { 404 tb.Close() 405 wg.Done() 406 }() 407 wg.Wait() 408 require.Eventually(t, func() bool { 409 return state.TableSinkStopped == tb.state.Load() 410 }, 5*time.Second, time.Millisecond*10, "table should be stopped") 411 tb.Close() 412 } 413 414 // TestCheckpointTsFrozenWhenStopping make sure wo do not update checkpoint 415 // ts when it is stopping. 416 func TestCheckpointTsFrozenWhenStopping(t *testing.T) { 417 t.Parallel() 418 419 sink := &mockEventSink{dead: make(chan struct{})} 420 tb := New[*model.SingleTableTxn]( 421 model.DefaultChangeFeedID("1"), spanz.TableIDToComparableSpan(1), model.Ts(0), 422 sink, &dmlsink.TxnEventAppender{}, 423 pdutil.NewClock4Test(), 424 prometheus.NewCounter(prometheus.CounterOpts{}), 425 prometheus.NewHistogram(prometheus.HistogramOpts{})) 426 427 tb.AppendRowChangedEvents(getTestRows()...) 428 err := tb.UpdateResolvedTs(model.NewResolvedTs(105)) 429 require.Nil(t, err) 430 require.Len(t, sink.events, 7, "all events should be flushed") 431 432 // Table sink close should return even if callbacks are not called, 433 // because the backend sink is closed. 434 sink.Close() 435 tb.Close() 436 437 require.Equal(t, state.TableSinkStopped, tb.state.Load()) 438 439 currentTs := tb.GetCheckpointTs() 440 sink.acknowledge(105) 441 require.Equal(t, currentTs, tb.GetCheckpointTs(), "checkpointTs should not be updated") 442 require.Equal(t, tb.lastSyncedTs.getLastSyncedTs(), uint64(105), "lastSyncedTs should not change") 443 }