alibaba/canal · error · RuntimeException
Failed rows
Error message
Failed rows:{rows} What it means
Thrown after the TablestoreAdapter flushes all writers and collects Future results, when one or more row writes failed (WriterResult.getFailedRows() is non-empty). The error message includes a delimited list of failure descriptions built from each RowChangeStatus. This indicates that the Table Store (OTS) write API rejected specific rows, not that the flush mechanism itself failed.
Solutions
- Inspect the error message details for each failed row — they typically contain the OTS error code (e.g. OTSQuotaExhausted, OTSParameterInvalid, OTSRowOperationConflict).
- If throttled, increase the OTS table's provisioned read/write capacity or enable auto-scaling.
- If PK conflicts, verify source data uniqueness and the PK mapping configuration.
- If data type mismatch, align the OTS table column types with the tablestore fieldType mapping in the config.
- For transient errors, implement retry logic or a dead-letter queue for failed rows.
Example fix
// increase OTS table throughput if throttled
// via Alibaba Cloud console or CLI:
// ots update-table --instance <name> --read-capacity 5000 --write-capacity 5000
// also check mapping config fieldType alignment
dbMapping:
targetColumns:
pk_col:
type: INTEGER Defensive patterns
Strategy: retry
Validate before calling
null
Type guard
null
Try / catch
// Retry with backoff for transient OTS write failures
int maxRetries = 3;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
tablestoreAdapter.sync(dmls);
break;
} catch (RuntimeException e) {
if (e.getMessage() != null && e.getMessage().contains("Failed rows:") && attempt < maxRetries) {
logger.warn("OTS write failed on attempt {}/{}, retrying...", attempt, maxRetries);
Thread.sleep(attempt * 2000L);
continue;
}
throw e;
}
} Prevention
- Provision OTS table throughput with headroom above peak binlog event rate.
- Monitor OTS throttling metrics and set alerts before capacity is exhausted.
- Implement a dead-letter mechanism to persist failed rows for later replay.
- Validate data types between source and OTS table schema before enabling sync.
When it happens
Trigger: Calling the sync/flush path where one or more async writes to Alibaba Tablestore (OTS) returned errors — common causes include primary key conflicts (duplicate PK), throughput exceeded (throttling), data type mismatches, row size limits exceeded, or transient network issues between canal and the OTS endpoint.
Common situations: OTS table provisioned throughput is too low for the binlog event rate; duplicate PK values from source data; column data types in OTS don't match the mapping; network latency or intermittent connectivity to the OTS endpoint; large rows exceeding OTS row size limits.
Related errors
- count is not supportted in tablestore
- dbMapping.database
- dbMapping.table
- dbMapping.targetTable
- ERROR Config
AI-assisted analysis of alibaba/canal@87be50e876 (2026-08-14).
Data as JSON: /api/errors/4db5d36a8a80d833.
Report an issue: GitHub.
Appendix: source
Thrown at client-adapter/tablestore/src/main/java/com/alibaba/otter/canal/client/adapter/tablestore/TablestoreAdapter.java:155
for (Future<WriterResult> future : futureList) {
try {
WriterResult result = future.get();
List<WriterResult.RowChangeStatus> failedRows = result.getFailedRows();
if (!CollectionUtils.isEmpty(failedRows)) {
totalFailedRows.addAll(failedRows);
}
} catch (InterruptedException e) {
logger.info("InterruptedException", e);
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
if (!CollectionUtils.isEmpty(totalFailedRows)) {
// 认为有失败的请求
List<String> msgs = totalFailedRows.stream().map(e -> buildErrorMsgForFailedRowChange(e)).collect(Collectors.toList());
throw new RuntimeException("Failed rows:" + org.springframework.util.StringUtils.collectionToDelimitedString(msgs, ",", "[", "]"));
}
} catch (Exception e) {
throw e;
}
}
/**
* 组装失败记录的信息
* @param rowChangeStatus
* @return
*/
public static String buildErrorMsgForFailedRowChange(WriterResult.RowChangeStatus rowChangeStatus) {
StringBuilder sb = new StringBuilder("{Exception:");
sb.append(rowChangeStatus.getException().getMessage()).append(",Table:")
.append(rowChangeStatus.getRowChange().getTableName()).append(",PrimaryKey:")
.append("{").append(rowChangeStatus.getRowChange().getPrimaryKey().toString())
.append("}}");View on GitHub (pinned to 87be50e876)