github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/sink/dmlsink/mq/worker_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 mq 15 16 import ( 17 "context" 18 "sync" 19 "testing" 20 "time" 21 22 "github.com/pingcap/tidb/pkg/parser/mysql" 23 "github.com/pingcap/tiflow/cdc/entry" 24 "github.com/pingcap/tiflow/cdc/model" 25 "github.com/pingcap/tiflow/cdc/sink/dmlsink" 26 "github.com/pingcap/tiflow/cdc/sink/dmlsink/mq/dmlproducer" 27 "github.com/pingcap/tiflow/cdc/sink/metrics" 28 "github.com/pingcap/tiflow/cdc/sink/tablesink/state" 29 "github.com/pingcap/tiflow/pkg/config" 30 "github.com/pingcap/tiflow/pkg/sink" 31 "github.com/pingcap/tiflow/pkg/sink/codec" 32 "github.com/pingcap/tiflow/pkg/sink/codec/builder" 33 "github.com/pingcap/tiflow/pkg/sink/codec/common" 34 "github.com/stretchr/testify/require" 35 ) 36 37 func newBatchEncodeWorker(ctx context.Context, t *testing.T) (*worker, dmlproducer.DMLProducer) { 38 id := model.DefaultChangeFeedID("test") 39 // 200 is about the size of a rowEvent change. 40 encoderConfig := common.NewConfig(config.ProtocolOpen).WithMaxMessageBytes(200).WithChangefeedID(id) 41 encoderBuilder, err := builder.NewRowEventEncoderBuilder(context.Background(), encoderConfig) 42 require.NoError(t, err) 43 p := dmlproducer.NewDMLMockProducer(context.Background(), id, nil, nil, nil, nil) 44 require.NoError(t, err) 45 encoderConcurrency := 4 46 statistics := metrics.NewStatistics(ctx, id, sink.RowSink) 47 cfg := config.GetDefaultReplicaConfig() 48 cfg.Sink.EncoderConcurrency = &encoderConcurrency 49 encoderGroup := codec.NewEncoderGroup(cfg.Sink, encoderBuilder, id) 50 return newWorker(id, config.ProtocolOpen, p, encoderGroup, statistics), p 51 } 52 53 func newNonBatchEncodeWorker(ctx context.Context, t *testing.T) (*worker, dmlproducer.DMLProducer) { 54 id := model.DefaultChangeFeedID("test") 55 // 300 is about the size of a rowEvent change. 56 encoderConfig := common.NewConfig(config.ProtocolCanalJSON).WithMaxMessageBytes(300).WithChangefeedID(id) 57 encoderBuilder, err := builder.NewRowEventEncoderBuilder(context.Background(), encoderConfig) 58 require.NoError(t, err) 59 p := dmlproducer.NewDMLMockProducer(context.Background(), id, nil, nil, nil, nil) 60 require.NoError(t, err) 61 encoderConcurrency := 4 62 statistics := metrics.NewStatistics(ctx, id, sink.RowSink) 63 cfg := config.GetDefaultReplicaConfig() 64 cfg.Sink.EncoderConcurrency = &encoderConcurrency 65 encoderGroup := codec.NewEncoderGroup(cfg.Sink, encoderBuilder, id) 66 return newWorker(id, config.ProtocolOpen, p, encoderGroup, statistics), p 67 } 68 69 func TestNonBatchEncode_SendMessages(t *testing.T) { 70 helper := entry.NewSchemaTestHelper(t) 71 defer helper.Close() 72 73 sql := `create table test.t(a varchar(255) primary key)` 74 job := helper.DDL2Job(sql) 75 tableInfo := model.WrapTableInfo(0, "test", 1, job.BinlogInfo.TableInfo) 76 77 ctx, cancel := context.WithCancel(context.Background()) 78 defer cancel() 79 80 worker, p := newNonBatchEncodeWorker(ctx, t) 81 defer worker.close() 82 83 key := model.TopicPartitionKey{ 84 Topic: "test", 85 Partition: 1, 86 } 87 row := &model.RowChangedEvent{ 88 CommitTs: 1, 89 TableInfo: tableInfo, 90 Columns: model.Columns2ColumnDatas([]*model.Column{{Name: "a", Value: "aa"}}, tableInfo), 91 } 92 tableStatus := state.TableSinkSinking 93 94 count := 512 95 total := 0 96 expected := 0 97 for i := 0; i < count; i++ { 98 expected += i 99 100 bit := i 101 worker.msgChan.In() <- mqEvent{ 102 key: key, 103 rowEvent: &dmlsink.RowChangeCallbackableEvent{ 104 Event: row, 105 Callback: func() { 106 total += bit 107 }, 108 SinkState: &tableStatus, 109 }, 110 } 111 } 112 113 var wg sync.WaitGroup 114 wg.Add(1) 115 go func() { 116 defer wg.Done() 117 _ = worker.run(ctx) 118 }() 119 120 mp := p.(*dmlproducer.MockDMLProducer) 121 require.Eventually(t, func() bool { 122 return len(mp.GetAllEvents()) == count 123 }, 3*time.Second, 100*time.Millisecond) 124 125 require.Eventually(t, func() bool { 126 return total == expected 127 }, 3*time.Second, 10*time.Millisecond) 128 cancel() 129 130 wg.Wait() 131 } 132 133 func TestBatchEncode_Batch(t *testing.T) { 134 t.Parallel() 135 136 ctx, cancel := context.WithCancel(context.Background()) 137 defer cancel() 138 worker, _ := newBatchEncodeWorker(ctx, t) 139 defer worker.close() 140 key := model.TopicPartitionKey{ 141 Topic: "test", 142 Partition: 1, 143 } 144 tableStatus := state.TableSinkSinking 145 cols := []*model.Column{{Name: "col1", Type: 1, Value: "aa"}} 146 tableInfo := model.BuildTableInfo("a", "b", cols, nil) 147 row := &model.RowChangedEvent{ 148 CommitTs: 1, 149 TableInfo: tableInfo, 150 Columns: model.Columns2ColumnDatas(cols, tableInfo), 151 } 152 153 for i := 0; i < 512; i++ { 154 worker.msgChan.In() <- mqEvent{ 155 key: key, 156 rowEvent: &dmlsink.RowChangeCallbackableEvent{ 157 Event: row, 158 Callback: func() {}, 159 SinkState: &tableStatus, 160 }, 161 } 162 } 163 164 // Test batching returns when the events count is equal to the batch size. 165 batch := make([]mqEvent, 512) 166 endIndex, err := worker.batch(ctx, batch, time.Minute) 167 require.NoError(t, err) 168 require.Equal(t, 512, endIndex) 169 } 170 171 func TestBatchEncode_Group(t *testing.T) { 172 t.Parallel() 173 174 key1 := model.TopicPartitionKey{ 175 Topic: "test", 176 Partition: 1, 177 } 178 key2 := model.TopicPartitionKey{ 179 Topic: "test", 180 Partition: 2, 181 } 182 key3 := model.TopicPartitionKey{ 183 Topic: "test1", 184 Partition: 2, 185 } 186 ctx, cancel := context.WithCancel(context.Background()) 187 defer cancel() 188 worker, _ := newBatchEncodeWorker(ctx, t) 189 defer worker.close() 190 191 tableStatus := state.TableSinkSinking 192 193 tableInfo1 := model.BuildTableInfo("a", "b", []*model.Column{{Name: "col1", Type: 1}}, nil) 194 tableInfo2 := model.BuildTableInfo("aa", "bb", []*model.Column{{Name: "col1", Type: 1}}, nil) 195 tableInfo3 := model.BuildTableInfo("aaa", "bbb", []*model.Column{{Name: "col1", Type: 1}}, nil) 196 events := []mqEvent{ 197 { 198 rowEvent: &dmlsink.RowChangeCallbackableEvent{ 199 Event: &model.RowChangedEvent{ 200 CommitTs: 1, 201 TableInfo: tableInfo1, 202 Columns: model.Columns2ColumnDatas([]*model.Column{{Name: "col1", Value: "aa"}}, tableInfo1), 203 }, 204 Callback: func() {}, 205 SinkState: &tableStatus, 206 }, 207 key: key1, 208 }, 209 { 210 rowEvent: &dmlsink.RowChangeCallbackableEvent{ 211 Event: &model.RowChangedEvent{ 212 CommitTs: 2, 213 TableInfo: tableInfo1, 214 Columns: model.Columns2ColumnDatas([]*model.Column{{Name: "col1", Value: "bb"}}, tableInfo1), 215 }, 216 Callback: func() {}, 217 SinkState: &tableStatus, 218 }, 219 key: key1, 220 }, 221 { 222 rowEvent: &dmlsink.RowChangeCallbackableEvent{ 223 Event: &model.RowChangedEvent{ 224 CommitTs: 3, 225 TableInfo: tableInfo1, 226 Columns: model.Columns2ColumnDatas([]*model.Column{{Name: "col1", Value: "cc"}}, tableInfo1), 227 }, 228 Callback: func() {}, 229 SinkState: &tableStatus, 230 }, 231 key: key1, 232 }, 233 { 234 rowEvent: &dmlsink.RowChangeCallbackableEvent{ 235 Event: &model.RowChangedEvent{ 236 CommitTs: 2, 237 TableInfo: tableInfo2, 238 Columns: model.Columns2ColumnDatas([]*model.Column{{Name: "col1", Value: "bb"}}, tableInfo2), 239 }, 240 Callback: func() {}, 241 SinkState: &tableStatus, 242 }, 243 key: key2, 244 }, 245 { 246 rowEvent: &dmlsink.RowChangeCallbackableEvent{ 247 Event: &model.RowChangedEvent{ 248 CommitTs: 2, 249 TableInfo: tableInfo3, 250 Columns: model.Columns2ColumnDatas([]*model.Column{{Name: "col1", Value: "bb"}}, tableInfo3), 251 }, 252 Callback: func() {}, 253 SinkState: &tableStatus, 254 }, 255 key: key3, 256 }, 257 } 258 259 partitionedRows := worker.group(events) 260 require.Len(t, partitionedRows, 3) 261 require.Len(t, partitionedRows[key1], 3) 262 // We must ensure that the sequence is not broken. 263 require.LessOrEqual( 264 t, 265 partitionedRows[key1][0].Event.GetCommitTs(), partitionedRows[key1][1].Event.GetCommitTs(), 266 partitionedRows[key1][2].Event.GetCommitTs(), 267 ) 268 require.Len(t, partitionedRows[key2], 1) 269 require.Len(t, partitionedRows[key3], 1) 270 } 271 272 func TestBatchEncode_GroupWhenTableStopping(t *testing.T) { 273 helper := entry.NewSchemaTestHelper(t) 274 defer helper.Close() 275 276 sql := `create table test.t(a varchar(255) primary key)` 277 job := helper.DDL2Job(sql) 278 tableInfo := model.WrapTableInfo(0, "test", 1, job.BinlogInfo.TableInfo) 279 280 key1 := model.TopicPartitionKey{ 281 Topic: "test", 282 Partition: 1, 283 } 284 key2 := model.TopicPartitionKey{ 285 Topic: "test", 286 Partition: 2, 287 } 288 ctx, cancel := context.WithCancel(context.Background()) 289 defer cancel() 290 worker, _ := newBatchEncodeWorker(ctx, t) 291 defer worker.close() 292 replicatingStatus := state.TableSinkSinking 293 stoppedStatus := state.TableSinkStopping 294 tableInfo2 := model.BuildTableInfo("a", "b", []*model.Column{{Name: "col1", Type: 1}}, nil) 295 events := []mqEvent{ 296 { 297 rowEvent: &dmlsink.RowChangeCallbackableEvent{ 298 Event: &model.RowChangedEvent{ 299 CommitTs: 1, 300 TableInfo: tableInfo, 301 Columns: model.Columns2ColumnDatas([]*model.Column{{Name: "a", Value: "aa"}}, tableInfo), 302 }, 303 Callback: func() {}, 304 SinkState: &replicatingStatus, 305 }, 306 key: key1, 307 }, 308 { 309 rowEvent: &dmlsink.RowChangeCallbackableEvent{ 310 Event: &model.RowChangedEvent{ 311 CommitTs: 2, 312 TableInfo: tableInfo2, 313 Columns: model.Columns2ColumnDatas([]*model.Column{{Name: "col1", Value: "bb"}}, tableInfo2), 314 }, 315 Callback: func() {}, 316 SinkState: &replicatingStatus, 317 }, 318 key: key1, 319 }, 320 { 321 rowEvent: &dmlsink.RowChangeCallbackableEvent{ 322 Event: &model.RowChangedEvent{ 323 CommitTs: 3, 324 TableInfo: tableInfo2, 325 Columns: model.Columns2ColumnDatas([]*model.Column{{Name: "col1", Value: "cc"}}, tableInfo2), 326 }, 327 Callback: func() {}, 328 SinkState: &stoppedStatus, 329 }, 330 key: key2, 331 }, 332 } 333 334 partitionedRows := worker.group(events) 335 require.Len(t, partitionedRows, 1) 336 require.Len(t, partitionedRows[key1], 2) 337 // We must ensure that the sequence is not broken. 338 require.LessOrEqual( 339 t, 340 partitionedRows[key1][0].Event.GetCommitTs(), 341 partitionedRows[key1][1].Event.GetCommitTs(), 342 ) 343 } 344 345 func TestBatchEncode_SendMessages(t *testing.T) { 346 key1 := model.TopicPartitionKey{ 347 Topic: "test", 348 Partition: 1, 349 } 350 key2 := model.TopicPartitionKey{ 351 Topic: "test", 352 Partition: 2, 353 } 354 key3 := model.TopicPartitionKey{ 355 Topic: "test1", 356 Partition: 2, 357 } 358 359 tableStatus := state.TableSinkSinking 360 ctx, cancel := context.WithCancel(context.Background()) 361 defer cancel() 362 worker, p := newBatchEncodeWorker(ctx, t) 363 defer worker.close() 364 365 helper := entry.NewSchemaTestHelper(t) 366 defer helper.Close() 367 368 sql := `create table test.t(a varchar(255) primary key)` 369 job := helper.DDL2Job(sql) 370 tableInfo := model.WrapTableInfo(0, "test", 1, job.BinlogInfo.TableInfo) 371 372 events := []mqEvent{ 373 { 374 rowEvent: &dmlsink.RowChangeCallbackableEvent{ 375 Event: &model.RowChangedEvent{ 376 CommitTs: 1, 377 TableInfo: tableInfo, 378 Columns: model.Columns2ColumnDatas([]*model.Column{{Name: "a", Value: "aa"}}, tableInfo), 379 }, 380 Callback: func() {}, 381 SinkState: &tableStatus, 382 }, 383 key: key1, 384 }, 385 { 386 rowEvent: &dmlsink.RowChangeCallbackableEvent{ 387 Event: &model.RowChangedEvent{ 388 CommitTs: 2, 389 TableInfo: tableInfo, 390 Columns: model.Columns2ColumnDatas([]*model.Column{{Name: "a", Value: "bb"}}, tableInfo), 391 }, 392 Callback: func() {}, 393 SinkState: &tableStatus, 394 }, 395 key: key1, 396 }, 397 { 398 rowEvent: &dmlsink.RowChangeCallbackableEvent{ 399 Event: &model.RowChangedEvent{ 400 CommitTs: 3, 401 TableInfo: tableInfo, 402 Columns: model.Columns2ColumnDatas([]*model.Column{{Name: "a", Value: "cc"}}, tableInfo), 403 }, 404 Callback: func() {}, 405 SinkState: &tableStatus, 406 }, 407 key: key1, 408 }, 409 { 410 rowEvent: &dmlsink.RowChangeCallbackableEvent{ 411 Event: &model.RowChangedEvent{ 412 CommitTs: 2, 413 TableInfo: tableInfo, 414 Columns: model.Columns2ColumnDatas([]*model.Column{{Name: "a", Value: "bb"}}, tableInfo), 415 }, 416 Callback: func() {}, 417 SinkState: &tableStatus, 418 }, 419 key: key2, 420 }, 421 { 422 rowEvent: &dmlsink.RowChangeCallbackableEvent{ 423 Event: &model.RowChangedEvent{ 424 CommitTs: 2, 425 TableInfo: tableInfo, 426 Columns: model.Columns2ColumnDatas([]*model.Column{{Name: "a", Value: "bb"}}, tableInfo), 427 }, 428 Callback: func() {}, 429 SinkState: &tableStatus, 430 }, 431 key: key3, 432 }, 433 { 434 rowEvent: &dmlsink.RowChangeCallbackableEvent{ 435 Event: &model.RowChangedEvent{ 436 CommitTs: 3, 437 TableInfo: tableInfo, 438 Columns: model.Columns2ColumnDatas([]*model.Column{{Name: "a", Value: "bb"}}, tableInfo), 439 }, 440 Callback: func() {}, 441 SinkState: &tableStatus, 442 }, 443 key: key3, 444 }, 445 } 446 447 var wg sync.WaitGroup 448 wg.Add(1) 449 go func() { 450 defer wg.Done() 451 _ = worker.run(ctx) 452 }() 453 454 for _, event := range events { 455 worker.msgChan.In() <- event 456 } 457 458 mp := p.(*dmlproducer.MockDMLProducer) 459 require.Eventually(t, func() bool { 460 return len(mp.GetAllEvents()) == len(events) 461 }, 3*time.Second, 100*time.Millisecond) 462 require.Eventually(t, func() bool { 463 return len(mp.GetEvents(key1.Topic, key1.Partition)) == 3 464 }, 3*time.Second, 100*time.Millisecond) 465 require.Eventually(t, func() bool { 466 return len(mp.GetEvents(key2.Topic, key2.Partition)) == 1 467 }, 3*time.Second, 100*time.Millisecond) 468 require.Eventually(t, func() bool { 469 return len(mp.GetEvents(key3.Topic, key3.Partition)) == 2 470 }, 3*time.Second, 100*time.Millisecond) 471 472 cancel() 473 wg.Wait() 474 } 475 476 func TestBatchEncodeWorker_Abort(t *testing.T) { 477 t.Parallel() 478 479 ctx, cancel := context.WithCancel(context.Background()) 480 worker, _ := newBatchEncodeWorker(ctx, t) 481 defer worker.close() 482 483 var wg sync.WaitGroup 484 wg.Add(1) 485 go func() { 486 defer wg.Done() 487 err := worker.run(ctx) 488 require.Error(t, context.Canceled, err) 489 }() 490 491 cancel() 492 wg.Wait() 493 } 494 495 func TestNonBatchEncode_SendMessagesWhenTableStopping(t *testing.T) { 496 key1 := model.TopicPartitionKey{ 497 Topic: "test", 498 Partition: 1, 499 } 500 key2 := model.TopicPartitionKey{ 501 Topic: "test", 502 Partition: 2, 503 } 504 ctx, cancel := context.WithCancel(context.Background()) 505 defer cancel() 506 worker, p := newNonBatchEncodeWorker(ctx, t) 507 defer worker.close() 508 replicatingStatus := state.TableSinkSinking 509 stoppedStatus := state.TableSinkStopping 510 511 helper := entry.NewSchemaTestHelper(t) 512 defer helper.Close() 513 514 sql := `create table test.t(a varchar(255) primary key)` 515 job := helper.DDL2Job(sql) 516 tableInfo := model.WrapTableInfo(0, "test", 1, job.BinlogInfo.TableInfo) 517 518 events := []mqEvent{ 519 { 520 rowEvent: &dmlsink.RowChangeCallbackableEvent{ 521 Event: &model.RowChangedEvent{ 522 CommitTs: 1, 523 TableInfo: tableInfo, 524 Columns: model.Columns2ColumnDatas([]*model.Column{{Name: "a", Type: mysql.TypeVarchar, Value: "aa"}}, tableInfo), 525 }, 526 Callback: func() {}, 527 SinkState: &replicatingStatus, 528 }, 529 key: key1, 530 }, 531 { 532 rowEvent: &dmlsink.RowChangeCallbackableEvent{ 533 Event: &model.RowChangedEvent{ 534 CommitTs: 2, 535 TableInfo: tableInfo, 536 Columns: model.Columns2ColumnDatas([]*model.Column{{Name: "a", Type: mysql.TypeVarchar, Value: "bb"}}, tableInfo), 537 }, 538 Callback: func() {}, 539 SinkState: &replicatingStatus, 540 }, 541 key: key1, 542 }, 543 { 544 rowEvent: &dmlsink.RowChangeCallbackableEvent{ 545 Event: &model.RowChangedEvent{ 546 CommitTs: 3, 547 TableInfo: tableInfo, 548 Columns: model.Columns2ColumnDatas([]*model.Column{{Name: "a", Type: mysql.TypeVarchar, Value: "cc"}}, tableInfo), 549 }, 550 Callback: func() {}, 551 SinkState: &stoppedStatus, 552 }, 553 key: key2, 554 }, 555 } 556 for _, e := range events { 557 worker.msgChan.In() <- e 558 } 559 560 var wg sync.WaitGroup 561 wg.Add(1) 562 go func() { 563 defer wg.Done() 564 _ = worker.run(ctx) 565 }() 566 mp := p.(*dmlproducer.MockDMLProducer) 567 require.Eventually(t, func() bool { 568 return len(mp.GetAllEvents()) == 2 569 }, 3*time.Second, 100*time.Millisecond) 570 cancel() 571 wg.Wait() 572 } 573 574 func TestNonBatchEncodeWorker_Abort(t *testing.T) { 575 ctx, cancel := context.WithCancel(context.Background()) 576 worker, _ := newBatchEncodeWorker(ctx, t) 577 defer worker.close() 578 579 var wg sync.WaitGroup 580 wg.Add(1) 581 go func() { 582 defer wg.Done() 583 err := worker.run(ctx) 584 require.Error(t, context.Canceled, err) 585 }() 586 587 cancel() 588 wg.Wait() 589 }