apache/seatunnel · error

recovery after rollback prepared transaction failure also fa

Error message

recovery after rollback prepared transaction failure also failed, xid={}

What it means

Logged by tryRecoverPreparedTransactionsAfterRollbackFailure when the fallback XA recovery itself throws: both the direct rollback of the prepared transaction and the recoverAndRollback sweep have failed. The recovery exception is attached as suppressed to the pending JdbcConnectorException, which is then thrown, so the transaction likely remains in-doubt on the database until manual or later recovery.

Source

Thrown at seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcExactlyOnceSinkWriter.java:304

                            String.format(
                                    "failed to rollback prepared transaction after begin next transaction failure, xid=%s",
                                    xid),
                            rollbackException);
            rollbackFailure.addSuppressed(beginTxException);
            tryRecoverPreparedTransactionsAfterRollbackFailure(xid, rollbackFailure);
            throw rollbackFailure;
        }
    }

    private void tryRecoverPreparedTransactionsAfterRollbackFailure(
            Xid failedRollbackXid, JdbcConnectorException rollbackFailure) {
        try {
            LOG.warn(
                    "rollback prepared transaction failed, try to recover pending transactions for current subtask, xid={}",
                    failedRollbackXid);
            xaGroupOps.recoverAndRollback(context, sinkcontext, xidGenerator, null);
        } catch (Exception recoveryException) {
            LOG.warn(
                    "recovery after rollback prepared transaction failure also failed, xid={}",
                    failedRollbackXid,
                    recoveryException);
            rollbackFailure.addSuppressed(recoveryException);
        }
    }

    private void failAndRollbackCurrentXidQuietly() {
        if (currentXid == null || !xaFacade.isOpen()) {
            return;
        }
        Xid xid = currentXid;
        try {
            LOG.debug("remove current transaction, xid={}", xid);
            xaFacade.failAndRollback(xid);
        } catch (Exception e) {
            LOG.warn("unable to fail/rollback current transaction, xid={}", xid, e);
        } finally {

View on GitHub (pinned to cf67b549a7)

Solutions

  1. Read the full thrown JdbcConnectorException: beginTxException + rollbackFailure + suppressed recoveryException pinpoint the root cause (usually connectivity)
  2. Restore DB connectivity, then restart the job — startup recovery will sweep and resolve in-doubt prepared transactions
  3. Grant recovery privileges: MySQL 8 requires XA_RECOVER_ADMIN for XA RECOVER; verify with a manual 'xa recover'
  4. Manually resolve in-doubt transactions with database tooling (xa rollback '<xid>' in MySQL, COMMIT FORCE/ROLLBACK FORCE in Oracle) to avoid locks and orphaned transactions
  5. Check dialect XA support and driver version; some drivers mis-report recovery lists — upgrade to a driver with solid XA support

Example fix

// before: user without recovery privilege
CREATE USER 'st'@'%' IDENTIFIED BY '***';
GRANT SELECT, INSERT, UPDATE ON db.* TO 'st'@'%';
// after
CREATE USER 'st'@'%' IDENTIFIED BY '***';
GRANT SELECT, INSERT, UPDATE ON db.* TO 'st'@'%';
GRANT XA_RECOVER_ADMIN ON *.* TO 'st'@'%';
Defensive patterns

Strategy: try-catch

Validate before calling

// verify recovery listing works with the configured account BEFORE running production jobs
-- MySQL: xa recover;  (needs XA_RECOVER_ADMIN)
-- Oracle: select * from dba_pending_transactions;
-- PostgreSQL: select gid from pg_prepared_xacts;

Try / catch

try {
    writer.prepareCommit();
} catch (JdbcConnectorException e) {
    // contains beginTxException + rollbackFailure + suppressed recoveryException
    for (Throwable t : e.getSuppressed()) {
        LOG.error("recovery also failed, resolve in-doubt xids manually", t);
    }
    // restore connectivity, then restart so startup recovery retries
}

Prevention

When it happens

Trigger: rollbackPrepareXidOrThrow -> tryRecoverPreparedTransactionsAfterRollbackFailure where xaGroupOps.recoverAndRollback(context, sinkcontext, xidGenerator, null) throws — typically because the connection/facade is still broken, the DB rejects XA RECOVER (missing privileges), or the resource manager is unreachable.

Common situations: Prolonged DB outage during checkpoint; DB account lacking recovery privileges (MySQL XA_RECOVER_ADMIN); XA connection still bound to the dead session; driver/dialect not fully supporting XA recovery listing.

Related errors


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