FelixYBW commented on issue #13140:
URL: https://github.com/apache/gluten/issues/13140#issuecomment-6281254885

   # Arrow UDF support in Gluten: today vs. target (Spark 4.2 + SPARK-57468)
   
   **Goal:** Gluten supports Spark's Arrow-based columnar operators 
(`ArrowEvalPythonExec`,
   `ArrowEvalPythonUDTFExec`, ...) in one of two ways. 
   - Keeps Spark's operator and only inserts Arrow <-> Velox transitions
   - It adds a thin columnar wrapper. Gluten stops re-implementing Spark's 
operators and Python runners.
   
   ## 0. Sources reviewed
   
   Spark commits on `SPARK-57468-convention-4.2`:
   
   | Commit | Change |
   |---|---|
   | `98c9eac81ee` | [SPARK-57468] Convention API: `RowType` / `BatchType` / 
`ArrowBatchType`, `TransitionGraph`, `SparkPlan.convention` / 
`requiredChildConventions`, graph-based transition insertion (+593/−53) |
   | `effa8e8cd3d` | Follow-up: `ArrowEvalPythonExec` consumes and produces 
`ArrowBatchType` |
   | `8341e63bf92` | Follow-up: ownership contract of `ArrowBatchType` batches, 
and `ArrowBatchOwner` |
   | `5c4190677bb` | Follow-up: buffers of a producer that has an allocation 
listener stay in the producer's allocator |
   
   Gluten commits on `spark-convention-arrow-udf-4.2`:
   
   | Commit | Change |
   |---|---|
   | `4a5a7a46dd` | `SparkConventions` bridge, `SparkArrowBatchType`, rewrite 
of `ColumnarArrowEvalPythonExec` on Spark APIs only, `BasePythonRunnerShim` 
removed |
   | `bc293a5c77` | Replace `ArrowWritableColumnVector` with Spark's 
`ArrowColumnVector` + `ArrowWriter` ("Arrow write"), `ArrowJavaBatchType` bound 
to Spark's `ArrowBatchType` (+378/−4420) |
   | `65db047126` | Port to Spark 4.2: use Spark's columnar Arrow runner 
(SPARK-56350) |
   | `8a430f38e4` | Remove Gluten's `ColumnarArrowEvalPythonExec` and use 
Spark's `ArrowEvalPythonExec` directly |
   | `ec3f05360d` | Gluten's per-task Arrow allocator becomes a child of 
`ArrowUtils.rootAllocator`, so UDF results move into it without a copy |
   
   Upstream status (2026-10-13):
   
   - SPARK-56350 / apache/spark#55120 (columnar input for Arrow UDFs) is in 
branch-4.2.
   - SPARK-57468 / apache/spark#59154 is open with no reviews yet. It targets 
master, so the 4.2 line
     needs a backport or our own patch.
   - SPARK-59792 / apache/spark#59062 (UDTF columnar input) is open.
   - The three SPARK-57468 follow-ups are only on the branch; no upstream PR 
exists for them yet.
   - Gluten #12276 (Arrow UDTF) is open.
   
   ---
   
   ## 1. What Gluten does today (Gluten main)
   
   ### Plan
   
   ```mermaid
   flowchart TD
       V1["VeloxOp<br/>(VeloxBatchType)"]
       L["[LoadArrowDataExec]<br/>native Arrow -> Java Arrow<br/>(C Data; 
copies strings and dictionaries)"]
       subgraph E["ColumnarArrowEvalPythonExec — Gluten's own operator 
(ArrowJavaBatchType in and out)"]
           R["ColumnarArrowPythonRunner<br/>Gluten's own runner<br/>built on 
BasePythonRunnerShim"]
       end
       O["[OffloadArrowDataExec]<br/>Java Arrow -> native Arrow"]
       A["[ArrowColumnarToVeloxColumnarExec]"]
       V2["VeloxOp"]
   
       V1 --> L --> E --> O --> A --> V2
   
       classDef gluten fill:#fde7e7,stroke:#c0392b,color:#1f2328;
       classDef inserted fill:#e8f0fb,stroke:#3b82d4,color:#1f2328;
       classDef velox fill:#eef7ee,stroke:#2e7d32,color:#1f2328;
       class R gluten;
       style E fill:#fdf3f3,stroke:#c0392b,color:#1f2328;
       class L,O,A inserted;
       class V1,V2 velox;
   ```
   
   Gluten's `ConventionFunc` sees every other Spark Arrow operator as a vanilla 
row or vanilla
   columnar plan. Gluten then plans `VeloxColumnarToRow -> Spark op -> 
RowToVeloxColumnar`.
   
   ### Components Gluten owns today
   
   | Area | Gluten code | Size |
   |---|---|---|
   | Python exec | 
`backends-velox/.../spark/api/python/ColumnarArrowEvalPythonExec.scala` (exec + 
`ColumnarArrowPythonRunner`, `adjustRefCnt`, input batch cache with retain / 
release) | 579 lines |
   | Worker protocol shims | 
`shims/spark3x,40,41/.../BasePythonRunnerShim.scala` | 4 files, about 66 lines 
each |
   | Arrow vector | `gluten-arrow/.../ArrowWritableColumnVector.java` (read, 
write, ref count) | 2,309 lines |
   | Vector API shims | `shims/*/WritableColumnVectorShim.java` | 4 × about 223 
lines |
   | Arrow write helpers | `ArrowProjection`, `GenerateArrowProjection`, 
`InterpretedArrowProjection`, `ArrowColumnarBatch`, `ArrowColumnarRow`, 
`ArrowColumnVectorUtils` | about 690 lines |
   | UDTF | #12276 (open): `ColumnarArrowEvalPythonUDTFExec` + per-version 
`ArrowEvalPythonUDTFShim`, then a copy of the Python output into Gluten's 
allocator | – |
   | Pre-project | `PullOutArrowEvalPythonPreProjectHelper` (complex UDF args 
-> columns) | – |
   | Allocator | Per-task `RootAllocator` with `ManagedAllocationListener`, 
separate from Spark's root | – |
   
   ### Pain points
   
   1. **Duplicated Spark logic.** Gluten must track every change to Spark's 
Python runner and worker
      protocol (e.g. SPARK-44705, SPARK-51384 `runnerConf` / `evalConf`). Each 
new Spark Arrow
      operator needs its own Gluten re-implementation.
   2. **No Arrow type in Spark's planner.** A vanilla plan with 
`supportsColumnar = true` is always
      `VanillaBatchType`, so "Velox -> Arrow -> Spark operator -> Arrow -> 
Velox" needs special cases
      for each operator.
   3. **Unsafe output hand-off.** `BasicPythonArrowOutput` allocates from a 
separate child of Spark's
      root and closes it at end of stream. Gluten has its own root, so it must 
