apache/seatunnel · warning
rollback {} recovered transactions
Error message
rollback {} recovered transactions What it means
A WARN log in XaGroupOpsImpl.recoverAndRollback(). At sink recovery (or with checkpoint disabled / on startup), recovered XA transactions belonging to this subtask that were prepared by a previous execution are rolled back; this warning announces how many are being cleaned up. Failures per-transaction are only logged at INFO so cleanup continues for the rest.
Source
Thrown at seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/xa/XaGroupOpsImpl.java:138
public void recoverAndRollback(
JobContext context,
SinkWriter.Context sinkContext,
XidGenerator xidGenerator,
Xid excludeXid) {
Collection<Xid> recovered =
xaFacade.recover().stream()
.map(
x ->
new XidImpl(
x.getFormatId(),
x.getGlobalTransactionId(),
x.getBranchQualifier()))
.collect(Collectors.toList());
recovered.remove(excludeXid);
if (recovered.isEmpty()) {
return;
}
LOG.warn("rollback {} recovered transactions", recovered.size());
for (Xid xid : recovered) {
if (xidGenerator.belongsToSubtask(xid, context, sinkContext)) {
try {
xaFacade.rollback(xid);
} catch (Exception e) {
LOG.info("unable to rollback recovered transaction, xid={}", xid, e);
}
}
}
}
private static void throwIfAnyReachedMaxAttempts(
GroupXaOperationResult<XidInfo> result, int maxAttempts) {
List<XidInfo> reached = null;
for (XidInfo x : result.getForRetry()) {
if (x.getAttempts() >= maxAttempts) {
if (reached == null) {
reached = new ArrayList<>();View on GitHub (pinned to cf67b549a7)
Solutions
- Let the recovery proceed — this is normal cleanup after a crash; ensure the DB user has XA recovery privileges (XA_RECOVER_ADMIN or equivalent)
- If transactions are skipped, verify the xidGenerator's subtask assignment matches the original job (same parallelism, same sink identifier)
- Manually roll back orphaned in-doubt transactions via DB tooling if recovery cannot (e.g. lost job state)
- Check the INFO logs for individual rollback failures if transactions persist
Defensive patterns
Strategy: validation
Validate before calling
// Check for lingering prepared transactions before restarting a job: // MySQL: XA RECOVER; // PostgreSQL: SELECT * FROM pg_prepared_xacts;
Try / catch
try {
xaFacade.rollback(xid);
} catch (Exception e) {
LOG.info("unable to rollback recovered transaction, xid={}", xid, e);
// continue with remaining recovered xids
} Prevention
- Grant the sink DB user XA recovery privileges (XA_RECOVER_ADMIN / similar)
- Keep parallelism and sink identifiers stable across restarts so xid ownership is preserved
- Inspect leftover in-doubt transactions if a job is deleted without recovery
When it happens
Trigger: recoverAndRollback() lists in-doubt XIDs via the facade (xa_recover), filters out the excluded xid and those not belonging to the subtask, then logs 'rollback N recovered transactions' and rolls each belonging xid back.
Common situations: Job restarted after a failure with prepared-but-uncommitted transactions left in the DB; manual restart with a different parallelism/uuid changes xid ownership so transactions are skipped; long-lived in-doubt transactions from crashed jobs.
Understand the failure class
Background: "query failed", "%w: SQL error" — wrapped database query errors in Go libraries explained — this error's family across 3 libraries.
Related errors
- XA_OPERATION_FAILED
- formatErrorMessage(action, xid, errorCode, errorMessage)
- unable to fail/rollback transaction, xid={}: {}
- rollback prepared transaction failed, try to recover pending
- recovery after rollback prepared transaction failure also fa
AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10).
Data as JSON: /api/errors/6ac0f1779b16511c.
Report an issue: GitHub.