Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
ee8cb6b
remove batch configuration from the stream level, and add size policy
3AceShowHand Jan 13, 2026
480045b
add batch capacity interface to the sink
3AceShowHand Jan 13, 2026
d6a3906
dispatcher expose sink method
3AceShowHand Jan 13, 2026
ea4c359
fix some dynamic stream
3AceShowHand Jan 13, 2026
64e5e27
add events
3AceShowHand Jan 13, 2026
64c783f
add more events
3AceShowHand Jan 13, 2026
a60ca8b
fix more code
3AceShowHand Jan 14, 2026
c60f623
adjust the code
3AceShowHand Jan 14, 2026
3611ef8
support config
3AceShowHand Jan 14, 2026
50a85fe
Adjust the batcher
3AceShowHand Jan 15, 2026
a794eae
adjust some batch configs
3AceShowHand Jan 15, 2026
3b6d851
add batch count and batch bytes configuration
3AceShowHand Jan 15, 2026
387ad33
log puller should works just like before, no behavior change
3AceShowHand Jan 15, 2026
f26babf
let the dispatcher manager determine batch counts and bytes
3AceShowHand Jan 15, 2026
d9b544c
remove get sink from the dispatcher service interfacE
3AceShowHand Jan 15, 2026
f0f949a
adjust code
3AceShowHand Jan 15, 2026
d66a3e4
adjust code
3AceShowHand Jan 16, 2026
18e7fa6
Add area level batch metrics
3AceShowHand Jan 16, 2026
a424789
adjust set
3AceShowHand Jan 16, 2026
7df217a
clear the event buf after used
3AceShowHand Jan 16, 2026
b26fd7a
adjust isFull logic
3AceShowHand Jan 16, 2026
ae12398
add more test
3AceShowHand Jan 16, 2026
b723afc
add more test
3AceShowHand Jan 17, 2026
91126f6
add more code
3AceShowHand Feb 3, 2026
db6738b
fix all code
3AceShowHand Feb 26, 2026
9a71b75
add more
3AceShowHand Feb 26, 2026
0e5c177
add more code
3AceShowHand Feb 27, 2026
d48f5ad
add more code
3AceShowHand Feb 27, 2026
2e09daa
Merge branch 'master' into batch-type-ds
3AceShowHand Mar 2, 2026
3a7d793
fix test
3AceShowHand Mar 2, 2026
f71af9a
fix a lot of code
3AceShowHand Mar 2, 2026
ebff397
add more code
3AceShowHand Mar 2, 2026
30e4f93
fix
3AceShowHand Mar 2, 2026
a829ae9
fix a lot of code
3AceShowHand Mar 3, 2026
7d93e10
add more
3AceShowHand Mar 3, 2026
e603509
Add more code
3AceShowHand Mar 4, 2026
2f1ddc8
Merge branch 'master' into batch-type-ds
3AceShowHand Mar 4, 2026
b7f7aea
fix format
3AceShowHand Mar 4, 2026
55ce609
adjust a lot of code
3AceShowHand Mar 4, 2026
bc53688
add mock sink
3AceShowHand Mar 4, 2026
dd35ba6
add more code
3AceShowHand Mar 4, 2026
2ba3dd8
add a lot of changes
3AceShowHand Mar 4, 2026
c051d99
add a lot of code
3AceShowHand Mar 4, 2026
5b47bd6
Adjust code a lot
3AceShowHand Mar 4, 2026
438d3aa
update test
3AceShowHand Mar 5, 2026
9aada3d
Merge branch 'master' into batch-type-ds
3AceShowHand Mar 6, 2026
cec71b2
Merge branch 'master' into batch-type-ds
3AceShowHand Mar 6, 2026
df3d505
fix the code
3AceShowHand Mar 6, 2026
9d5d463
Merge branch 'batch-type-ds' of https://github.com/3AceShowHand/ticdc…
3AceShowHand Mar 30, 2026
7a82da3
Merge branch 'master' into batch-type-ds
3AceShowHand Mar 30, 2026
163eba4
fix redo sink build
3AceShowHand Mar 30, 2026
224bebd
fix cases
3AceShowHand Mar 30, 2026
5e57c4c
add more unit test
3AceShowHand Mar 30, 2026
1ba1d26
simplify the batcher
3AceShowHand Mar 31, 2026
c13098f
add more code
3AceShowHand Mar 31, 2026
a9424d8
redo support config batch count
3AceShowHand Mar 31, 2026
ed7c260
clean up used buf
3AceShowHand Mar 31, 2026
1bcc008
update code
3AceShowHand Mar 31, 2026
4310e39
Merge branch 'master' into batch-type-ds
3AceShowHand Apr 1, 2026
75c82f6
fix all code
3AceShowHand Apr 1, 2026
5dbd0e4
Merge branch 'master' into batch-type-ds
3AceShowHand Apr 4, 2026
a561c89
Merge branch 'master' into batch-type-ds
3AceShowHand Apr 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 44 additions & 59 deletions downstreamadapter/dispatchermanager/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (

"github.com/golang/mock/gomock"
"github.com/pingcap/ticdc/downstreamadapter/dispatcher"
"github.com/pingcap/ticdc/downstreamadapter/sink/mock"
sinkmock "github.com/pingcap/ticdc/downstreamadapter/sink/mock"
"github.com/pingcap/ticdc/downstreamadapter/sink/redo"
"github.com/pingcap/ticdc/heartbeatpb"
"github.com/pingcap/ticdc/pkg/common"
Expand All @@ -34,112 +34,97 @@ func TestCheckpointTsMessageHandlerDeadlock(t *testing.T) {
Keyspace: "test-namespace",
Name: "test-changefeed",
}

checkpointTsMessage := NewCheckpointTsMessage(&heartbeatpb.CheckpointTsMessage{
ChangefeedID: changefeedID,
CheckpointTs: 12345,
})

handler := &CheckpointTsMessageHandler{}

t.Run("normal_operation", func(t *testing.T) {
t.Run("normalOperation", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

mockSink := mock.NewMockSink(ctrl)
mockSink.EXPECT().AddCheckpointTs(checkpointTsMessage.CheckpointTs).Times(1)

mockSink := sinkmock.NewMockSink(ctrl)
mockSink.EXPECT().AddCheckpointTs(uint64(12345)).Times(1)
dispatcherManager := &DispatcherManager{
sink: mockSink,
tableTriggerEventDispatcher: &dispatcher.EventDispatcher{},
}

done := make(chan bool, 1)
go func() {
blocking := handler.Handle(dispatcherManager, checkpointTsMessage)
require.False(t, blocking, "Handler should not return blocking=true")
done <- true
done <- handler.Handle(dispatcherManager, checkpointTsMessage)
}()

select {
case <-done:
case <-time.After(1 * time.Second):
t.Fatal("Normal operation took too long, unexpected")
case blocking := <-done:
require.False(t, blocking)
case <-time.After(time.Second):
t.Fatal("normal operation timed out")
}
})

t.Run("deadlock_scenario", func(t *testing.T) {
t.Run("deadlockScenario", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

mockSink := sinkmock.NewMockSink(ctrl)
blockCh := make(chan struct{})
deadlockMockSink := mock.NewMockSink(ctrl)
deadlockMockSink.EXPECT().AddCheckpointTs(checkpointTsMessage.CheckpointTs).Do(
func(uint64) {
<-blockCh
},
).Times(1)

deadlockDispatcherManager := &DispatcherManager{
sink: deadlockMockSink,
mockSink.EXPECT().AddCheckpointTs(uint64(12345)).DoAndReturn(func(uint64) {
<-blockCh
}).Times(1)

dispatcherManager := &DispatcherManager{
sink: mockSink,
tableTriggerEventDispatcher: &dispatcher.EventDispatcher{},
}

done := make(chan bool, 1)
go func() {
handler.Handle(deadlockDispatcherManager, checkpointTsMessage)
done <- true
done <- handler.Handle(dispatcherManager, checkpointTsMessage)
}()

select {
case <-done:
t.Fatal("Handler completed unexpectedly - deadlock was not reproduced")
case <-time.After(1 * time.Second):
t.Log("Successfully reproduced the deadlock: handler is blocked in AddCheckpointTs")
t.Fatal("handler completed unexpectedly, deadlock not reproduced")
case <-time.After(time.Second):
}

close(blockCh)

select {
case <-done:
case <-time.After(1 * time.Second):
t.Fatal("handler should resume once AddCheckpointTs unblocks")
case blocking := <-done:
require.False(t, blocking)
case <-time.After(time.Second):
t.Fatal("handler did not return after unblocking AddCheckpointTs")
}
})

t.Run("deadlock_resolve_scenario", func(t *testing.T) {
t.Run("deadlockResolveScenario", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

ctx, cancel := context.WithCancel(context.Background())
cancel()
mockSink := sinkmock.NewMockSink(ctrl)
sinkCtx, cancel := context.WithCancel(context.Background())
defer cancel()
mockSink.EXPECT().AddCheckpointTs(uint64(12345)).DoAndReturn(func(uint64) {
<-sinkCtx.Done()
}).Times(1)

deadlockMockSink := mock.NewMockSink(ctrl)
deadlockMockSink.EXPECT().AddCheckpointTs(checkpointTsMessage.CheckpointTs).Do(
func(uint64) {
select {
case <-ctx.Done():
return
case <-time.After(1 * time.Second):
t.Fatal("context cancellation should unblock AddCheckpointTs path")
}
},
).Times(1)

deadlockDispatcherManager := &DispatcherManager{
sink: deadlockMockSink,
dispatcherManager := &DispatcherManager{
sink: mockSink,
tableTriggerEventDispatcher: &dispatcher.EventDispatcher{},
}

done := make(chan bool, 1)
go func() {
handler.Handle(deadlockDispatcherManager, checkpointTsMessage)
done <- true
done <- handler.Handle(dispatcherManager, checkpointTsMessage)
}()

select {
case <-done:
t.Log("Handler completed normally")
case <-time.After(1 * time.Second):
t.Fatal("handler completed unexpectedly before cancel")
case <-time.After(200 * time.Millisecond):
}

cancel()
select {
case blocking := <-done:
require.False(t, blocking)
case <-time.After(time.Second):
t.Fatal("deadlock: handler is blocked in AddCheckpointTs")
}
})
Expand Down
2 changes: 2 additions & 0 deletions downstreamadapter/eventcollector/event_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,8 @@ func (c *EventCollector) PrepareAddDispatcher(
err := ds.AddPath(target.GetId(), stat, areaSetting)
if err != nil {
log.Warn("add dispatcher to dynamic stream failed", zap.Error(err))
stat.run()
return
}
stat.run()
}
Expand Down
3 changes: 2 additions & 1 deletion downstreamadapter/sink/mysql/sink.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type Sink struct {
cfg *mysql.Config
maxTxnRows int
bdrMode bool

// enableActiveActive enables active-active replication behaviors in the MySQL-class sink.
enableActiveActive bool

Expand Down Expand Up @@ -440,7 +441,7 @@ func (s *Sink) CleanupRemovedChangefeed() error {
}

func (s *Sink) BatchCount() int {
return s.maxTxnRows * len(s.dmlWriter)
return s.cfg.MaxTxnRow * len(s.dmlWriter)
}

func (s *Sink) BatchBytes() int {
Expand Down
7 changes: 4 additions & 3 deletions downstreamadapter/sink/mysql/sink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func MysqlSinkForTest() (*Sink, sqlmock.Sqlmock) {

func MysqlSinkForTestWithMaxTxnRows(maxTxnRows int) (*Sink, sqlmock.Sqlmock) {
ctx, sink, mock := getMysqlSink()
sink.cfg.MaxTxnRow = maxTxnRows
sink.maxTxnRows = maxTxnRows
go sink.Run(ctx)
return sink, mock
Expand Down Expand Up @@ -334,12 +335,12 @@ func TestMysqlSinkFlushLargeBatchEvent(t *testing.T) {
require.Equal(t, int32(10), dmlEvent1.Length, "First event should contain 10 rows")
require.Equal(t, int32(10), dmlEvent2.Length, "Second event should contain 10 rows")

// Set up mock expectations for DDL
mock.ExpectExec("BEGIN;INSERT INTO `test`.`t` (`id`,`name`) VALUES (?,?),(?,?),(?,?),(?,?),(?,?),(?,?),(?,?),(?,?),(?,?),(?,?);COMMIT;").
// Each 10-row event is split into 4+4+2 rows when maxTxnRows is 4.
mock.ExpectExec("BEGIN;INSERT INTO `test`.`t` (`id`,`name`) VALUES (?,?),(?,?),(?,?),(?,?);INSERT INTO `test`.`t` (`id`,`name`) VALUES (?,?),(?,?),(?,?),(?,?);INSERT INTO `test`.`t` (`id`,`name`) VALUES (?,?),(?,?);COMMIT;").
WithArgs(1, "test1", 2, "test2", 3, "test3", 4, "test4", 5, "test5", 6, "test6", 7, "test7", 8, "test8", 9, "test9", 10, "test10").
WillReturnResult(sqlmock.NewResult(1, 1))

mock.ExpectExec("BEGIN;INSERT INTO `test`.`t` (`id`,`name`) VALUES (?,?),(?,?),(?,?),(?,?),(?,?),(?,?),(?,?),(?,?),(?,?),(?,?);COMMIT;").
mock.ExpectExec("BEGIN;INSERT INTO `test`.`t` (`id`,`name`) VALUES (?,?),(?,?),(?,?),(?,?);INSERT INTO `test`.`t` (`id`,`name`) VALUES (?,?),(?,?),(?,?),(?,?);INSERT INTO `test`.`t` (`id`,`name`) VALUES (?,?),(?,?);COMMIT;").
WithArgs(11, "test11", 12, "test12", 13, "test13", 14, "test14", 15, "test15", 16, "test16", 17, "test17", 18, "test18", 19, "test19", 20, "test20").
WillReturnResult(sqlmock.NewResult(1, 1))

Expand Down
8 changes: 4 additions & 4 deletions logservice/logpuller/region_event_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func TestHandleEventEntryEventOutOfOrder(t *testing.T) {
advanceResolvedTs: advanceResolvedTs,
advanceInterval: 0,
}
ds.AddPath(subID, subSpan, dynstream.AreaSettings{})
ds.AddPath(subID, subSpan)

worker := &regionRequestWorker{
requestCache: &requestCache{},
Expand Down Expand Up @@ -235,7 +235,7 @@ func TestHandleResolvedTs(t *testing.T) {
advanceResolvedTs: advanceResolvedTs,
advanceInterval: 0,
}
ds.AddPath(subID1, subSpan, dynstream.AreaSettings{})
ds.AddPath(subID1, subSpan)
state1.region.subscribedSpan = subSpan
state1.region.lockedRangeState = &regionlock.LockedRangeState{}
state1.setInitialized()
Expand All @@ -259,7 +259,7 @@ func TestHandleResolvedTs(t *testing.T) {
advanceResolvedTs: advanceResolvedTs,
advanceInterval: 0,
}
ds.AddPath(subID2, subSpan, dynstream.AreaSettings{})
ds.AddPath(subID2, subSpan)
state2.region.subscribedSpan = subSpan
state2.region.lockedRangeState = &regionlock.LockedRangeState{}
state2.setInitialized()
Expand All @@ -283,7 +283,7 @@ func TestHandleResolvedTs(t *testing.T) {
advanceResolvedTs: advanceResolvedTs,
advanceInterval: 0,
}
ds.AddPath(subID3, subSpan, dynstream.AreaSettings{})
ds.AddPath(subID3, subSpan)
state3.region.subscribedSpan = subSpan
state3.region.lockedRangeState = &regionlock.LockedRangeState{}
state3.updateResolvedTs(8)
Expand Down
5 changes: 5 additions & 0 deletions pkg/config/consistent.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ func (c *ConsistentConfig) validateAndAdjust(enableIOCheck bool) error {
fmt.Sprintf("The consistent.meta-flush-interval:%d must be equal or greater than %d",
util.GetOrZero(c.MetaFlushIntervalInMs), redo.MinFlushIntervalInMs))
}
if c.EventCollectorBatchCount != nil && *c.EventCollectorBatchCount <= 0 {
return errors.ErrInvalidReplicaConfig.FastGenByArgs(
fmt.Sprintf("The consistent.event-collector-batch-count:%d must be larger than 0",
*c.EventCollectorBatchCount))
}

if c.EventCollectorBatchCount != nil {
if *c.EventCollectorBatchCount < 0 {
Expand Down
6 changes: 3 additions & 3 deletions pkg/metrics/dynamic_stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,11 @@ var (

func initDynamicStreamMetrics(registry *prometheus.Registry) {
registry.MustRegister(DynamicStreamMemoryUsage)
registry.MustRegister(DynamicStreamBatchCount)
registry.MustRegister(DynamicStreamBatchBytes)
registry.MustRegister(DynamicStreamBatchDuration)
registry.MustRegister(DynamicStreamEventChanSize)
registry.MustRegister(DynamicStreamPendingQueueLen)
registry.MustRegister(DynamicStreamAddPathNum)
registry.MustRegister(DynamicStreamRemovePathNum)
registry.MustRegister(DynamicStreamBatchCount)
registry.MustRegister(DynamicStreamBatchBytes)
registry.MustRegister(DynamicStreamBatchDuration)
}
1 change: 1 addition & 0 deletions scripts/generate-mock.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ fi
"$MOCKGEN" -source pkg/sink/codec/simple/marshaller.go -destination pkg/sink/codec/simple/mock/marshaller.go
"$MOCKGEN" -source pkg/keyspace/keyspace_manager.go -destination pkg/keyspace/keyspace_manager_mock.go -package keyspace
"$MOCKGEN" -source pkg/txnutil/gc/gc_manager.go -destination pkg/txnutil/gc/gc_manager_mock.go -package gc
"$MOCKGEN" -source downstreamadapter/sink/sink.go -destination downstreamadapter/sink/mock/sink_mock.go -package mock
"$MOCKGEN" -source pkg/txnutil/gc/gc_client.go -destination pkg/txnutil/gc/gc_client_mock.go -package gc
"$MOCKGEN" -source pkg/redo/writer/writer.go -destination pkg/redo/writer/writer_mock.go -package writer
"$MOCKGEN" -source downstreamadapter/sink/sink.go -destination downstreamadapter/sink/mock/sink_mock.go -package mock
3 changes: 3 additions & 0 deletions utils/dynstream/stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,9 @@ func (s *stream[A, P, T, D, H]) handleLoop() {
s.option.handleWait.Wait()
}

// Variables below will be used in the Loop below.
// Declared here to avoid repeated allocation.
// todo: shall we preallocate the event buff and path here ?
// 1. Drain the eventChan to pendingQueue.
// 2. Pop events from the eventQueue and handle them.
Loop:
Expand Down
Loading