Skip to content

Refactor event ingestion into conversion + pipeline queues with MySQL source support - #21

Open
gengdy1545 wants to merge 8 commits into
AntiO2:masterfrom
gengdy1545:feature/mysqlSource
Open

Refactor event ingestion into conversion + pipeline queues with MySQL source support#21
gengdy1545 wants to merge 8 commits into
AntiO2:masterfrom
gengdy1545:feature/mysqlSource

Conversation

@gengdy1545

Copy link
Copy Markdown

Summary

  • Refactor sink ingestion so sources own protocol conversion and publish only canonical RowChangeEvent / TransactionMetadata into pipeline queues.
  • Replace the provider hierarchy with pipeline-owned BlockingBoundedQueue for backpressure and ordered delivery.
  • Add MySQL Debezium source support (adapter + config) alongside Postgres, and unify logging on Log4j2 to remove competing bindings.

Changes

Source conversion & pipelines

  • Move Kafka / Engine / Storage deserialization and Debezium envelope handling into conversion (JSON/Avro/Struct converters, envelope normalizer, source adapters).
  • Introduce TablePipeline / TransactionPipeline / TablePipelineManager so processors consume canonical events only.
  • Add KafkaRowSource / KafkaTransactionSource and rework PixelsDebeziumConsumer to convert at the source boundary.
  • Add MySQL source adapter and conf/pixels-sink.mysql.properties.

Provider removal

  • Remove EventProvider and per-source provider subclasses.
  • Pipelines own BlockingBoundedQueue for bounded, blocking handoff between sources and processors.
  • Update docs (overview, usage, configuration) to match the new architecture.

Logging

  • Standardize on Log4j2; exclude competing SLF4J/Log4j bindings in pom.xml.
  • Refresh log4j2.properties / jvm.conf and drop unused logging.properties.

Comment thread pom.xml
Comment on lines +146 to +230
public static RecordType classify(SourceRecord sourceRecord, String transactionTopic)
{
if (sourceRecord == null || sourceRecord.value() == null)
{
return RecordType.TOMBSTONE;
}
if (!(sourceRecord.value() instanceof Struct value))
{
return RecordType.UNKNOWN_CONTROL;
}

if (transactionTopic != null && transactionTopic.equals(sourceRecord.topic()))
{
String status = DebeziumRecordUtil.getStringSafely(value, "status");
String id = DebeziumRecordUtil.getStringSafely(value, "id");
return (status.equals("BEGIN") || status.equals("END")) && !id.isBlank()
? RecordType.TRANSACTION
: RecordType.UNKNOWN_CONTROL;
}

return isRowChange(value) ? RecordType.ROW : RecordType.UNKNOWN_CONTROL;
}

private static boolean isRowChange(Struct value)
{
String op = DebeziumRecordUtil.getStringSafely(value, "op").toLowerCase(Locale.ROOT);
if (!(op.equals("c") || op.equals("u") || op.equals("d") || op.equals("r")))
{
return false;
}

Object sourceObject = DebeziumRecordUtil.getFieldSafely(value, "source");
if (!(sourceObject instanceof Struct source) ||
DebeziumRecordUtil.getStringSafely(source, "db").isBlank() ||
DebeziumRecordUtil.getStringSafely(source, "table").isBlank())
{
return false;
}

Object before = DebeziumRecordUtil.getFieldSafely(value, "before");
Object after = DebeziumRecordUtil.getFieldSafely(value, "after");
return switch (op)
{
case "c", "r" -> after instanceof Struct;
case "u" -> before instanceof Struct && after instanceof Struct;
case "d" -> before instanceof Struct;
default -> false;
};
}

private void logSourceRecord(SourceRecord record, RecordType recordType)
{
if (!LOGGER.isDebugEnabled())
{
return;
}
Struct value = record.value() instanceof Struct struct ? struct : null;
Struct source = value == null ? null :
asStruct(DebeziumRecordUtil.getFieldSafely(value, "source"));
Struct transaction = value == null ? null :
asStruct(DebeziumRecordUtil.getFieldSafely(value, "transaction"));
String rawTransactionId = recordType == RecordType.TRANSACTION
? DebeziumRecordUtil.getStringSafely(value, "id")
: DebeziumRecordUtil.getStringSafely(transaction, "id");
String canonicalTransactionId =
connectorAdapter.normalizeTransactionId(rawTransactionId);

LOGGER.debug("Debezium SourceRecord topic={}, sourcePartition={}, sourceOffset={}, " +
"keySchema={}, key={}, valueSchema={}, category={}, transaction.id={}, " +
"source.gtid={}, source.file={}, source.pos={}, source.row={}",
record.topic(), record.sourcePartition(), record.sourceOffset(),
schemaName(record.keySchema()), record.key(), schemaName(record.valueSchema()),
recordType, canonicalTransactionId,
DebeziumRecordUtil.getStringSafely(source, "gtid"),
DebeziumRecordUtil.getStringSafely(source, "file"),
DebeziumRecordUtil.getStringSafely(source, "pos"),
DebeziumRecordUtil.getStringSafely(source, "row"));
}

private static Struct asStruct(Object value)
{
return value instanceof Struct struct ? struct : null;
}

private static String schemaName(org.apache.kafka.connect.data.Schema schema)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里应该也要保留流水线。

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个是测试什么的

return (dateToString);
}

public static String convertTimestampToString(Date date)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

连MySQL和PG确认下CDC能不能正确解析。我记得Debezium Date、Time、Timestamp,包括不同精度的时间,有点复杂。

@AntiO2

AntiO2 commented Jul 30, 2026

Copy link
Copy Markdown
Owner

还有测试一下Decimal的解析

@AntiO2

AntiO2 commented Jul 30, 2026

Copy link
Copy Markdown
Owner

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aa78cf9286

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +128 to +129
tablePipelineManager.close();
transactionPipeline.close();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Drain pipeline queues before closing storage playback

When sink.datasource=storage runs without looping, clean() waits only for the file-consumer threads, which merely enqueue converted records into the new table and transaction pipelines. These calls then close queues that explicitly discard pending values, so any backlog not yet written when file playback finishes is silently lost; wait for the pipeline processors to drain before closing them.

Useful? React with 👍 / 👎.

Comment on lines +66 to +69
if (adapter == null)
{
throw new IllegalStateException(
"No Debezium source adapter configured for " + topic);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Configure the adapter before decoding transaction metadata

With the packaged Kafka configuration (src/main/resources/pixels-sink.properties), debezium.connector.class is absent while this JSON transaction deserializer now requires an adapter. The first transaction record therefore throws here, and KafkaTransactionSource catches that exception outside its polling loop and terminates permanently, preventing transactional rows from being committed. Either supply/infer the adapter for Kafka transaction messages or reject the incomplete configuration at startup.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants