Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
80 changes: 28 additions & 52 deletions cmd/kafka-consumer/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -609,77 +609,53 @@ func (w *writer) appendMessage2Group(message *common.DMLMessage, progress *parti
table = message.Table
commitTs = message.GetCommitTs()
)
globalWatermark := w.globalWatermark()
if commitTs < globalWatermark {
log.Warn("DML event fallback row, since less than the global watermark, ignore it",
zap.Int64("tableID", tableID), zap.Int32("partition", progress.partition),
zap.Uint64("commitTs", commitTs), zap.Any("offset", offset),
zap.Uint64("globalWatermark", globalWatermark),
zap.Uint64("partitionWatermark", progress.watermark),
zap.Any("watermarkOffset", progress.watermarkOffset),
zap.String("schema", schema), zap.String("table", table),
zap.Stringer("eventType", message.RowType),
zap.Any("protocol", w.protocol), zap.Bool("enableTableAcrossNodes", w.enableTableAcrossNodes))
return
}

group := progress.eventsGroup[tableID]
if group == nil {
group = util.NewEventsGroup(progress.partition, tableID)
progress.eventsGroup[tableID] = group
}
message = w.messageWithPartitionCheck(message, progress.partition, offset)
group.AppendMessage(message)
if commitTs < progress.watermark {
log.Warn("DML Event fallback row, since less than the partition watermark, ignore it",
log.Warn("DML event fallback row, since less than the partition watermark, append it and sort before flush",
zap.Int64("tableID", tableID), zap.Int32("partition", group.Partition),
zap.Uint64("commitTs", commitTs), zap.Any("offset", offset),
zap.Uint64("watermark", progress.watermark), zap.Any("watermarkOffset", progress.watermarkOffset),
zap.String("schema", schema), zap.String("table", table))
zap.Uint64("globalWatermark", globalWatermark),
zap.String("schema", schema), zap.String("table", table),
zap.Stringer("eventType", message.RowType),
zap.Any("protocol", w.protocol), zap.Bool("enableTableAcrossNodes", w.enableTableAcrossNodes))
return
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if commitTs >= group.HighWatermark {
message = w.messageWithPartitionCheck(message, progress.partition, offset)
group.AppendMessage(message, false)
log.Debug("DML event append to the group",
zap.Int32("partition", group.Partition), zap.Any("offset", offset),
zap.Uint64("commitTs", commitTs), zap.Uint64("HighWatermark", group.HighWatermark),
zap.String("schema", schema), zap.String("table", table), zap.Int64("tableID", tableID),
zap.Stringer("eventType", message.RowType))
return
}
if w.enableTableAcrossNodes {
log.Warn("DML events fallback, but enableTableAcrossNodes is true, still append it",
zap.Int32("partition", group.Partition), zap.Any("offset", offset),
zap.Uint64("commitTs", commitTs), zap.Uint64("HighWatermark", group.HighWatermark),
zap.String("schema", schema), zap.String("table", table), zap.Int64("tableID", tableID),
zap.Stringer("eventType", message.RowType))
group.AppendMessage(w.messageWithPartitionCheck(message, progress.partition, offset), true)
return
}
switch w.protocol {
case config.ProtocolSimple:
// simple protocol set the table id for all row message, it can be known which table the row message belongs to,
// also consider the table partition.
// open protocol set the partition table id if the table is partitioned.
// for normal table, the table id is generated by the fake table id generator by using schema and table name.
// so one event group for one normal table or one table partition, replayed messages can be ignored.
log.Warn("DML event fallback row, since less than the group high watermark, ignore it",
zap.Int32("partition", progress.partition), zap.Any("offset", offset),
zap.Uint64("commitTs", commitTs), zap.Uint64("highWatermark", group.HighWatermark),
zap.Any("partitionWatermark", progress.watermark), zap.Any("watermarkOffset", progress.watermarkOffset),
zap.String("schema", schema), zap.String("table", table), zap.Int64("tableID", tableID),
zap.Stringer("eventType", message.RowType),
// zap.Any("columns", row.Columns), zap.Any("preColumns", row.PreColumns),
zap.Any("protocol", w.protocol))
case config.ProtocolCanalJSON, config.ProtocolOpen, config.ProtocolAvro,
config.ProtocolDebezium, config.ProtocolDebeziumAvro:
// for partition table, these protocols cannot assign physical table id to each dml message,
// we cannot distinguish whether it's a real fallback event or not, still append it.
if w.partitionTableAccessor.IsPartitionTable(schema, table) {
log.Warn("DML events fallback, but the table is a partition table, still append it",
zap.Int32("partition", group.Partition), zap.Any("offset", offset),
zap.Uint64("commitTs", commitTs), zap.Uint64("highWatermark", group.HighWatermark),
zap.String("schema", schema), zap.String("table", table), zap.Int64("tableID", tableID),
zap.Stringer("eventType", message.RowType), zap.Any("protocol", w.protocol))
group.AppendMessage(w.messageWithPartitionCheck(message, progress.partition, offset), true)
return
}
log.Warn("DML event fallback row, since less than the group high watermark, ignore it",
zap.Int32("partition", progress.partition), zap.Any("offset", offset),
zap.Uint64("commitTs", commitTs), zap.Uint64("HighWatermark", group.HighWatermark),
zap.Any("partitionWatermark", progress.watermark), zap.Any("watermarkOffset", progress.watermarkOffset),
zap.String("schema", schema), zap.String("table", table), zap.Int64("tableID", tableID),
zap.Stringer("eventType", message.RowType),
// zap.Any("columns", row.Columns), zap.Any("preColumns", row.PreColumns),
zap.Any("protocol", w.protocol))
default:
log.Panic("unknown protocol", zap.Any("protocol", w.protocol))
}
log.Warn("DML event commit ts fallback, append it and sort before flush",
zap.Int32("partition", progress.partition), zap.Any("offset", offset),
zap.Uint64("commitTs", commitTs), zap.Uint64("highWatermark", group.HighWatermark),
zap.Any("partitionWatermark", progress.watermark), zap.Any("watermarkOffset", progress.watermarkOffset),
zap.String("schema", schema), zap.String("table", table), zap.Int64("tableID", tableID),
zap.Stringer("eventType", message.RowType),
zap.Any("protocol", w.protocol), zap.Bool("enableTableAcrossNodes", w.enableTableAcrossNodes))
}

