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
287 changes: 165 additions & 122 deletions cmd/kafka-consumer/writer.go

Large diffs are not rendered by default.

55 changes: 55 additions & 0 deletions cmd/kafka-consumer/writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,16 @@ import (
"context"
"testing"

"github.com/confluentinc/confluent-kafka-go/v2/kafka"
"github.com/pingcap/ticdc/cmd/util"
"github.com/pingcap/ticdc/downstreamadapter/sink"
"github.com/pingcap/ticdc/downstreamadapter/sink/eventrouter"
"github.com/pingcap/ticdc/pkg/common"
commonEvent "github.com/pingcap/ticdc/pkg/common/event"
"github.com/pingcap/ticdc/pkg/config"
codecCommon "github.com/pingcap/ticdc/pkg/sink/codec/common"
timodel "github.com/pingcap/tidb/pkg/meta/model"
"github.com/pingcap/tidb/pkg/util/chunk"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -278,3 +283,53 @@ func TestWriterWrite_handlesOutOfOrderDDLsByCommitTs(t *testing.T) {
require.Len(t, w.ddlList, 1)
require.Equal(t, "CREATE TABLE `common_1`.`a` (`a` BIGINT PRIMARY KEY,`b` INT)", w.ddlList[0].Query)
}

func TestAppendRow2GroupKeepsDebeziumPartitionTableFallback(t *testing.T) {
for _, protocol := range []config.Protocol{
config.ProtocolDebezium,
config.ProtocolDebeziumAvro,
} {
t.Run(protocol.String(), func(t *testing.T) {
replicaCfg := config.GetDefaultReplicaConfig()
eventRouter, err := eventrouter.NewEventRouter(replicaCfg.Sink, "test-topic", false, false)
require.NoError(t, err)

w := &writer{
progresses: []*partitionProgress{{partition: 0, eventsGroup: make(map[int64]*util.EventsGroup)}},
eventRouter: eventRouter,
protocol: protocol,
partitionTableAccessor: codecCommon.NewPartitionTableAccessor(),
}

w.partitionTableAccessor.Add("target", "src")
ddl := &commonEvent.DDLEvent{
Query: "CREATE TABLE `target`.`dst` LIKE `target`.`src`",
SchemaName: "target",
TableName: "dst",
Type: byte(timodel.ActionCreateTable),
}
w.onDDL(ddl)
require.True(t, w.partitionTableAccessor.IsPartitionTable("target", "dst"))

newDMLEvent := func(commitTs uint64) *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: "target", Table: "dst"},
},
}
}

progress := w.progresses[0]
w.appendMessage2Group(codecCommon.NewDMLMessageFromEvent(newDMLEvent(200)), progress, kafka.Offset(10))
w.appendMessage2Group(codecCommon.NewDMLMessageFromEvent(newDMLEvent(100)), progress, kafka.Offset(11))

resolved := progress.eventsGroup[1].ResolveInto(150, nil)
require.Len(t, resolved, 1)
require.Equal(t, uint64(100), resolved[0].GetCommitTs())
})
}
}
2 changes: 1 addition & 1 deletion cmd/pulsar-consumer/consumer.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func (c *consumer) readMessage(ctx context.Context) error {
if !needCommit {
continue
}
err := c.pulsarConsumer.AckID(consumerMsg.Message.ID())
err := c.pulsarConsumer.AckIDCumulative(consumerMsg.ID())
if err != nil {
log.Panic("Error ack message", zap.Error(err))
}
Expand Down
184 changes: 97 additions & 87 deletions cmd/pulsar-consumer/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,43 +143,28 @@ func (w *writer) flushDDLEvent(ctx context.Context, ddl *commonEvent.DDLEvent) e
var (
done = make(chan struct{}, 1)

total int
flushed atomic.Int64
)

tableIDs := w.getBlockTableIDs(ddl)
commitTs := ddl.GetCommitTs()
resolvedEvents := make([]*commonEvent.DMLEvent, 0)
// resolvedGroups records which EventsGroup has flushed events so we can
// advance its AppliedWatermark after the flush is fully finished.
resolvedGroups := make([]struct {
group *util.EventsGroup
maxCommitTs uint64
}, 0)
for tableID := range tableIDs {
for _, progress := range w.progresses {
g, ok := progress.eventsGroup[tableID]
if !ok {
continue
}
before := len(resolvedEvents)
resolvedEvents = g.ResolveInto(commitTs, resolvedEvents)
resolvedCount := len(resolvedEvents) - before
if resolvedCount == 0 {
continue
messages := g.ResolveInto(commitTs, nil)
events := make([]*commonEvent.DMLEvent, 0, len(messages))
for _, message := range messages {
events = util.AppendOrMergeDMLEvent(events, message.ToDMLEvent())
}

resolvedGroups = append(resolvedGroups, struct {
group *util.EventsGroup
maxCommitTs uint64
}{
group: g,
maxCommitTs: resolvedEvents[len(resolvedEvents)-1].GetCommitTs(),
})
total += resolvedCount
resolvedEvents = append(resolvedEvents, events...)
}
}

total := len(resolvedEvents)
if total == 0 {
return w.mysqlSink.WriteBlockEvent(ddl)
}
Expand All @@ -204,11 +189,6 @@ func (w *writer) flushDDLEvent(ctx context.Context, ddl *commonEvent.DDLEvent) e
log.Info("flush DML events before DDL done", zap.Uint64("DDLCommitTs", commitTs),
zap.Int("total", total), zap.Duration("duration", time.Since(start)),
zap.Any("tables", tableIDs))
for _, item := range resolvedGroups {
if item.maxCommitTs > item.group.AppliedWatermark {
item.group.AppliedWatermark = item.maxCommitTs
}
}
return w.mysqlSink.WriteBlockEvent(ddl)
case <-ticker.C:
log.Warn("DML events cannot be flushed in time",
Expand Down Expand Up @@ -280,37 +260,22 @@ func (w *writer) flushDMLEventsByWatermark(ctx context.Context) error {
var (
done = make(chan struct{}, 1)

total int
flushed atomic.Int64
)

watermark := w.globalWatermark()
resolvedEvents := make([]*commonEvent.DMLEvent, 0)
// resolvedGroups records which EventsGroup has flushed events so we can
// advance its AppliedWatermark after the flush is fully finished.
resolvedGroups := make([]struct {
group *util.EventsGroup
maxCommitTs uint64
}, 0)
for _, p := range w.progresses {
for _, group := range p.eventsGroup {
before := len(resolvedEvents)
resolvedEvents = group.ResolveInto(watermark, resolvedEvents)
resolvedCount := len(resolvedEvents) - before
if resolvedCount == 0 {
continue
messages := group.ResolveInto(watermark, nil)
events := make([]*commonEvent.DMLEvent, 0, len(messages))
for _, message := range messages {
events = util.AppendOrMergeDMLEvent(events, message.ToDMLEvent())
}

resolvedGroups = append(resolvedGroups, struct {
group *util.EventsGroup
maxCommitTs uint64
}{
group: group,
maxCommitTs: resolvedEvents[len(resolvedEvents)-1].GetCommitTs(),
})
total += resolvedCount
resolvedEvents = append(resolvedEvents, events...)
}
}
total := len(resolvedEvents)
if total == 0 {
return nil
}
Expand All @@ -334,11 +299,6 @@ func (w *writer) flushDMLEventsByWatermark(ctx context.Context) error {
case <-done:
log.Info("flush DML events done", zap.Uint64("watermark", watermark),
zap.Int("total", total), zap.Duration("duration", time.Since(start)))
for _, item := range resolvedGroups {
if item.maxCommitTs > item.group.AppliedWatermark {
item.group.AppliedWatermark = item.maxCommitTs
}
}
return nil
case <-ticker.C:
log.Warn("DML events cannot be flushed in time", zap.Uint64("watermark", watermark),
Expand Down Expand Up @@ -387,12 +347,11 @@ func (w *writer) WriteMessage(ctx context.Context, message pulsar.Message) bool
zap.Any("blockedTables", ddl.GetBlockedTables()))
needFlush = true
case common.MessageTypeRow:
row := progress.decoder.NextDMLEvent()
if row == nil {
log.Panic("DML event is nil, it's not expected")
dmlMessage := progress.decoder.NextDMLMessage()
if dmlMessage == nil {
log.Panic("DML message is nil, it's not expected")
}

w.appendRow2Group(row, progress)
w.appendMessage2Group(dmlMessage, progress)
default:
log.Panic("unknown message type", zap.Any("messageType", messageType))
}
Expand Down Expand Up @@ -484,59 +443,110 @@ func (w *writer) onDDL(ddl *commonEvent.DDLEvent) {
// e.g. create partition table + drop table(rename table) + create normal table: the partitionTableAccessor should drop the table when the table become normal.
switch timodel.ActionType(ddl.Type) {
case timodel.ActionCreateTable:
if w.markPartitionTableFromDDL(ddl) {
return
}
stmt, err := parser.New().ParseOneStmt(ddl.Query, "", "")
if err != nil {
log.Panic("parse ddl query failed", zap.String("query", ddl.Query), zap.Error(err))
}
if v, ok := stmt.(*ast.CreateTableStmt); ok && v.Partition != nil {
w.partitionTableAccessor.Add(ddl.GetSchemaName(), ddl.GetTableName())
if v, ok := stmt.(*ast.CreateTableStmt); ok {
if v.Partition != nil {
w.addPartitionTable(ddl.GetSchemaName(), ddl.GetTableName())
return
}
if v.ReferTable != nil {
referSchema := v.ReferTable.Schema.O
if referSchema == "" {
referSchema = ddl.GetSchemaName()
}
if w.partitionTableAccessor.IsPartitionTable(referSchema, v.ReferTable.Name.O) {
w.addPartitionTable(ddl.GetSchemaName(), ddl.GetTableName())
}
}
}
case timodel.ActionRenameTable:
if w.partitionTableAccessor.IsPartitionTable(ddl.ExtraSchemaName, ddl.ExtraTableName) {
w.partitionTableAccessor.Add(ddl.GetSchemaName(), ddl.GetTableName())
w.addPartitionTable(ddl.GetSchemaName(), ddl.GetTableName())
}
w.markPartitionTableFromDDL(ddl)
}
}

func (w *writer) appendRow2Group(dml *commonEvent.DMLEvent, progress *partitionProgress) {
func (w *writer) markPartitionTableFromDDL(ddl *commonEvent.DDLEvent) bool {
if ddl.TableInfo == nil || !ddl.TableInfo.IsPartitionTable() {
return false
}

w.addPartitionTable(ddl.GetSchemaName(), ddl.GetTableName())
w.addPartitionTable(ddl.TableInfo.GetSchemaName(), ddl.TableInfo.GetTableName())
w.addPartitionTable(ddl.TableInfo.GetSchemaName(), ddl.TableInfo.GetTableName())
return true
}

func (w *writer) addPartitionTable(schema, table string) {
if schema == "" || table == "" {
return
}
w.partitionTableAccessor.Add(schema, table)
}

func (w *writer) appendMessage2Group(message *common.DMLMessage, progress *partitionProgress) {
var (
tableID = dml.GetTableID()
schema = dml.TableInfo.GetSchemaName()
table = dml.TableInfo.GetTableName()
commitTs = dml.GetCommitTs()
tableID = message.TableID
schema = message.Schema
table = message.Table
commitTs = message.GetCommitTs()
)
group := progress.eventsGroup[tableID]
if group == nil {
group = util.NewEventsGroup(progress.partition, tableID)
progress.eventsGroup[tableID] = group
}
if commitTs <= group.AppliedWatermark {
log.Warn("DML event replayed after applied, ignore it",
if commitTs < progress.watermark {
log.Warn("DML Event fallback row, since less than the partition watermark, ignore it",
zap.Int64("tableID", tableID), zap.Int32("partition", group.Partition),
zap.Uint64("commitTs", commitTs),
zap.Uint64("appliedWatermark", group.AppliedWatermark), zap.Uint64("highWatermark", group.HighWatermark),
zap.Uint64("partitionWatermark", progress.watermark),
zap.String("schema", schema), zap.String("table", table), zap.Any("protocol", w.protocol))
zap.Uint64("commitTs", commitTs), zap.Uint64("watermark", progress.watermark),
zap.String("schema", schema), zap.String("table", table))
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
}
forceInsert := commitTs < group.HighWatermark || commitTs < progress.watermark || w.enableTableAcrossNodes
if forceInsert {
log.Warn("DML event commit ts fallback, append with forceInsert",
zap.Int32("partition", group.Partition),
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.Uint64("appliedWatermark", group.AppliedWatermark),
zap.Uint64("partitionWatermark", progress.watermark),
zap.String("schema", schema), zap.String("table", table), zap.Int64("tableID", tableID),
zap.Stringer("eventType", dml.RowTypes[0]), zap.Any("protocol", w.protocol),
zap.Bool("IsPartition", dml.TableInfo.TableName.IsPartition))
group.Append(dml, true)
zap.Stringer("eventType", message.RowType))
group.AppendMessage(message, true)
return
}
group.Append(dml, false)
log.Info("DML event append to the group",
zap.Int32("partition", group.Partition),
zap.Uint64("commitTs", commitTs), zap.Uint64("highWatermark", group.HighWatermark),
zap.Uint64("appliedWatermark", group.AppliedWatermark),
zap.String("schema", schema), zap.String("table", table), zap.Int64("tableID", tableID),
zap.Stringer("eventType", dml.RowTypes[0]))
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))
}
}
Loading
Loading