{"record":{"id":"73f526758a09f723","repo":"apache/seatunnel","slug":"fail-to-deserialize-row-row-table-tableinf","errorCode":null,"errorMessage":"Fail to deserialize row: ${row}, table: ${tableInfo.getId()}","messagePattern":"Fail to deserialize row: (.+?), table: (.+?)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"seatunnel-connectors-v2/connector-cdc/connector-cdc-tidb/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/tidb/source/deserializer/SeaTunnelRowStreamingRecordDeserializer.java","lineNumber":77,"sourceCode":"            case PUT:\n                try {\n                    values =\n                            decodeObjects(\n                                    row.getValue().toByteArray(),\n                                    RowKey.decode(row.getKey().toByteArray()).getHandle(),\n                                    tableInfo);\n                    if (row.getOldValue() == null || row.getOldValue().isEmpty()) {\n                        SeaTunnelRow insert = converter.convert(values, tableInfo, rowType);\n                        insert.setRowKind(RowKind.INSERT);\n                        collect(insert, output);\n                    } else {\n                        SeaTunnelRow update = converter.convert(values, tableInfo, rowType);\n                        update.setRowKind(RowKind.UPDATE_AFTER);\n                        collect(update, output);\n                    }\n                    break;\n                } catch (final RuntimeException e) {\n                    throw new RuntimeException(\n                            String.format(\n                                    \"Fail to deserialize row: %s, table: %s\",\n                                    row, tableInfo.getId()),\n                            e);\n                }\n            default:\n                throw new IllegalArgumentException(\"Unknown Row Op Type: \" + row.getOpType());\n        }\n    }\n\n    private Object[] decodeDeleteValues(Cdcpb.Event.Row row, long handle) {\n        ByteString oldValue = row.getOldValue();\n        if (oldValue != null && !oldValue.isEmpty()) {\n            return decodeObjects(oldValue.toByteArray(), handle, tableInfo);\n        }\n        ByteString value = row.getValue();\n        if (value != null && !value.isEmpty()) {\n            // Prefer an available row image before falling back to a PK-only delete row.","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/apache/seatunnel/blob/cf67b549a7a6c35fa0beb12d83c62892427ea919/seatunnel-connectors-v2/connector-cdc/connector-cdc-tidb/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/tidb/source/deserializer/SeaTunnelRowStreamingRecordDeserializer.java#L59-L95","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect the wrapped cause ('Caused by') to see the actual decode failure (type/format) and fix the schema mapping","Restart the job after any DDL change so the table schema is re-captured","Check the printed table ID and compare its current schema in TiDB with the connector's expected schema","Upgrade the connector if the failing column type is a newly supported TiDB type"],"exampleFix":"// before: deserialize fails mid-job after ALTER TABLE ADD COLUMN\nconverter.convert(values, tableInfo, rowType);\n// after: capture schema fresh and exclude unsupported columns before starting the CDC source\ntableInfo = TiDbSchemaUtils.getTableInfo(dataSourceConfig, tableId); // refresh schema\njob restart with updated table schema","handlingStrategy":"validation","validationCode":"// Before starting CDC, verify table schema matches expectations\nTableInfo current = TiDbSchemaUtils.getTableInfo(cfg, tableId);\nif (!current.getColumns().equals(expectedColumns)) {\n    throw new IllegalStateException(\"Schema drift on table \" + tableId + \"; refresh snapshot\");\n}","typeGuard":"// Guard values before conversion\nif (row.getValue() == null || row.getValue().isEmpty()) {\n    LOG.warn(\"CDC row {} for table {} has empty value; skipping\", row.getStartTs(), tableInfo.getId());\n    return;\n}","tryCatchPattern":"try {\n    deserializer.deserialize(row, output);\n} catch (RuntimeException e) {\n    LOG.error(\"CDC deserialize failed for table \" + tableInfo.getId(), e.getCause());\n    throw e; // fail job; schema must be re-captured\n}","preventionTips":["Avoid DDL changes on tables during an active CDC job","Keep connector and TiDB/TiKV versions compatible","Always inspect the wrapped cause to find the true decode failure","Pin table schemas in staging before running production CDC"],"tags":["cdc","tidb","deserialization","schema-mismatch"],"backgroundTag":"protobuf-unmarshal-failed","analyzedSha":"cf67b549a7a6c35fa0beb12d83c62892427ea919","analyzedAt":"2026-09-10T21:44:55.265Z","contentChangedAt":"2026-09-10T21:44:55.265Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}