andygrove opened a new issue, #6063:
URL: https://github.com/apache/datafusion-comet/issues/6063
### What is the problem the feature request solves?
On-heap mode is not a production configuration.
`spark.comet.exec.onHeap.enabled` defaults to
`false`, lives in `CATEGORY_TESTING`, and `CometDriverPlugin.init` disables
Comet outright when
off-heap is off and the flag is not set. Comet's own suites run off-heap:
`CometTestBase` sets
`spark.memory.offHeap.enabled=true` with 2 GiB
(`CometTestBase.scala:85-86`), which makes the
`spark.comet.exec.onHeap.enabled` and `spark.comet.memoryOverhead` it also
sets inert. The only
things that reach the on-heap path are Spark's own SQL suite and the Iceberg
suites, which set
`ENABLE_COMET_ONHEAP=true` because Spark's test harness does not configure
off-heap memory.
That path nevertheless carries a complete second memory-accounting
implementation, and the
accounting it performs does not protect anything.
**Comet cannot honestly join Spark's ledger in on-heap mode.**
`CometTaskMemoryManager`'s
`NativeMemoryConsumer` is hardcoded to `MemoryMode.OFF_HEAP`
(`CometTaskMemoryManager.java:106`).
Spark sizes the off-heap execution pool from `spark.memory.offHeap.size`
alone
(`MemoryManager.scala:61-66`, byte-identical on 3.4.3, 3.5.9, 4.0.4 and
4.1.3), and that is 0 when
off-heap is disabled, so `acquireExecutionMemory` grants nothing and every
native `try_grow` would
fail. Registering as an `ON_HEAP` consumer instead would be a category
error: Comet's bytes are in
the Rust heap, so Spark would evict cached blocks and spill its own sorters
to make room for memory
that is not on the heap, while doing nothing about the native RSS that
actually gets an executor
OOM-killed. `parse_memory_pool_config` therefore rejects the unified pools
in on-heap mode
(`config.rs:63-67`) and falls back to DataFusion's own pools, sized from
`spark.comet.memoryOverhead`.
**That budget is a leftover, not a design.** `spark.comet.memoryOverhead`
was in the initial commit
(PR #1, February 2024), a month before `CometTaskMemoryManager` and the
unified pool existed
(PR #83). It survived because deleting it broke the tests: PR #1062 required
off-heap and removed
the on-heap branch, and PR #1177 restored it a month later with the
rationale "after #1062 we have
not been running Spark tests for native execution". PR #2554 then made
on-heap opt-in and
testing-only, closing #2342 ("Remove on-heap memory pools") without removing
them.
**And it does not bound what a container cares about.** Per the analysis in
#6054, the driver
plugin's attempt to fold `spark.comet.memoryOverhead` into
`spark.executor.memoryOverhead` is inert
on 3.4, 3.5 and 4.0, because `ResourceProfileManager` has already
materialized and cached the
default profile by the time plugins initialize. Separately, on-heap sizes
the native pool *and* the
JVM shuffle pool from that same figure —
`spark.comet.shuffle.jvm.memoryFactor` defaults to `1.0` —
so the two together can allocate roughly twice what is declared.
What the accounting does buy is complexity, all of it on a path no user runs:
- Six of the nine `MemoryPoolType` variants (`greedy`, `fair_spill`, and
their `_task_shared` and
`_global` pairings) plus `memory_limit_per_task`, which no off-heap pool
reads.
- Four configs: `spark.comet.memoryOverhead`,
`spark.comet.exec.onHeap.memoryPool`,
`spark.comet.shuffle.jvm.memoryFactor`,
`spark.comet.shuffle.jvm.memoryWaitTimeout`.
- Most of `CometBoundedShuffleMemoryAllocator` (352 lines). Because on-heap
mode shares one
executor-wide bounded allocator across all tasks, and #5493 removed the
cross-task force-spill
that used to paper over contention on it, that class now carries a
blocking-allocation protocol:
per-thread retention accounting, two fail-fast liveness predicates, a
5-minute timeout,
30-second progress logging, and cooperative task-kill polling. Eight tests
in
`CometDiskBlockWriterSuite` exist solely to cover it.
### Describe the potential solution
Stop accounting in on-heap mode rather than accounting badly.
1. `parse_memory_pool_config` returns `MemoryPoolType::Unbounded` whenever
off-heap is disabled.
Delete the six on-heap pool variants and their arms in
`memory_pools/mod.rs`.
2. Delete `memory_limit_per_task` from `MemoryConfig`, `Native.createPlan`
and `jni_api.rs`. Nothing
else reads it.
3. Reduce `CometBoundedShuffleMemoryAllocator` to an unbounded page table
over
`UnsafeMemoryAllocator` — the pages must stay `Unsafe`-allocated in
either memory mode because
`SpillWriter` hands their addresses to `writeSortedFileNative` for Rust
to dereference — and
rename it accordingly. With no shared budget there is nothing to wait
for, so `allocateBlocking`
collapses into `allocate` and leaves `CometShuffleMemoryAllocatorTrait`,
and the allocator
becomes per-task like the off-heap one instead of an executor-wide
singleton.
4. Delete the four configs above and the now-unused
`getCometMemoryOverhead*` and
`getCometShuffleMemorySize` helpers.
5. Drop `.set("spark.comet.memoryOverhead", ...)` from the four `dev/diffs`
patches, regenerating
them through the documented flow rather than editing them by hand.
6. Update the tuning guide, `configs.md`, and the memory management page,
which currently has to
explain the on-heap pool types in order to exclude them.
`spark.comet.exec.onHeap.enabled` stays: it is still the switch that keeps
Comet off in on-heap mode
unless a test opts in.
### Additional context
**What this gives up.** The on-heap pool is the only bound the Spark SQL
suite runs under, so any
memory-pressure-driven native spill it triggers today stops happening, and
nothing caps Comet's RSS
in those jobs. Given the suite's data sizes the spill coverage is probably
near zero already, but
that is an assumption, not a measurement. Row-count-driven JVM shuffle
spilling
(`spark.comet.shuffle.jvm.spillThreshold`) and
`spark.comet.shuffle.native.maxBufferBytes` both
still trigger independently of any pool, and the off-heap "memory pressure
spills only writers of
the requesting task" test in `CometDiskBlockWriterSuite` keeps covering the
spill policy itself. If
CI memory does regress, the cheap recovery is a single executor-wide
`GreedyMemoryPool` with a fixed
cap, which is one arm in `parse_memory_pool_config` rather than the whole
subsystem.
**Two alternatives were considered and rejected.**
*Set `spark.memory.offHeap.size` in the Spark SQL tests while leaving
`spark.memory.offHeap.enabled`
false.* This is legal and unvalidated on every supported version:
`MemoryManager` sizes the off-heap
execution pool from the size alone, `tungstenMemoryMode` is the only thing
gated on the flag, and
nothing in `SparkConf.validateSettings` objects. Comet's native pool would
then run the real unified
path under the Spark SQL suite, which is a genuine coverage gain. But it
does not unify the JVM
shuffle allocator: `TaskMemoryManager.allocatePage` asserts
`consumer.getMode() == tungstenMemoryMode`
and returns `long[]` heap pages in on-heap mode, which cannot be handed to
native code, so that
allocator still forks on page provenance. It also means regenerating the
diffs against a different
memory model and accepting that `Utils.checkOffHeapEnabled` returns 0, so
the size never reaches the
container — a mechanism that works only because it is a test harness.
*Set `spark.memory.offHeap.enabled=true` in `SharedSparkSessionBase`.* This
is the only option that
truly leaves one code path, but it flips Spark's own Tungsten to off-heap
for the entire Comet test
run, so Spark's operators change behavior and the diffs absorb the fallout
across four versions.
This is what PR #1177 backed out of in December 2024.
**Sequencing.** #6054 removes the driver plugin's
`spark.executor.memoryOverhead` mutation and
`shouldOverrideMemoryConf`, which this change also touches. That should land
first.
Related: #5212, #2342, #6050.
--
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]