fix: wrap Iceberg split-write failures the way Spark does when abort fails - #6153
Merged
andygrove merged 3 commits intoSep 24, 2026
Merged
Conversation
…fails
On every supported Spark version (3.4 through 4.2), V2TableWriteExec.writeWithV2
rethrows the original job or commit failure unchanged when the abort succeeds,
and only wraps it in QueryExecutionErrors.writingJobFailedError ("Writing job
failed.") when batchWrite.abort itself throws, with the abort failure attached
to the cause as a suppressed exception. The wrap-every-non-fatal-failure
behaviour described in the issue is Spark 3.3 and earlier, which Comet does not
support, so no version shim is needed.
IcebergCommitExec already rethrew the raw cause, matching Spark in the common
case, but also did so when the abort failed. Throw writingJobFailedError around
the cause in that case. Abort, completed-task file cleanup and suppression of
abort and cleanup failures onto the cause are unchanged.
Add tests that run the same failing Iceberg write (a commit-time validation
failure and a failed write job) with the split operator off and on and assert
the thrown exception and its cause have the same types, plus tests that drive
IcebergCommitExec with a BatchWrite whose abort fails.
Closes apache#6143.
This was referenced Sep 23, 2026
sunchao
approved these changes
Sep 23, 2026
sunchao
left a comment
Member
There was a problem hiding this comment.
Summary
- Prior state and problem:
IcebergCommitExecpreserved the original write failure even whenBatchWrite.abortfailed. Supported Spark versions wrap that case inwritingJobFailedError, with the abort failure suppressed on the original cause. - Design approach: The small
abortAfterhelper shares that behavior between job and commit failures. It returns the throwable so completed-task cleanup still runs after a job failure. - Correctness / compatibility analysis: Reviewed at
6d674be4f067644544cc8891fa0b03a4ba14d72f. The helper matches upstream Spark 3.4.3, 3.5.9, 4.0.4, 4.1.3, and 4.2.0. The wrapper retains the original cause, including cleanup errors suppressed after wrapping. I found no reproducible P1/P2 regression. - Key design decisions: Abort success preserves the original exception object. Only abort failure adds Spark's wrapper. Cleanup failure alone keeps the prior behavior, and the successful write path is unchanged.
- Implementation sketch: Both failure handlers call
abortAfter. It invokesabort, records a suppressed abort error when necessary, and returns either the original cause orQueryExecutionErrors.writingJobFailedError(cause). The tests cover task and commit failures with successful and failed aborts. - Behavioral changes worth calling out: Callers now receive
SparkException("Writing job failed.")when both the write and abort fail. The original failure remains its cause. No row-processing or successful-write overhead was added. - Suggested improvements: No additional code changes identified at the requested P1/P2 bar.
Validation: the exact-head scans CI job passed all 73 CometIcebergWriteActionSuite tests, including all four new tests and the existing completed-task cleanup cases. The head has 24 passing and 14 skipped checks. I did not rerun the suite locally. Broader Iceberg SQL and non-default runtime coverage remains outside this PR-tier evidence, so this review does not establish that the repository's pre-queue integration validation is complete.
71 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Which issue does this PR close?
Closes #6143.
Rationale for this change
#6143 reported that
IcebergCommitExecrethrows the raw cause of a failed write where Spark 3.x wraps it in a "Writing job aborted"SparkException. Checking Spark's source shows that premise is only true for Spark 3.3 and earlier, which Comet does not support.At every Spark version Comet builds against (3.4, 3.5, 4.0, 4.1, 4.2),
V2TableWriteExec.writeWithV2catches anyThrowablefrom the write job or frombatchWrite.commitand aborts. Then:batchWrite.abortsucceeds, it rethrows the original cause unchangedbatchWrite.abortitself throws, it attaches the abort failure to the cause as suppressed and throwsQueryExecutionErrors.writingJobFailedError(cause), aSparkException("Writing job failed.") with the original failure as its causeWriteToDataSourceV2Exec.scala, wrap / rethrowQueryExecutionErrors.writingJobFailedErrorIcebergCommitExecalready matched Spark when the abort succeeds. It diverged only when the abort failed: it attached the abort failure as suppressed but rethrew the raw cause instead of wrapping it. So the fix is narrower than the issue suggests, and needs no version shim.What changes are included in this PR?
IcebergCommitExec: on both the job-failure and the commit-failure path, whenbatchWrite.abortthrows, throwwritingJobFailedError(cause)instead ofcause. A single helper,abortAfter, runs the abort and returns what the failed write throws: the cause itself when the abort succeeds, or the wrapped cause when it fails. Logging an abort failure logs only the message, as Spark does, since its stack trace travels with the thrown exception as a suppressed exception. Everything else is unchanged: the abort itself, the deletion of completed tasks' data files on a job failure, and attaching abort and cleanup failures to the cause as suppressed exceptions. Cleanup failures alone do not cause wrapping, because that cleanup is Comet's own addition and has no counterpart in Spark's path.CometIcebergWriteActionSuite, four tests:IcebergCommitExecIcebergCommitExecwith a stubBatchWrite, asserting thewritingJobFailedErrorwrapping, the cause, and the suppressed abort failureOne thing noticed while writing the job-failure test: the existing "failed write job aborts and leaves the table unchanged" test uses a local relation as its source, so on Spark 4.1 the optimizer evaluates its UDF during planning and the query fails before a write job starts. The new job-failure test reads from Parquet so that a task actually fails. The existing test is unchanged here.
How are these changes tested?
./mvnw test -Dtest=none -Dsuites="org.apache.comet.CometIcebergWriteActionSuite", 73 succeeded, 0 failed.-Pspark-3.5, 70 succeeded, 0 failed, 3 canceled (tests gated on Spark 4.1+).main'sIcebergCommitExec.