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

  1. Let the recovery proceed — this is normal cleanup after a crash; ensure the DB user has XA recovery privileges (XA_RECOVER_ADMIN or equivalent)
  2. If transactions are skipped, verify the xidGenerator's subtask assignment matches the original job (same parallelism, same sink identifier)
  3. Manually roll back orphaned in-doubt transactions via DB tooling if recovery cannot (e.g. lost job state)
  4. 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

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


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