NoahKusaba opened a new issue, #2622:
URL: https://github.com/apache/datafusion-ballista/issues/2622

   **Is your feature request related to a problem or challenge? Please describe 
what you are trying to do.**
   
   Table formats such as Iceberg and Delta finish a write with one commit. 
Every writer task's data files are gathered in one place and committed through 
the catalog, exactly once, after all writes have succeeded.
   
   DataFusion represents this commit as an ordinary plan node at the root of 
the INSERT plan. For Iceberg (#2217) that plan is:
   
   ```
   IcebergCommitExec          1 partition: one catalog commit, outputs a 
`count` row
     CoalescePartitionsExec
       IcebergWriteExec       N partitions: writes Parquet files, outputs their 
metadata
   ```
   
   Ballista sends every plan node to an executor, so the commit runs as a 
one-task final stage on some executor. That has three costs, all of which come 
from where the commit runs rather than from the table format:
   
   1. **Executors need the catalog.** The plan has to carry the catalog's 
configuration, credentials included, so that an executor can rebuild the 
catalog client and commit.
   2. **Commits must be idempotent.** Ballista retries failed tasks, so a 
commit task can run twice. #2217 has to give each commit an id and refuse to 
run a commit without one.
   3. **Every executor has to cache catalog clients,** with logic to evict and 
retry stale ones.
   
   Spark avoids all three because its write API ends on the driver. Writer 
tasks send small commit messages back, and the driver calls 
`BatchWrite.commit(messages)` once. Ballista has nowhere to run code on the 
scheduler at the end of a job: `succeed_job` only saves the job's final state.
   
   **Describe the solution you'd like**
   
   A way for a job's final step to run on the scheduler, after all of its 
stages have succeeded. Its small result becomes the job's result.
   
   A minimal version, which I have a working prototype of:
   
   - **A config hook to mark the final step:** 
`SessionConfigExt::with_ballista_scheduler_root(predicate)`. When a job's root 
operator matches, the scheduler removes it before splitting the plan into 
stages, and the rest runs on executors as usual.
   - **Running it when the job ends:** once the job succeeds, the scheduler 
reads the final stage's output (here, the data files' metadata) with a 
`ShuffleReaderExec` and runs the removed root on it.
   - **Returning the result:** the root's output comes back to the client in a 
new field, `SuccessfulJob.inline_result`, as Arrow IPC bytes. That's a few rows 
for a commit.
   
   For Iceberg this is a one-line opt-in that matches `IcebergCommitExec`. It 
needs no change to datafusion-iceberg or to the plan.
   
   Points for discussion:
   
   - **Scope:** the hook could run any root operator, but it is only meant for 
small final steps such as commits. Should the API be narrowed to that, for 
example a `JobFinalizer` trait with a capped result size?
   - **Job state:** the prototype reports a finished job as running until the 
commit is done. An explicit `Finalizing` state in the job graph would be 
cleaner.
   - **Cancellation:** if a job is cancelled while its commit runs, the commit 
may still go through. Spark has the same behaviour; here it should at least be 
documented.
   
   **Describe alternatives you've considered**
   
   - **Keep committing on an executor** (what #2217 does). It works, but has 
the three costs above.
   - **A Spark-style commit protocol:** writer tasks return commit messages in 
their task status, and a scheduler-side hook commits them. This avoids writing 
the metadata to shuffle files and reading it back, but it needs a new 
writer/committer API in Ballista core, and the write node would have to report 
its output through task status.
   - **Run the final stage on the scheduler** as an ordinary stage that the 
scheduler executes itself. This is closer to how the job graph already works, 
but it changes both graph implementations, and the scheduler would have to 
serve that stage's output.
   
   **Additional context**
   
   - **Use case:** Iceberg writes in #2217, part of #2174. Any sink that ends 
with a single commit would benefit, Delta Lake and Hudi included.
   - **Prototype:** I'm happy to open it as a draft PR for discussion. It 
covers both the adaptive and the static planner and both pull and push job 
status. It includes a standalone test and a Docker test against a real Iceberg 
REST catalog.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to