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
|
d3eb8ca to
bca8f8c
Compare
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bca8f8c to
e578aa0
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.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 already best-effort and idempotent (CatalogUtil.deleteFileswallowsNotFoundException,cleanAllUpdates()suppresses failures), so calling abort after a failedcommitStagedChanges()is safe.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../gradlew revApiCheck,spotlessApply -DallModules, and the tests above all pass.AI Disclosure