func openDB(ctx context.Context, dsn string) (*sql.DB, error) {
Expand Down
129 changes: 129 additions & 0 deletions cmd/kafka-consumer/writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,96 @@ func TestWriterWrite_handlesOutOfOrderDDLsByCommitTs(t *testing.T) {
require.Equal(t, "CREATE TABLE `common_1`.`a` (`a` BIGINT PRIMARY KEY,`b` INT)", w.ddlList[0].Query)
}

func TestWriterWrite_sortsOutOfOrderDMLByWatermark(t *testing.T) {
ctx := context.Background()
ctrl := gomock.NewController(t)
s := sinkmock.NewMockSink(ctrl)
flushedCommitTs := make([]uint64, 0)
s.EXPECT().AddDMLEvent(gomock.Any()).Do(func(event *commonEvent.DMLEvent) {
flushedCommitTs = append(flushedCommitTs, event.GetCommitTs())
event.PostFlush()
}).Times(2)

replicaCfg := config.GetDefaultReplicaConfig()
eventRouter, err := eventrouter.NewEventRouter(replicaCfg.Sink, "test-topic", false, false)
require.NoError(t, err)

p := &partitionProgress{
partition: 0,
eventsGroup: make(map[int64]*util.EventsGroup),
watermark: 0,
}
w := &writer{
progresses: []*partitionProgress{p},
mysqlSink: s,
eventRouter: eventRouter,
protocol: config.ProtocolOpen,
}

w.appendMessage2Group(newDMLMessageForWriterTest(20), p, kafka.Offset(1))
w.appendMessage2Group(newDMLMessageForWriterTest(10), p, kafka.Offset(2))
w.appendMessage2Group(newDMLMessageForWriterTest(20), p, kafka.Offset(3))

p.watermark = 20
require.True(t, w.Write(ctx, codeccommon.MessageTypeResolved))
require.Equal(t, []uint64{10, 20}, flushedCommitTs)
}

func TestWriteMessageIgnoresFallbackDMLBelowGlobalWatermark(t *testing.T) {
ctx := context.Background()
ctrl := gomock.NewController(t)
s := sinkmock.NewMockSink(ctrl)
s.EXPECT().AddDMLEvent(gomock.Any()).Times(0)

progress := &partitionProgress{
partition: 0,
eventsGroup: make(map[int64]*util.EventsGroup),
watermark: 20,
decoder: &singleDMLDecoder{message: newDMLMessageForWriterTest(10)},
}
w := &writer{
progresses: []*partitionProgress{progress},
mysqlSink: s,
protocol: config.ProtocolOpen,
maxBatchSize: 64,
maxMessageBytes: 1,
}

needCommit := w.WriteMessage(ctx, &kafka.Message{
TopicPartition: kafka.TopicPartition{Partition: 0, Offset: kafka.Offset(10)},
})

require.False(t, needCommit)
require.Nil(t, progress.eventsGroup[1])
}

func TestAppendMessageKeepsFallbackDMLAboveGlobalWatermark(t *testing.T) {
replicaCfg := config.GetDefaultReplicaConfig()
eventRouter, err := eventrouter.NewEventRouter(replicaCfg.Sink, "test-topic", false, false)
require.NoError(t, err)

progress := &partitionProgress{
partition: 0,
eventsGroup: make(map[int64]*util.EventsGroup),
watermark: 20,
}
w := &writer{
progresses: []*partitionProgress{
progress,
{partition: 1, watermark: 5},
},
eventRouter: eventRouter,
protocol: config.ProtocolOpen,
}

w.appendMessage2Group(newDMLMessageForWriterTest(10), progress, kafka.Offset(10))

require.NotNil(t, progress.eventsGroup[1])
resolved := progress.eventsGroup[1].ResolveInto(20, nil)
require.Len(t, resolved, 1)
require.Equal(t, uint64(10), resolved[0].GetCommitTs())
}

func TestOnDDLMarksRoutedCreateTableLikePartitionTableForAvro(t *testing.T) {
replicaCfg := config.GetDefaultReplicaConfig()
eventRouter, err := eventrouter.NewEventRouter(replicaCfg.Sink, "test-topic", false, true)
Expand Down Expand Up @@ -368,3 +458,42 @@ func TestAppendRow2GroupKeepsDebeziumPartitionTableFallback(t *testing.T) {
})
}
}

