apache/seatunnel · error · RuntimeException

Fail to deserialize row: ${row}, table: ${tableInfo.getId()}

Error message

Fail to deserialize row: ${row}, table: ${tableInfo.getId()}

What it means

This wrapper exception is thrown by SeaTunnelRowStreamingRecordDeserializer.deserialize when the TiKV CDC event row (INSERT/DELETE/UPDATE) fails during conversion into a SeaTunnelRow via the row converter. It wraps the original RuntimeException and annotates it with the raw row and the TiDB table ID so the offending CDC event can be identified. It indicates the change event payload could not be decoded against the captured table schema.

Source

Thrown at seatunnel-connectors-v2/connector-cdc/connector-cdc-tidb/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/tidb/source/deserializer/SeaTunnelRowStreamingRecordDeserializer.java:77

            case PUT:
                try {
                    values =
                            decodeObjects(
                                    row.getValue().toByteArray(),
                                    RowKey.decode(row.getKey().toByteArray()).getHandle(),
                                    tableInfo);
                    if (row.getOldValue() == null || row.getOldValue().isEmpty()) {
                        SeaTunnelRow insert = converter.convert(values, tableInfo, rowType);
                        insert.setRowKind(RowKind.INSERT);
                        collect(insert, output);
                    } else {
                        SeaTunnelRow update = converter.convert(values, tableInfo, rowType);
                        update.setRowKind(RowKind.UPDATE_AFTER);
                        collect(update, output);
                    }
                    break;
                } catch (final RuntimeException e) {
                    throw new RuntimeException(
                            String.format(
                                    "Fail to deserialize row: %s, table: %s",
                                    row, tableInfo.getId()),
                            e);
                }
            default:
                throw new IllegalArgumentException("Unknown Row Op Type: " + row.getOpType());
        }
    }

    private Object[] decodeDeleteValues(Cdcpb.Event.Row row, long handle) {
        ByteString oldValue = row.getOldValue();
        if (oldValue != null && !oldValue.isEmpty()) {
            return decodeObjects(oldValue.toByteArray(), handle, tableInfo);
        }
        ByteString value = row.getValue();
        if (value != null && !value.isEmpty()) {
            // Prefer an available row image before falling back to a PK-only delete row.

View on GitHub (pinned to cf67b549a7)

Solutions

  1. Inspect the wrapped cause ('Caused by') to see the actual decode failure (type/format) and fix the schema mapping
  2. Restart the job after any DDL change so the table schema is re-captured
  3. Check the printed table ID and compare its current schema in TiDB with the connector's expected schema
  4. Upgrade the connector if the failing column type is a newly supported TiDB type

Example fix

// before: deserialize fails mid-job after ALTER TABLE ADD COLUMN
converter.convert(values, tableInfo, rowType);
// after: capture schema fresh and exclude unsupported columns before starting the CDC source
tableInfo = TiDbSchemaUtils.getTableInfo(dataSourceConfig, tableId); // refresh schema
job restart with updated table schema
Defensive patterns

Strategy: validation

Validate before calling

// Before starting CDC, verify table schema matches expectations
TableInfo current = TiDbSchemaUtils.getTableInfo(cfg, tableId);
if (!current.getColumns().equals(expectedColumns)) {
    throw new IllegalStateException("Schema drift on table " + tableId + "; refresh snapshot");
}

Type guard

// Guard values before conversion
if (row.getValue() == null || row.getValue().isEmpty()) {
    LOG.warn("CDC row {} for table {} has empty value; skipping", row.getStartTs(), tableInfo.getId());
    return;
}

Try / catch

try {
    deserializer.deserialize(row, output);
} catch (RuntimeException e) {
    LOG.error("CDC deserialize failed for table " + tableInfo.getId(), e.getCause());
    throw e; // fail job; schema must be re-captured
}

Prevention

When it happens

Trigger: The CDC event's column values (values/oldValue bytes) do not match the tableInfo schema used by the converter (e.g. schema changed since snapshot, unsupported data type, or malformed/deleted row data from TiKV); converter.convert throws inside the switch statement's try block.

Common situations: Running a CDC job while a DDL change (ALTER TABLE) happens on the source TiDB table; ingesting a table with column types the converter cannot decode; TiKV CDC delivering events for a table whose schema snapshot is stale.

Understand the failure class

Background: "cannot parse invalid wire-format data", "cannot unmarshal", "failed unmarshalling": protobuf unmarshal errors explained — this error's family across 10 libraries.

Related errors


AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10). Data as JSON: /api/errors/73f526758a09f723. Report an issue: GitHub.