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
122 changes: 34 additions & 88 deletions cmd/kafka-consumer/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,8 @@ func (p *partitionProgress) updateWatermark(newWatermark uint64, offset kafka.Of
zap.Uint64("watermark", newWatermark))
return
}
readOldOffset := true
if offset > p.watermarkOffset {
readOldOffset = false
}
readOldOffset := offset <= p.watermarkOffset

log.Warn("partition resolved ts fall back, ignore it",
zap.Bool("readOldOffset", readOldOffset),
zap.Int32("partition", p.partition),
Expand Down Expand Up @@ -121,7 +119,8 @@ func newWriter(ctx context.Context, o *option) *writer {
w.progresses[i] = newPartitionProgress(int32(i), decoder)
}

eventRouter, err := eventrouter.NewEventRouter(o.sinkConfig, o.topic, false, o.protocol == config.ProtocolAvro)
isAvroLike := o.protocol == config.ProtocolAvro
eventRouter, err := eventrouter.NewEventRouter(o.sinkConfig, o.topic, false, isAvroLike)
if err != nil {
log.Panic("initialize the event router failed",
zap.Any("protocol", o.protocol), zap.Any("topic", o.topic),
Expand Down Expand Up @@ -158,12 +157,6 @@ func (w *writer) flushDDLEvent(ctx context.Context, ddl *event.DDLEvent) error {
tableIDs := w.getBlockTableIDs(ddl)
commitTs := ddl.GetCommitTs()
resolvedEvents := make([]*event.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]
Expand Down Expand Up @@ -204,11 +197,6 @@ func (w *writer) flushDDLEvent(ctx context.Context, ddl *event.DDLEvent) error {
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 @@ -285,12 +273,6 @@ func (w *writer) flushDMLEventsByWatermark(ctx context.Context) error {

watermark := w.globalWatermark()
resolvedEvents := make([]*event.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 {
messages := group.ResolveInto(watermark, nil)
Expand Down Expand Up @@ -327,11 +309,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 @@ -632,84 +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
}
// IMPORTANT: Kafka offsets are append-only, but CommitTs can go backwards after
// a TiCDC restart/retry (at-least-once replay). We must not drop such events
// solely based on a "seen" watermark (e.g. HighWatermark). The only safe
// ignore condition is "already flushed to downstream".
if commitTs <= group.AppliedWatermark {
log.Warn("DML event replayed after applied, ignore it",
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, 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("appliedWatermark", group.AppliedWatermark), zap.Uint64("highWatermark", group.HighWatermark),
zap.Uint64("partitionWatermark", progress.watermark), zap.Any("watermarkOffset", progress.watermarkOffset),
zap.String("schema", schema), zap.String("table", table), zap.Any("protocol", w.protocol))
zap.Uint64("watermark", progress.watermark), zap.Any("watermarkOffset", progress.watermarkOffset),
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 {
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.Uint64("appliedWatermark", group.AppliedWatermark),
zap.Uint64("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))
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:
// 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
Loading
Loading