func newDMLMessageForWriterTest(commitTs uint64) *codeccommon.DMLMessage {
return codeccommon.NewDMLMessage(1, "test", "t", commitTs, common.RowTypeUpdate, func() *commonEvent.DMLEvent {
return &commonEvent.DMLEvent{
PhysicalTableID: 1,
CommitTs: commitTs,
RowTypes: []common.RowType{common.RowTypeUpdate},
Rows: chunk.NewChunkWithCapacity(nil, 0),
TableInfo: &common.TableInfo{
TableName: common.TableName{Schema: "test", Table: "t", TableID: 1},
},
}
})
}

type singleDMLDecoder struct {
message *codeccommon.DMLMessage
consumed bool
}

func (d *singleDMLDecoder) AddKeyValue(_, _ []byte) {
}

func (d *singleDMLDecoder) HasNext() (codeccommon.MessageType, bool) {
return codeccommon.MessageTypeRow, !d.consumed
}

func (d *singleDMLDecoder) NextResolvedEvent() uint64 {
return 0
}

func (d *singleDMLDecoder) NextDMLMessage() *codeccommon.DMLMessage {
d.consumed = true
return d.message
}

func (d *singleDMLDecoder) NextDDLEvent() *commonEvent.DDLEvent {
return nil
}
60 changes: 26 additions & 34 deletions cmd/pulsar-consumer/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -498,55 +498,47 @@ func (w *writer) appendMessage2Group(message *common.DMLMessage, progress *parti
table = message.Table
commitTs = message.GetCommitTs()
)
globalWatermark := w.globalWatermark()
if commitTs < globalWatermark {
log.Warn("DML event fallback row, since less than the global watermark, ignore it",
zap.Int64("tableID", tableID), zap.Int32("partition", progress.partition),
zap.Uint64("commitTs", commitTs),
zap.Uint64("globalWatermark", globalWatermark),
zap.Uint64("partitionWatermark", progress.watermark),
zap.String("schema", schema), zap.String("table", table),
zap.Stringer("eventType", message.RowType),
zap.Any("protocol", w.protocol), zap.Bool("enableTableAcrossNodes", w.enableTableAcrossNodes))
return
}