copy the UDF output
      (#12276), and Spark's side of that memory isn't tracked.
   4. **Ref-count lifecycle.** `ColumnarBatches` loads and offloads batches in 
place, with reference
      counts kept by `ArrowWritableColumnVector` and `IndicatorVector`. This 
leads to workarounds such
      as `adjustRefCnt`.
   5. **Everything else falls back to rows**: UDTF (until #12276), `MapInArrow` 
/ `MapInPandas`,
      `FlatMapGroupsIn*`, `FlatMapCoGroupsIn*`, `ArrowAggregatePython`, 
`ArrowWindowPython`.
   
   ---
   
   ## 2. What Gluten will do (Spark 4.2 + SPARK-57468 and follow-ups, Gluten 
Arrow write)
   
   ### Plan
   
   The plan below is produced by `ArrowEvalPythonExecSuite` on the 4.2 branch. 
Every bracketed node
   is inserted automatically.
   
   ```mermaid
   flowchart TD
       V1["VeloxOp<br/>(VeloxBatchType)"]
       L["[LoadArrowDataExec]<br/>reports Spark's 
ArrowBatchType<br/>(ArrowJavaBatchType is bound to it)"]
       subgraph E["ArrowEvalPythonExec"]
           direction TB
           T["Spark's own operator, unchanged<br/>convention = (VanillaRow, 
ArrowBatchType)"]
           R["Spark's Python 
runner<br/>(ColumnarArrowPythonWithNamedArgumentRunner)"]
           B["ArrowBatchOwner<br/>takes ownership of input vectors<br/>by 
transfer (no copy)"]
       end
       O["[OffloadArrowDataExec]<br/>adopt(): moves vectors, same root 
allocator,<br/>no copy for UDF results"]
       A["[ArrowColumnarToVeloxColumnarExec]"]
       V2["VeloxOp"]
   
       V1 --> L --> E --> O --> A --> V2
   
       classDef spark fill:#fff4e0,stroke:#d98e04,color:#1f2328;
       classDef inserted fill:#e8f0fb,stroke:#3b82d4,color:#1f2328;
       classDef velox fill:#eef7ee,stroke:#2e7d32,color:#1f2328;
       class T,R,B spark;
       style E fill:#fffaf0,stroke:#d98e04,color:#1f2328;
       class L,O,A inserted;
       class V1,V2 velox;
   ```
   
   ### How it works
   
   - **Spark** (`SparkPlan.convention`, `requiredChildConventions`, 
`TransitionGraph`): every plan
     declares its row and batch types. `ArrowBatchType` means "a batch of 
`ArrowColumnVector`", and
     its transition to `VanillaBatchType` is free.
   - **Spark `ArrowEvalPythonExec`**: if `arrowBatchInput` holds, the operator 
declares
     `(VanillaRowType, ArrowBatchType)`, requires Arrow batches from its child, 
always takes the Arrow
     columnar path, and outputs the child's columns followed by the UDF results 
as Arrow. The
     condition is: the child's batch type is not vanilla, a path to 
`ArrowBatchType` exists, and every
     UDF input is a child column without UDTs.
   - **Ownership contract**: an `ArrowBatchType` batch is valid until the next 
`hasNext` / `next`. To
     keep a batch longer, a consumer transfers its vectors. The vectors move to 
the consumer's
     allocator only when it shares the root and the producer's allocator has no 
listener. Otherwise
     they stay in the producer's allocator.
   - **Gluten bridge** (`SparkConventions` + 7 lines in `ConventionFunc` + 
`GlutenPlan.convention`):
     - A Spark plan that declares a non-vanilla Spark type is planned as the 
Gluten type bound to it.
     - A Gluten plan whose type is bound to a Spark type reports that type to 
Spark. For example,
       `LoadArrowDataExec` reports `ArrowBatchType`.
     - Gluten plans declare `Any` on their children to Spark, because Gluten 
plans its own transitions.
   - **Gluten "Arrow write"**:
     - `RefCountedArrowColumnVector` (a subclass of Spark's 
`ArrowColumnVector`) replaces
       `ArrowWritableColumnVector`.
     - Rows are written to Arrow with Spark's `ArrowWriter` 
(`ArrowColumnVectors.fromRows`) in
       `ColumnarPartialProjectExec`, `ColumnarPartialGenerateExec`, 
`ColumnarRangeExec` and
       `ExecUtil`.
     - `ArrowJavaBatchType` is bound to Spark's `ArrowBatchType`.
   - **Allocator**: Gluten's task allocator is a child of 
`ArrowUtils.rootAllocator`. `adopt()`
     transfers Spark's vectors and moves their accounting between the two 
allocation listeners.
   
   ### Copies left on the path (from the "final plan" comment in #13140)
   
   | Step | Copy? |
   |---|---|
   | Velox -> Java Arrow | Fixed-width: none. Dictionary or constant vectors: 
flattened (copy). Strings: copied, because Arrow string views aren't enabled |
   | Spark `ArrowEvalPythonExec` input | No conversion. The UDF columns are 
serialized to IPC directly from Gluten's buffers |
   | Spark -> Gluten (offload) | None for pass-through columns or UDF results 
(same root since `ec3f05360d`) |
   | Java Arrow -> Velox | Fixed-width: none. Strings: a new string-view buffer 
(strings of 12 bytes or less are inlined) |
   
   ---
   
   ## 3. Side-by-side comparison
   
   | Aspect | Today (Gluten main) | Target (Spark 4.2 + SPARK-57468, Gluten 
branch) |
   |---|---|---|
   | Operator executing Arrow UDFs | Gluten `ColumnarArrowEvalPythonExec` (a 
`GlutenPlan`) | Spark `ArrowEvalPythonExec`, unchanged and not offloaded |
   | Python runner / worker protocol | Gluten `ColumnarArrowPythonRunner` + 
`BasePythonRunnerShim` × 4 | Spark's runners; Gluten has no Python runner code |
   | How Gluten learns the operator wants Arrow | Hard-coded offload rule 
(`createColumnarArrowEvalPythonExec`) | `SparkPlan.convention` = 
`ArrowBatchType`, mapped through `SparkConventions` |
   | Transition insertion | Gluten's own operator declares `ArrowJavaBatchType` 
| Generic: any Spark plan declaring a bound type gets Gluten's transitions |
   | Operator-specific Gluten code | Exec + runner + shims (about 850 lines) | 
`InsertArrowInputForArrowEvalPython` (53 lines), pre-project helper |
   | Java Arrow vector | `ArrowWritableColumnVector` + 
`WritableColumnVectorShim` × 4 | Spark `ArrowColumnVector` 
(`RefCountedArrowColumnVector`, 57 lines) |
   | Writing rows into Arrow | `ArrowWritableColumnVector.put*`, Gluten 
`ArrowProjection` codegen | Spark `ArrowWriter` + `MutableProjection` |
   | Gluten Arrow batch type vs. Spark | Unrelated; Spark sees vanilla | 
`ArrowJavaBatchType` ≡ Spark `ArrowBatchType` |
   | Input batch lifetime in UDF exec | Retain / release + `adjustRefCnt` | 
`ArrowBatchOwner` transfers vectors, releases after the output is consumed |
   | UDF result columns | Read by Gluten's runner into Gluten's allocator | 
Read by Spark's runner, moved into Gluten by `adopt()` (same root, no copy) |
   | Arrow allocator | Separate Gluten `RootAllocator` | Child of Spark's 
`ArrowUtils.rootAllocator`, with listener accounting moved on transfer |
   | Memory accounting of UDF output | Spark side untracked | Moved to Gluten's 
`ManagedAllocationListener` on adopt |
   | UDTF | #12276: Gluten exec + shim + output copy | Spark 
`ArrowEvalPythonUDTFExec` after SPARK-59792 + an `ArrowBatchType` declaration 
(still to do) |
   | MapInArrow / FlatMapGroups / Window / Agg | Row fallback (C2R + R2C) | 
Still row fallback until Spark operators declare `ArrowBatchType` (still to do) 
|
   | New Spark Arrow operator | New Gluten re-implementation | Zero Gluten code 
once it declares `ArrowBatchType` |
   | Supported Spark versions | 3.4 – 4.1 (4.2 via #13126) | Spark with 
SPARK-57468 only; older versions need the old path kept (still to do) |
   | Code delta | – | Spark +939/−79; Gluten +1,370/−6,025 (`bc293a5c77` alone 
is +378/−4,420) |
   | Performance (5M rows, 4 pandas UDFs) | 2.14–2.22 s | 2.24–2.35 s (within 
noise, measured before `ec3f05360d` removed the result copy) |
   
   ---
   
   ## 4. Prototype status
   
   Done and tested on the branches:
   
   - Spark: convention API, `ConventionSuite`; `ArrowEvalPythonExec` on 
`ArrowBatchType`; ownership
     contract; listener-aware transfer. All are covered by 
`ArrowColumnarPythonUDFSuite`.
   - Gluten: bridge; `ArrowJavaBatchType` bound to Spark; switch to 
`ArrowColumnVector` /
     `ArrowWriter`; Gluten's Python exec and shims removed; shared root 
allocator.
     `ArrowEvalPythonExecSuite`, `ColumnarBatchTest`, `ArrowColumnVectorTest` 
and the transition and
     fallback suites pass.
   
   Gaps in the prototype:
   
   1. `InsertArrowInputForArrowEvalPython` is still a rule specific to 
`ArrowEvalPythonExec`. Spark
      picks the Arrow path from `child.convention`, and a Velox child reports 
`VanillaBatchType`, so
      Gluten has to insert `LoadArrowDataExec` first and then check what Spark 
decided.
   2. `gluten-core` / `gluten-arrow` reference 
`org.apache.spark.sql.execution.convention` directly.
      They no longer compile against Spark versions without SPARK-57468, so the 
bridge needs a shim
      (or a no-op).
   3. `backends-bolt` still uses the removed `BasePythonRunnerShim` / 
`ArrowWritableColumnVector` and
      doesn't compile.
   4. `RefCountedArrowColumnVector` keeps Gluten's reference count. The 
in-place load / offload
      lifecycle in `ColumnarBatches` hasn't been moved to ownership transfer 
yet.
   5. The fallback reporter lists Spark Arrow operators planned through a 
convention as "not
      supported" (log noise only).
   6. The write-path performance of `ArrowWriter` vs. the old codegen 
projection hasn't been
      benchmarked.
   7. Only `ArrowEvalPythonExec` is covered.
   
   ---
   
   ## 5. Features to add
   
   ### 5.1 Spark (target: our 4.2 line, and upstream master)
   
   | # | Feature | Why | Status |
   |---|---|---|---|
   | S1 | **SPARK-57468 convention API**: `Convention`, `RowType`, `BatchType` 
(`Vanilla*`, `ArrowBatchType`), `TransitionGraph` with transition costs, 
`SparkPlan.convention` / `requiredChildConventions`, graph-based 
`ApplyColumnarRulesAndInsertTransitions`, convention propagation through 
`QueryStageExec` / `Exchange` / `DataWritingCommandExec` | The foundation: lets 
a plan declare the Arrow layout and lets plugins bind their types | Branch 
`98c9eac81ee`; upstream PR apache/spark#59154 open |
   | S2 | **`ArrowEvalPythonExec` declares `ArrowBatchType`** 
(`arrowBatchInput`, `inputsAreChildColumns`, always takes the Arrow path, 
outputs Arrow) | Spark's operator is used as is, with no Gluten wrapper | 
Branch `effa8e8cd3d`; no upstream PR yet |
   | S3 | **`ArrowBatchType` ownership contract** + `ArrowBatchOwner` (transfer 
on read, release after the output is consumed, close at end of output or task) 
| Makes zero-copy hand-off safe for producers that allocate per batch or reuse 
vectors (Velox -> Arrow, DSv2 readers) | Branch `8341e63bf92`; no upstream PR 
yet |
   | S4 | **Listener-aware transfer**: keep a producer's buffers in its 
allocator when it has an `AllocationListener` or a different root | Without it, 
Gluten's memory reservation grows until the task ends | Branch `5c4190677bb`; 
no upstream PR yet |
   | S5 | **SPARK-59792 UDTF columnar input** + make `ArrowEvalPythonUDTFExec` 
declare `ArrowBatchType` the same way as S2 / S3 | Replaces Gluten #12276 | 
apache/spark#59062 open; the `ArrowBatchType` declaration is not started |
   | S6 | **A planner-level "prefer Arrow" request** for operators that benefit 
from Arrow input, e.g. let `ArrowEvalPythonExec` require `ArrowBatchType` 
whenever any registered path from the child exists, not only when the child 
already declares a non-vanilla type. Alternative: Gluten makes Velox visible to 
Spark as a Spark `BatchType` (see G5) | Removes Gluten's 
`InsertArrowInputForArrowEvalPython`, so support is fully automatic | Not 
started; needs design |
   | S7 | **Other Arrow operators declare `ArrowBatchType` input and output**: 
`MapInArrowExec` / `MapInPandasExec`, `FlatMapGroupsInArrowExec` / `Pandas`, 
`FlatMapCoGroupsInArrowExec` / `Pandas`, `ArrowAggregatePythonExec`, 
`ArrowWindowPythonExec` | Today they take rows and output rows, so Gluten does 
C2R + R2C around them. The grouped and window operators need a group-aware 
Arrow input, which is harder | Not started |
   | S8 | **`RowToArrowColumnarExec`** on `ArrowWriter`, registered as 
`ArrowBatchType.fromRow` (revive SPARK-37124) | Planner path rows -> Arrow, so 
consumers can drop their row-by-row fallbacks. Also covers local table scan, 
RDD scan and range | Not started |
   | S9 | **Public Arrow batch utilities**: `isArrowBacked` + layout check 
(`ArrowUtils.isCompatibleWithDeclaredField`), unwrap a batch into a 
`VectorSchemaRoot` / record batch, take ownership by transfer | Gluten, the 
Arrow cache (SPARK-57268) and the UDTF path stop duplicating private helpers | 
Not started |
   | S10 | *(Optional)* Direct vanilla columnar -> `ArrowBatchType` transition 
(on-heap / off-heap vectors -> Arrow) | Parquet / ORC vectorized reads feeding 
UDFs without a row round trip | Not started |
   | S11 | *(Optional)* Python reader allocator closed at task end rather than 
at end of stream | No longer needed for Gluten once G6 is in (same root plus 
adopt-on-consume). Still useful for other consumers that keep output batches | 
Not needed for Gluten |
   | S12 | *(Optional)* Arrow string view (`Utf8View`) support in 
`ArrowColumnVector` / the IPC path to pyarrow | Removes the string copy on 
Velox -> Arrow export (see G10) | Not started |
   | S13 | *(Optional)* Spark-side rule that pulls complex UDF arguments into a 
`ProjectExec` (the TODO in `ColumnarArrowEvalPythonEvaluatorFactory`) | 
Satisfies `inputsAreChildColumns` in plain Spark too; Gluten could then drop 
`PullOutArrowEvalPythonPreProjectHelper` | Not started |
   
   ### 5.2 Gluten
   
   | # | Feature | Why | Status |
   |---|---|---|---|
   | G1 | **`SparkConventions` bridge**: bind Spark and Gluten types; 
`ConventionFunc` maps a Spark plan's declared type and child requirements; 
`GlutenPlan.convention` reports bound types to Spark, with `Any` child 
requirements | Generic transition insertion for any Spark operator that 
declares a bound type | Done (`4a5a7a46dd`, `8a430f38e4`) |
   | G2 | **Gluten Arrow batches on Spark's `ArrowColumnVector`**: 
`RefCountedArrowColumnVector`; `ArrowJavaBatchType` bound to Spark 
`ArrowBatchType`; delete `ArrowWritableColumnVector`, 
`WritableColumnVectorShim` × 4, `ArrowColumnarBatch`, `ArrowColumnarRow`, 
`ArrowColumnVectorUtils` | One Java Arrow batch type shared with Spark; no 
Spark-to-Gluten Arrow transitions; no shims | Done (`bc293a5c77`) |
   | G3 | **Arrow write via Spark's `ArrowWriter`**: 
`ColumnarPartialProjectExec` (`MutableProjection` + `ArrowWriter`), 
`ColumnarPartialGenerateExec`, `ColumnarRangeExec`, `ExecUtil`, 
`ArrowColumnVectors.fromRows`; drop the `ArrowProjection` codegen | Removes 
Gluten's own Arrow writer and codegen | Done (`bc293a5c77`); **benchmark 
pending** |
   | G4 | **Remove Gluten's Python exec**: `ColumnarArrowEvalPythonExec`, 
`ColumnarArrowPythonRunner`, `BasePythonRunnerShim` × 5; 
`createColumnarArrowEvalPythonExec` returns Spark's exec | Gluten no longer 
owns any Python runner or worker protocol code | Done for Velox (`8a430f38e4`) |
   | G5 | **Remove `InsertArrowInputForArrowEvalPython`**: once S6 lands, or by 
making Velox visible to Spark (bind `VeloxBatchType` to a Spark `BatchType` 
with a registered path to `ArrowBatchType`) | Fully automatic support for any 
Arrow operator, with no rule per operator | Interim rule done (53 lines); 
removal not started |
   | G6 | **Shared root allocator**: the task allocator is a child of 
`ArrowUtils.rootAllocator`; `ArrowColumnVectors.adopt` transfers vectors and 
moves listener accounting | Zero-copy UDF results; memory accounted by Gluten | 
Done (`ec3f05360d`) |
   | G7 | **Version gating / shim for the bridge**: keep the today path (Gluten 
exec + runner + `ArrowWritableColumnVector`) for Spark 3.4 – 4.1 (and 4.2 
without SPARK-57468); put `SparkConventions`, the `GlutenPlan.convention` 
overrides and the `ArrowBatchType` binding behind a shim | The branch only 
compiles against patched Spark | Not started |
   | G8 | **Lifecycle rewrite**: replace reference counts and in-place load / 
offload in `ColumnarBatches` with "the producer owns a batch until the next 
one; a consumer that keeps it transfers the vectors". The `IndicatorVector` 
reference count stays for native batches shared by limit, tail and broadcast | 
Removes the reference count from `RefCountedArrowColumnVector`; matches Spark's 
`ArrowBatchType` contract | Not started (largest and riskiest part) |
   | G9 | **Fallback reporter**: skip Spark operators planned through a Spark 
convention | Spurious "not supported" entries | Not started |
   | G10 | *(Optional)* Velox -> Arrow export: enable Arrow string views and 
avoid flattening where the consumer accepts it (needs S12) | Removes the 
remaining string and dictionary copies | Not started |
   | G11 | **UDTF**: drop #12276 in favor of S5; Gluten only needs the existing 
transitions | No Gluten UDTF exec, shim or output copy | Waiting for S5 |
   | G12 | **Other Arrow operators** (S7): no Gluten code beyond tests once 
Spark declares `ArrowBatchType`. Before S7, a one-line convention declaration 
(rule) per operator is enough for the input side | Removes the C2R + R2C around 
them | Waiting for S7 |
   | G13 | **Pre-project**: keep `PullOutArrowEvalPythonPreProjectHelper` (in 
its own file now) so complex UDF arguments become columns and 
`inputsAreChildColumns` holds; drop it if S13 lands | Otherwise Spark takes the 
row path | Done (kept) |
   | G14 | **`backends-bolt` migration** to the same model | It doesn't compile 
after G2 / G4 | Not started |
   | G15 | **Tests**: `gluten-ut` coverage for the Spark Arrow operators on 
Velox input (multi-batch, nulls, chained UDFs, ANSI on and off, AQE, config 
`columnarInput.enabled` off), memory leak checks, and an end-to-end pyspark 
benchmark after G6 / G3 | Required before upstreaming | Partly done 
(`ArrowEvalPythonExecSuite` with ANSI off) |
   
   ---
   
   ## 6. Suggested order
   
   1. **Upstream S1** (apache/spark#59154), then S2 – S4 as SPARK-57468 
follow-ups. Backport them to
      our 4.2 line.
   2. **Gluten G7** (shim and version gating), so G1 – G6 can merge into Gluten 
main without breaking
      Spark 3.4 – 4.1.
   3. **Merge G1, G2, G3, G4, G6 and G13** for Spark builds with SPARK-57468, 
after the G3 benchmark
      and G15 tests.
   4. **S5 + G11** (UDTF), replacing #12276.
   5. **S6 + G5**, so the remaining operator-specific rule goes away.
   6. **G8** (lifecycle rewrite), then G9 and G14.
   7. **S7 / G12** (other Arrow operators), then S8 – S10 and S12 / G10 as 
optimizations.
   
   ## 7. Risks and open points
   
   - **Upstream acceptance of S1 – S4.** Spark has to accept a plugin-facing 
plan API and an ownership
     contract. The UDF-specific follow-ups may need to be generalized (S6) 
before review.
   - **Planning interplay.** Spark's `insertTransitions` and Gluten's 
`InsertTransitions` both run. It
     must be verified that they never insert competing transitions. Gluten 
plans report `Any` child
     requirements to Spark for this reason.
   - **Memory accounting.** Arrow doesn't notify listeners of transfers. Both 
S4 and `adopt()`
     compensate for this manually, which is fragile if Arrow changes its 
behavior.
   - **Grouped and window operators (S7)** need grouped Arrow input. That's a 
bigger Spark change than
     for map-style operators.
   - **Older Spark versions** keep today's code until they're dropped, so 
Gluten carries both paths
     for a while.
   # Arrow UDF support in Gluten: today vs. target (Spark 4.2 + SPARK-57468)
   
   Tracking issue: 
[apache/gluten#13140](https://github.com/apache/gluten/issues/13140)
   
   **Goal:** Gluten supports Spark's Arrow-based columnar operators 
(`ArrowEvalPythonExec`,
   `ArrowEvalPythonUDTFExec`, ...) in one of two ways. It either keeps Spark's 
operator and only
   inserts Arrow <-> Velox transitions, or it adds a thin columnar wrapper. 
Gluten stops
   re-implementing Spark's operators and Python runners.
   
   ## 0. Sources reviewed
   
   | Repo | Location | Branch / base |
   |---|---|---|
   | Gluten (today) | local `main` (`8fa03c0fe`) | apache/gluten main, Spark 
3.4 – 4.1 shims |
   | Spark (target) | `[email protected]:2202:/spark` | 
`SPARK-57468-convention-4.2` on top of `v4.2.1` (`4a1cd45b891`) |
   | Gluten (target) | `[email protected]:2202:/incubator-gluten` | 
`spark-convention-arrow-udf-4.2` on top of the Spark 4.2 support (#13126) |
   
   Spark commits on `SPARK-57468-convention-4.2`:
   
   | Commit | Change |
   |---|---|
   | `98c9eac81ee` | [SPARK-57468] Convention API: `RowType` / `BatchType` / 
`ArrowBatchType`, `TransitionGraph`, `SparkPlan.convention` / 
`requiredChildConventions`, graph-based transition insertion (+593/−53) |
   | `effa8e8cd3d` | Follow-up: `ArrowEvalPythonExec` consumes and produces 
`ArrowBatchType` |
   | `8341e63bf92` | Follow-up: ownership contract of `ArrowBatchType` batches, 
and `ArrowBatchOwner` |
   | `5c4190677bb` | Follow-up: buffers of a producer that has an allocation 
listener stay in the producer's allocator |
   
   Gluten commits on `spark-convention-arrow-udf-4.2`:
   
   | Commit | Change |
   |---|---|
   | `4a5a7a46dd` | `SparkConventions` bridge, `SparkArrowBatchType`, rewrite 
of `ColumnarArrowEvalPythonExec` on Spark APIs only, `BasePythonRunnerShim` 
removed |
   | `bc293a5c77` | Replace `ArrowWritableColumnVector` with Spark's 
`ArrowColumnVector` + `ArrowWriter` ("Arrow write"), `ArrowJavaBatchType` bound 
to Spark's `ArrowBatchType` (+378/−4420) |
   | `65db047126` | Port to Spark 4.2: use Spark's columnar Arrow runner 
(SPARK-56350) |
   | `8a430f38e4` | Remove Gluten's `ColumnarArrowEvalPythonExec` and use 
Spark's `ArrowEvalPythonExec` directly |
   | `ec3f05360d` | Gluten's per-task Arrow allocator becomes a child of 
`ArrowUtils.rootAllocator`, so UDF results move into it without a copy |
   
   Upstream status (2026-10-13):
   
   - SPARK-56350 / apache/spark#55120 (columnar input for Arrow UDFs) is in 
branch-4.2.
   - SPARK-57468 / apache/spark#59154 is open with no reviews yet. It targets 
master, so the 4.2 line
     needs a backport or our own patch.
   - SPARK-59792 / apache/spark#59062 (UDTF columnar input) is open.
   - The three SPARK-57468 follow-ups are only on the branch; no upstream PR 
exists for them yet.
   - Gluten #12276 (Arrow UDTF) is open.
   
   ---
   
   ## 1. What Gluten does today (Gluten main)
   
   ### Plan
   
   ```mermaid
   flowchart TD
       V1["VeloxOp<br/>(VeloxBatchType)"]
       L["[LoadArrowDataExec]<br/>native Arrow -> Java Arrow<br/>(C Data; 
copies strings and dictionaries)"]
       subgraph E["ColumnarArrowEvalPythonExec — Gluten's own operator 
(ArrowJavaBatchType in and out)"]
           R["ColumnarArrowPythonRunner<br/>Gluten's own runner<br/>built on 
BasePythonRunnerShim"]
       end
       O["[OffloadArrowDataExec]<br/>Java Arrow -> native Arrow"]
       A["[ArrowColumnarToVeloxColumnarExec]"]
       V2["VeloxOp"]
   
       V1 --> L --> E --> O --> A --> V2
   
       classDef gluten fill:#fde7e7,stroke:#c0392b,color:#1f2328;
       classDef inserted fill:#e8f0fb,stroke:#3b82d4,color:#1f2328;
       classDef velox fill:#eef7ee,stroke:#2e7d32,color:#1f2328;
       class R gluten;
       style E fill:#fdf3f3,stroke:#c0392b,color:#1f2328;
       class L,O,A inserted;
       class V1,V2 velox;
   ```
   
   Gluten's `ConventionFunc` sees every other Spark Arrow operator as a vanilla 
row or vanilla
   columnar plan. Gluten then plans `VeloxColumnarToRow -> Spark op -> 
RowToVeloxColumnar`.
   
   ### Components Gluten owns today
   
   | Area | Gluten code | Size |
   |---|---|---|
   | Python exec | 
`backends-velox/.../spark/api/python/ColumnarArrowEvalPythonExec.scala` (exec + 
`ColumnarArrowPythonRunner`, `adjustRefCnt`, input batch cache with retain / 
release) | 579 lines |
   | Worker protocol shims | 
`shims/spark3x,40,41/.../BasePythonRunnerShim.scala` | 4 files, about 66 lines 
each |
   | Arrow vector | `gluten-arrow/.../ArrowWritableColumnVector.java` (read, 
write, ref count) | 2,309 lines |
   | Vector API shims | `shims/*/WritableColumnVectorShim.java` | 4 × about 223 
lines |
   | Arrow write helpers | `ArrowProjection`, `GenerateArrowProjection`, 
`InterpretedArrowProjection`, `ArrowColumnarBatch`, `ArrowColumnarRow`, 
`ArrowColumnVectorUtils` | about 690 lines |
   | UDTF | #12276 (open): `ColumnarArrowEvalPythonUDTFExec` + per-version 
`ArrowEvalPythonUDTFShim`, then a copy of the Python output into Gluten's 
allocator | – |
   | Pre-project | `PullOutArrowEvalPythonPreProjectHelper` (complex UDF args 
-> columns) | – |
   | Allocator | Per-task `RootAllocator` with `ManagedAllocationListener`, 
separate from Spark's root | – |
   
   ### Pain points
   
   1. **Duplicated Spark logic.** Gluten must track every change to Spark's 
Python runner and worker
      protocol (e.g. SPARK-44705, SPARK-51384 `runnerConf` / `evalConf`). Each 
new Spark Arrow
      operator needs its own Gluten re-implementation.
   2. **No Arrow type in Spark's planner.** A vanilla plan with 
`supportsColumnar = true` is always
      `VanillaBatchType`, so "Velox -> Arrow -> Spark operator -> Arrow -> 
Velox" needs special cases
      for each operator.
   3. **Unsafe output hand-off.** `BasicPythonArrowOutput` allocates from a 
separate child of Spark's
      root and closes it at end of stream. Gluten has its own root, so it must 
copy the UDF output
      (#12276), and Spark's side of that memory isn't tracked.
   4. **Ref-count lifecycle.** `ColumnarBatches` loads and offloads batches in 
place, with reference
      counts kept by `ArrowWritableColumnVector` and `IndicatorVector`. This 
leads to workarounds such
      as `adjustRefCnt`.
   5. **Everything else falls back to rows**: UDTF (until #12276), `MapInArrow` 
/ `MapInPandas`,
      `FlatMapGroupsIn*`, `FlatMapCoGroupsIn*`, `ArrowAggregatePython`, 
`ArrowWindowPython`.
   
   ---
   
   ## 2. What Gluten will do (Spark 4.2 + SPARK-57468 and follow-ups, Gluten 
Arrow write)
   
   ### Plan
   
   The plan below is produced by `ArrowEvalPythonExecSuite` on the 4.2 branch. 
Every bracketed node
   is inserted automatically.
   
   ```mermaid
   flowchart TD
       V1["VeloxOp<br/>(VeloxBatchType)"]
       L["[LoadArrowDataExec]<br/>reports Spark's 
ArrowBatchType<br/>(ArrowJavaBatchType is bound to it)"]
       subgraph E["ArrowEvalPythonExec"]
           direction TB
           T["Spark's own operator, unchanged<br/>convention = (VanillaRow, 
ArrowBatchType)"]
           R["Spark's Python 
runner<br/>(ColumnarArrowPythonWithNamedArgumentRunner)"]
           B["ArrowBatchOwner<br/>takes ownership of input vectors<br/>by 
transfer (no copy)"]
       end
       O["[OffloadArrowDataExec]<br/>adopt(): moves vectors, same root 
allocator,<br/>no copy for UDF results"]
       A["[ArrowColumnarToVeloxColumnarExec]"]
       V2["VeloxOp"]
   
       V1 --> L --> E --> O --> A --> V2
   
       classDef spark fill:#fff4e0,stroke:#d98e04,color:#1f2328;
       classDef inserted fill:#e8f0fb,stroke:#3b82d4,color:#1f2328;
       classDef velox fill:#eef7ee,stroke:#2e7d32,color:#1f2328;
       class T,R,B spark;
       style E fill:#fffaf0,stroke:#d98e04,color:#1f2328;
       class L,O,A inserted;
       class V1,V2 velox;
   ```
   
   ### How it works
   
   - **Spark** (`SparkPlan.convention`, `requiredChildConventions`, 
`TransitionGraph`): every plan
     declares its row and batch types. `ArrowBatchType` means "a batch of 
`ArrowColumnVector`", and
     its transition to `VanillaBatchType` is free.
   - **Spark `ArrowEvalPythonExec`**: if `arrowBatchInput` holds, the operator 
declares
     `(VanillaRowType, ArrowBatchType)`, requires Arrow batches from its child, 
always takes the Arrow
     columnar path, and outputs the child's columns followed by the UDF results 
as Arrow. The
     condition is: the child's batch type is not vanilla, a path to 
`ArrowBatchType` exists, and every
     UDF input is a child column without UDTs.
   - **Ownership contract**: an `ArrowBatchType` batch is valid until the next 
`hasNext` / `next`. To
     keep a batch longer, a consumer transfers its vectors. The vectors move to 
the consumer's
     allocator only when it shares the root and the producer's allocator has no 
listener. Otherwise
     they stay in the producer's allocator.
   - **Gluten bridge** (`SparkConventions` + 7 lines in `ConventionFunc` + 
`GlutenPlan.convention`):
     - A Spark plan that declares a non-vanilla Spark type is planned as the 
Gluten type bound to it.
     - A Gluten plan whose type is bound to a Spark type reports that type to 
Spark. For example,
       `LoadArrowDataExec` reports `ArrowBatchType`.
     - Gluten plans declare `Any` on their children to Spark, because Gluten 
plans its own transitions.
   - **Gluten "Arrow write"**:
     - `RefCountedArrowColumnVector` (a subclass of Spark's 
`ArrowColumnVector`) replaces
       `ArrowWritableColumnVector`.
     - Rows are written to Arrow with Spark's `ArrowWriter` 
(`ArrowColumnVectors.fromRows`) in
       `ColumnarPartialProjectExec`, `ColumnarPartialGenerateExec`, 
`ColumnarRangeExec` and
       `ExecUtil`.
     - `ArrowJavaBatchType` is bound to Spark's `ArrowBatchType`.
   - **Allocator**: Gluten's task allocator is a child of 
`ArrowUtils.rootAllocator`. `adopt()`
     transfers Spark's vectors and moves their accounting between the two 
allocation listeners.
   
   ### Copies left on the path (from the "final plan" comment in #13140)
   
   | Step | Copy? |
   |---|---|
   | Velox -> Java Arrow | Fixed-width: none. Dictionary or constant vectors: 
flattened (copy). Strings: copied, because Arrow string views aren't enabled |
   | Spark `ArrowEvalPythonExec` input | No conversion. The UDF columns are 
serialized to IPC directly from Gluten's buffers |
   | Spark -> Gluten (offload) | None for pass-through columns or UDF results 
(same root since `ec3f05360d`) |
   | Java Arrow -> Velox | Fixed-width: none. Strings: a new string-view buffer 
(strings of 12 bytes or less are inlined) |
   
   ---
   
   ## 3. Side-by-side comparison
   
   | Aspect | Today (Gluten main) | Target (Spark 4.2 + SPARK-57468, Gluten 
branch) |
   |---|---|---|
   | Operator executing Arrow UDFs | Gluten `ColumnarArrowEvalPythonExec` (a 
`GlutenPlan`) | Spark `ArrowEvalPythonExec`, unchanged and not offloaded |
   | Python runner / worker protocol | Gluten `ColumnarArrowPythonRunner` + 
`BasePythonRunnerShim` × 4 | Spark's runners; Gluten has no Python runner code |
   | How Gluten learns the operator wants Arrow | Hard-coded offload rule 
(`createColumnarArrowEvalPythonExec`) | `SparkPlan.convention` = 
`ArrowBatchType`, mapped through `SparkConventions` |
   | Transition insertion | Gluten's own operator declares `ArrowJavaBatchType` 
| Generic: any Spark plan declaring a bound type gets Gluten's transitions |
   | Operator-specific Gluten code | Exec + runner + shims (about 850 lines) | 
`InsertArrowInputForArrowEvalPython` (53 lines), pre-project helper |
   | Java Arrow vector | `ArrowWritableColumnVector` + 
`WritableColumnVectorShim` × 4 | Spark `ArrowColumnVector` 
(`RefCountedArrowColumnVector`, 57 lines) |
   | Writing rows into Arrow | `ArrowWritableColumnVector.put*`, Gluten 
`ArrowProjection` codegen | Spark `ArrowWriter` + `MutableProjection` |
   | Gluten Arrow batch type vs. Spark | Unrelated; Spark sees vanilla | 
`ArrowJavaBatchType` ≡ Spark `ArrowBatchType` |
   | Input batch lifetime in UDF exec | Retain / release + `adjustRefCnt` | 
`ArrowBatchOwner` transfers vectors, releases after the output is consumed |
   | UDF result columns | Read by Gluten's runner into Gluten's allocator | 
Read by Spark's runner, moved into Gluten by `adopt()` (same root, no copy) |
   | Arrow allocator | Separate Gluten `RootAllocator` | Child of Spark's 
`ArrowUtils.rootAllocator`, with listener accounting moved on transfer |
   | Memory accounting of UDF output | Spark side untracked | Moved to Gluten's 
`ManagedAllocationListener` on adopt |
   | UDTF | #12276: Gluten exec + shim + output copy | Spark 
`ArrowEvalPythonUDTFExec` after SPARK-59792 + an `ArrowBatchType` declaration 
(still to do) |
   | MapInArrow / FlatMapGroups / Window / Agg | Row fallback (C2R + R2C) | 
Still row fallback until Spark operators declare `ArrowBatchType` (still to do) 
|
   | New Spark Arrow operator | New Gluten re-implementation | Zero Gluten code 
once it declares `ArrowBatchType` |
   | Supported Spark versions | 3.4 – 4.1 (4.2 via #13126) | Spark with 
SPARK-57468 only; older versions need the old path kept (still to do) |
   | Code delta | – | Spark +939/−79; Gluten +1,370/−6,025 (`bc293a5c77` alone 
is +378/−4,420) |
   | Performance (5M rows, 4 pandas UDFs) | 2.14–2.22 s | 2.24–2.35 s (within 
noise, measured before `ec3f05360d` removed the result copy) |
   
   ---
   
   ## 4. Prototype status
   
   Done and tested on the branches:
   
   - Spark: convention API, `ConventionSuite`; `ArrowEvalPythonExec` on 
`ArrowBatchType`; ownership
     contract; listener-aware transfer. All are covered by 
`ArrowColumnarPythonUDFSuite`.
   - Gluten: bridge; `ArrowJavaBatchType` bound to Spark; switch to 
`ArrowColumnVector` /
     `ArrowWriter`; Gluten's Python exec and shims removed; shared root 
allocator.
     `ArrowEvalPythonExecSuite`, `ColumnarBatchTest`, `ArrowColumnVectorTest` 
and the transition and
     fallback suites pass.
   
   Gaps in the prototype:
   
   1. `InsertArrowInputForArrowEvalPython` is still a rule specific to 
`ArrowEvalPythonExec`. Spark
      picks the Arrow path from `child.convention`, and a Velox child reports 
`VanillaBatchType`, so
      Gluten has to insert `LoadArrowDataExec` first and then check what Spark 
decided.
   2. `gluten-core` / `gluten-arrow` reference 
`org.apache.spark.sql.execution.convention` directly.
      They no longer compile against Spark versions without SPARK-57468, so the 
bridge needs a shim
      (or a no-op).
   3. `backends-bolt` still uses the removed `BasePythonRunnerShim` / 
`ArrowWritableColumnVector` and
      doesn't compile.
   4. `RefCountedArrowColumnVector` keeps Gluten's reference count. The 
in-place load / offload
      lifecycle in `ColumnarBatches` hasn't been moved to ownership transfer 
yet.
   5. The fallback reporter lists Spark Arrow operators planned through a 
convention as "not
      supported" (log noise only).
   6. The write-path performance of `ArrowWriter` vs. the old codegen 
projection hasn't been
      benchmarked.
   7. Only `ArrowEvalPythonExec` is covered.
   
   ---
   
   ## 5. Features to add
   
   ### 5.1 Spark (target: our 4.2 line, and upstream master)
   
   | # | Feature | Why | Status |
   |---|---|---|---|
   | S1 | **SPARK-57468 convention API**: `Convention`, `RowType`, `BatchType` 
(`Vanilla*`, `ArrowBatchType`), `TransitionGraph` with transition costs, 
`SparkPlan.convention` / `requiredChildConventions`, graph-based 
`ApplyColumnarRulesAndInsertTransitions`, convention propagation through 
`QueryStageExec` / `Exchange` / `DataWritingCommandExec` | The foundation: lets 
a plan declare the Arrow layout and lets plugins bind their types | Branch 
`98c9eac81ee`; upstream PR apache/spark#59154 open |
   | S2 | **`ArrowEvalPythonExec` declares `ArrowBatchType`** 
(`arrowBatchInput`, `inputsAreChildColumns`, always takes the Arrow path, 
outputs Arrow) | Spark's operator is used as is, with no Gluten wrapper | 
Branch `effa8e8cd3d`; no upstream PR yet |
   | S3 | **`ArrowBatchType` ownership contract** + `ArrowBatchOwner` (transfer 
on read, release after the output is consumed, close at end of output or task) 
| Makes zero-copy hand-off safe for producers that allocate per batch or reuse 
vectors (Velox -> Arrow, DSv2 readers) | Branch `8341e63bf92`; no upstream PR 
yet |
   | S4 | **Listener-aware transfer**: keep a producer's buffers in its 
allocator when it has an `AllocationListener` or a different root | Without it, 
Gluten's memory reservation grows until the task ends | Branch `5c4190677bb`; 
no upstream PR yet |
   | S5 | **SPARK-59792 UDTF columnar input** + make `ArrowEvalPythonUDTFExec` 
declare `ArrowBatchType` the same way as S2 / S3 | Replaces Gluten #12276 | 
apache/spark#59062 open; the `ArrowBatchType` declaration is not started |
   | S6 | **A planner-level "prefer Arrow" request** for operators that benefit 
from Arrow input, e.g. let `ArrowEvalPythonExec` require `ArrowBatchType` 
whenever any registered path from the child exists, not only when the child 
already declares a non-vanilla type. Alternative: Gluten makes Velox visible to 
Spark as a Spark `BatchType` (see G5) | Removes Gluten's 
`InsertArrowInputForArrowEvalPython`, so support is fully automatic | Not 
started; needs design |
   | S7 | **Other Arrow operators declare `ArrowBatchType` input and output**: 
`MapInArrowExec` / `MapInPandasExec`, `FlatMapGroupsInArrowExec` / `Pandas`, 
`FlatMapCoGroupsInArrowExec` / `Pandas`, `ArrowAggregatePythonExec`, 
`ArrowWindowPythonExec` | Today they take rows and output rows, so Gluten does 
C2R + R2C around them. The grouped and window operators need a group-aware 
Arrow input, which is harder | Not started |
   | S8 | **`RowToArrowColumnarExec`** on `ArrowWriter`, registered as 
`ArrowBatchType.fromRow` (revive SPARK-37124) | Planner path rows -> Arrow, so 
consumers can drop their row-by-row fallbacks. Also covers local table scan, 
RDD scan and range | Not started |
   | S9 | **Public Arrow batch utilities**: `isArrowBacked` + layout check 
(`ArrowUtils.isCompatibleWithDeclaredField`), unwrap a batch into a 
`VectorSchemaRoot` / record batch, take ownership by transfer | Gluten, the 
Arrow cache (SPARK-57268) and the UDTF path stop duplicating private helpers | 
Not started |
   | S10 | *(Optional)* Direct vanilla columnar -> `ArrowBatchType` transition 
(on-heap / off-heap vectors -> Arrow) | Parquet / ORC vectorized reads feeding 
UDFs without a row round trip | Not started |
   | S11 | *(Optional)* Python reader allocator closed at task end rather than 
at end of stream | No longer needed for Gluten once G6 is in (same root plus 
adopt-on-consume). Still useful for other consumers that keep output batches | 
Not needed for Gluten |
   | S12 | *(Optional)* Arrow string view (`Utf8View`) support in 
`ArrowColumnVector` / the IPC path to pyarrow | Removes the string copy on 
Velox -> Arrow export (see G10) | Not started |
   | S13 | *(Optional)* Spark-side rule that pulls complex UDF arguments into a 
`ProjectExec` (the TODO in `ColumnarArrowEvalPythonEvaluatorFactory`) | 
Satisfies `inputsAreChildColumns` in plain Spark too; Gluten could then drop 
`PullOutArrowEvalPythonPreProjectHelper` | Not started |
   
   ### 5.2 Gluten
   
   | # | Feature | Why | Status |
   |---|---|---|---|
   | G1 | **`SparkConventions` bridge**: bind Spark and Gluten types; 
`ConventionFunc` maps a Spark plan's declared type and child requirements; 
`GlutenPlan.convention` reports bound types to Spark, with `Any` child 
requirements | Generic transition insertion for any Spark operator that 
declares a bound type | Done (`4a5a7a46dd`, `8a430f38e4`) |
   | G2 | **Gluten Arrow batches on Spark's `ArrowColumnVector`**: 
`RefCountedArrowColumnVector`; `ArrowJavaBatchType` bound to Spark 
`ArrowBatchType`; delete `ArrowWritableColumnVector`, 
`WritableColumnVectorShim` × 4, `ArrowColumnarBatch`, `ArrowColumnarRow`, 
`ArrowColumnVectorUtils` | One Java Arrow batch type shared with Spark; no 
Spark-to-Gluten Arrow transitions; no shims | Done (`bc293a5c77`) |
   | G3 | **Arrow write via Spark's `ArrowWriter`**: 
`ColumnarPartialProjectExec` (`MutableProjection` + `ArrowWriter`), 
`ColumnarPartialGenerateExec`, `ColumnarRangeExec`, `ExecUtil`, 
`ArrowColumnVectors.fromRows`; drop the `ArrowProjection` codegen | Removes 
Gluten's own Arrow writer and codegen | Done (`bc293a5c77`); **benchmark 
pending** |
   | G4 | **Remove Gluten's Python exec**: `ColumnarArrowEvalPythonExec`, 
`ColumnarArrowPythonRunner`, `BasePythonRunnerShim` × 5; 
`createColumnarArrowEvalPythonExec` returns Spark's exec | Gluten no longer 
owns any Python runner or worker protocol code | Done for Velox (`8a430f38e4`) |
   | G5 | **Remove `InsertArrowInputForArrowEvalPython`**: once S6 lands, or by 
making Velox visible to Spark (bind `VeloxBatchType` to a Spark `BatchType` 
with a registered path to `ArrowBatchType`) | Fully automatic support for any 
Arrow operator, with no rule per operator | Interim rule done (53 lines); 
removal not started |
   | G6 | **Shared root allocator**: the task allocator is a child of 
`ArrowUtils.rootAllocator`; `ArrowColumnVectors.adopt` transfers vectors and 
moves listener accounting | Zero-copy UDF results; memory accounted by Gluten | 
Done (`ec3f05360d`) |
   | G7 | **Version gating / shim for the bridge**: keep the today path (Gluten 
exec + runner + `ArrowWritableColumnVector`) for Spark 3.4 – 4.1 (and 4.2 
without SPARK-57468); put `SparkConventions`, the `GlutenPlan.convention` 
overrides and the `ArrowBatchType` binding behind a shim | The branch only 
compiles against patched Spark | Not started |
   | G8 | **Lifecycle rewrite**: replace reference counts and in-place load / 
offload in `ColumnarBatches` with "the producer owns a batch until the next 
one; a consumer that keeps it transfers the vectors". The `IndicatorVector` 
reference count stays for native batches shared by limit, tail and broadcast | 
Removes the reference count from `RefCountedArrowColumnVector`; matches Spark's 
`ArrowBatchType` contract | Not started (largest and riskiest part) |
   | G9 | **Fallback reporter**: skip Spark operators planned through a Spark 
convention | Spurious "not supported" entries | Not started |
   | G10 | *(Optional)* Velox -> Arrow export: enable Arrow string views and 
avoid flattening where the consumer accepts it (needs S12) | Removes the 
remaining string and dictionary copies | Not started |
   | G11 | **UDTF**: drop #12276 in favor of S5; Gluten only needs the existing 
transitions | No Gluten UDTF exec, shim or output copy | Waiting for S5 |
   | G12 | **Other Arrow operators** (S7): no Gluten code beyond tests once 
Spark declares `ArrowBatchType`. Before S7, a one-line convention declaration 
(rule) per operator is enough for the input side | Removes the C2R + R2C around 
them | Waiting for S7 |
   | G13 | **Pre-project**: keep `PullOutArrowEvalPythonPreProjectHelper` (in 
its own file now) so complex UDF arguments become columns and 
`inputsAreChildColumns` holds; drop it if S13 lands | Otherwise Spark takes the 
row path | Done (kept) |
   | G14 | **`backends-bolt` migration** to the same model | It doesn't compile 
after G2 / G4 | Not started |
   | G15 | **Tests**: `gluten-ut` coverage for the Spark Arrow operators on 
Velox input (multi-batch, nulls, chained UDFs, ANSI on and off, AQE, config 
`columnarInput.enabled` off), memory leak checks, and an end-to-end pyspark 
benchmark after G6 / G3 | Required before upstreaming | Partly done 
(`ArrowEvalPythonExecSuite` with ANSI off) |
   
   ---
   
   ## 6. Suggested order
   
   1. **Upstream S1** (apache/spark#59154), then S2 – S4 as SPARK-57468 
follow-ups. Backport them to
      our 4.2 line.
   2. **Gluten G7** (shim and version gating), so G1 – G6 can merge into Gluten 
main without breaking
      Spark 3.4 – 4.1.
   3. **Merge G1, G2, G3, G4, G6 and G13** for Spark builds with SPARK-57468, 
after the G3 benchmark
      and G15 tests.
   4. **S5 + G11** (UDTF), replacing #12276.
   5. **S6 + G5**, so the remaining operator-specific rule goes away.
   6. **G8** (lifecycle rewrite), then G9 and G14.
   7. **S7 / G12** (other Arrow operators), then S8 – S10 and S12 / G10 as 
optimizations.
   
   ## 7. Risks and open points
   
   - **Upstream acceptance of S1 – S4.** Spark has to accept a plugin-facing 
plan API and an ownership
     contract. The UDF-specific follow-ups may need to be generalized (S6) 
before review.
   - **Planning interplay.** Spark's `insertTransitions` and Gluten's 
`InsertTransitions` both run. It
     must be verified that they never insert competing transitions. Gluten 
plans report `Any` child
     requirements to Spark for this reason.
   - **Memory accounting.** Arrow doesn't notify listeners of transfers. Both 
S4 and `adopt()`
     compensate for this manually, which is fragile if Arrow changes its 
behavior.
   - **Grouped and window operators (S7)** need grouped Arrow input. That's a 
bigger Spark change than
     for map-style operators.
   - **Older Spark versions** keep today's code until they're dropped, so 
Gluten carries both paths
     for a while.
   


-- 
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