Core, Spark: Clean up uncommitted files when a staged table is aborted - #16388
Core, Spark: Clean up uncommitted files when a staged table is aborted#16388wombatu-kun wants to merge 1 commit into
Conversation
bf3c656 to
d3eb8ca
Compare
|
This pull request has been marked as stale due to 30 days of inactivity. It will be closed in 1 week if no further activity occurs. If you think that’s incorrect or this pull request requires a review, please simply write any comment. If closed, you can revive the PR at any time and @mention a reviewer or discuss it on the dev@iceberg.apache.org list. Thank you for your contributions. |
|
not stale |
|
@RussellSpitzer @rdblue this has been open since May 18 with no review (it already collected a stale-bot warning). CI is green (56/56) and it still merges cleanly on current
|
bca8f8c to
e578aa0
Compare
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Generated-by: Claude Opus 5 (1M context)
e578aa0 to
2e7fa16
Compare
Summary
StagedSparkTable.abortStagedChanges()was an empty// TODO: clean up. Spark calls it to roll back an atomic CTAS/RTAS, and it is also used by the snapshot/migrate actions. Iceberg's internal transaction cleanup only runs whencommitTransaction()is actually invoked and then fails. When a failure happens after the staged write but beforecommitStagedChanges()is called (for example,SnapshotTableSparkAction/MigrateTableSparkActionfailing while importing data, or an interrupted CTAS), nothing cleaned the manifest list and manifests already written into the uncommitted transaction. For a staged CREATE the table is never registered, so those orphans have no table metadata pointing at them and are unreachable byremoveOrphanFiles- they leak permanently.Changes
default void abortTransaction()to theTransactionAPI. The default is a documented no-op, so existing implementations are unaffected.BaseTransactionto run the existingcleanUp()(cleanAllUpdates()+deleteUncommittedFiles()) - the same cleanup Iceberg already performs when a create/replace transaction's own commit fails. The override is skipped oncecommitTransaction()has been attempted (see below).CommitCallbackTransaction; the post-commit callback is intentionally not run on abort.StagedSparkTable.abortStagedChanges()totransaction.abortTransaction()inspark/v4.1. The Spark-side change is identical for 3.5 and 4.0; a backport PR will follow.java.method.addedToInterfaceentry to.palantir/revapi.yml.A no-op default (rather than throwing) is used because
abortTransaction()runs fromcatch/finallyblocks where a secondary exception would mask the original failure. The underlying deletion is best-effort and idempotent (CatalogUtil.deleteFileswallowsNotFoundException,cleanAllUpdates()suppresses failures).abortTransaction()does nothing oncecommitTransaction()has been attempted. Spark callsabortStagedChanges()from the catch block wrappingcommitStagedChanges(), so an abort can follow a commit that threwCommitStateUnknownExceptionafter succeeding; cleaning up there would delete metadata the committed snapshot references. The commit path already performs whatever cleanup is safe, guarded byCommitStateUnknownExceptionandTableOperations.requireStrictCleanup().Out of scope
Executor-written data files in the write-succeeds-then-commit-fails path are not deleted here. That matches Iceberg's existing create-transaction behavior, and those files are handled by
SparkWrite.abort()on write-job failure.Testing
TestCreateTransaction(runs across all format-version templates): stages a create transaction, performs an append, asserts the manifest and manifest list exist, callsabortTransaction(), asserts they are removed and the table is not created, and verifies a secondabortTransaction()does not throw.TestStagedSparkTableinspark/v4.1: stages a CREATE viaSparkCatalog.stageCreate, routes an append into the staged transaction, callsabortStagedChanges(), and asserts the uncommitted manifest/manifest-list files are deleted and the table is not created. The session-catalog parameterization is skipped (it producesRollbackStagedTable, notStagedSparkTable). 3 configs (hive/hadoop/rest) pass, 1 skipped, 0 failures.TestReplaceTransaction.testAbortTransactionAfterUnknownStateKeepsCommittedFiles: runs a create transaction whose commit succeeds but reportsCommitStateUnknownException, then callsabortTransaction()and asserts the committed manifest and manifest list survive../gradlew revApiCheck,spotlessApply -DallModules, and the tests above all pass.AI Disclosure