group := progress.eventsGroup[tableID]
if group == nil {
group = util.NewEventsGroup(progress.partition, tableID)
progress.eventsGroup[tableID] = group
}
group.AppendMessage(message)
if commitTs < progress.watermark {
log.Warn("DML Event fallback row, since less than the partition watermark, ignore it",
log.Warn("DML event fallback row, since less than the partition watermark, append it and sort before flush",
zap.Int64("tableID", tableID), zap.Int32("partition", group.Partition),
zap.Uint64("commitTs", commitTs), zap.Uint64("watermark", progress.watermark),
zap.String("schema", schema), zap.String("table", table))
zap.Uint64("globalWatermark", globalWatermark),
zap.String("schema", schema), zap.String("table", table),
zap.Stringer("eventType", message.RowType),
zap.Any("protocol", w.protocol), zap.Bool("enableTableAcrossNodes", w.enableTableAcrossNodes))
return
}
if commitTs >= group.HighWatermark {
group.AppendMessage(message, false)
log.Debug("DML event append to the group",
zap.Uint64("commitTs", commitTs), zap.Uint64("highWatermark", group.HighWatermark),
zap.String("schema", schema), zap.String("table", table), zap.Int64("tableID", tableID),
zap.Stringer("eventType", message.RowType))
return
}
if w.enableTableAcrossNodes {
log.Warn("DML events fallback, but enableTableAcrossNodes is true, still append it",
zap.Uint64("commitTs", commitTs), zap.Uint64("highWatermark", group.HighWatermark),
zap.String("schema", schema), zap.String("table", table), zap.Int64("tableID", tableID),
zap.Stringer("eventType", message.RowType))
group.AppendMessage(message, true)
return
}
switch w.protocol {
case config.ProtocolCanalJSON:
// for partition table, the canal-json message cannot assign physical table id to each dml message,
// we cannot distinguish whether it's a real fallback event or not, still append it.
isPartitionTable := w.partitionTableAccessor != nil &&
w.partitionTableAccessor.IsPartitionTable(schema, table)
if isPartitionTable {
log.Warn("DML events fallback, but it's canal-json and partition table, still append it",
zap.Uint64("commitTs", commitTs), zap.Uint64("highWatermark", group.HighWatermark),
zap.String("schema", schema), zap.String("table", table), zap.Int64("tableID", tableID),
zap.Stringer("eventType", message.RowType))
group.AppendMessage(message, true)
return
}
log.Warn("DML event fallback row, since less than the group high watermark, ignore it",
zap.Uint64("commitTs", commitTs), zap.Uint64("highWatermark", group.HighWatermark),
zap.Any("partitionWatermark", progress.watermark), zap.Any("watermark", progress.watermark),
zap.String("schema", schema), zap.String("table", table), zap.Int64("tableID", tableID),
zap.Stringer("eventType", message.RowType),
zap.Any("protocol", w.protocol), zap.Bool("IsPartition", isPartitionTable))
default:
log.Panic("unknown protocol", zap.Any("protocol", w.protocol))
}
log.Warn("DML event commit ts fallback, append it and sort before flush",
zap.Int32("partition", progress.partition),
zap.Uint64("commitTs", commitTs), zap.Uint64("highWatermark", group.HighWatermark),
zap.Any("partitionWatermark", progress.watermark),
zap.String("schema", schema), zap.String("table", table), zap.Int64("tableID", tableID),
zap.Stringer("eventType", message.RowType),
zap.Any("protocol", w.protocol), zap.Bool("enableTableAcrossNodes", w.enableTableAcrossNodes))
}
Loading
Loading