andygrove opened a new issue, #6727:
URL: https://github.com/apache/datafusion-comet/issues/6727
### What is the problem the feature request solves?
Sizing `spark.executor.memoryOverhead` for Comet is a guess made before the
job runs. The tuning guide says the memory Comet allocates without reserving it
(per-batch working memory, Parquet and decompression buffers, allocator
fragmentation and retained pages, JVM-side Arrow buffers) has to fit in the
overhead, and that executors that outgrow it are killed with exit code 137.
Measurements in #4576 found jemalloc's resident memory at 6x to 8x `allocated`
at the median, so the right overhead depends on the workload and is hard to
know up front. Operators over-provision every executor for the worst case, or
under-provision and lose executors mid-query.
Spark 4.2.0 adds a built-in
`org.apache.spark.scheduler.cluster.k8s.ExecutorResizePlugin` (SPARK-55432)
that uses Kubernetes in-place pod resize (beta in 1.33, GA in 1.35) to grow an
executor's memory without restarting it. This looks like a good fit for the
untracked part of Comet's footprint, and nobody has looked at it from Comet's
side yet.
### What the Spark plugin does
Read from `branch-4.2` of `ExecutorResizePlugin.scala`:
- It runs in the driver, registered through `spark.plugins`. Despite the
docs showing `spark.kubernetes.executor.resizeInterval` as `0s`, the config
default in code is `1m`.
- Every interval it reads each executor container's memory usage from the
Kubernetes metrics API (metrics-server) and compares it with the container's
memory limit.
- If `usage > resizeThreshold * limit` (default `0.9`), it patches the pod's
`resize` subresource, setting both the memory request and limit to `limit * (1
+ resizeFactor)` (default `0.1`).
- It only grows memory. There is no scale-down, and 4.2.0 has no cap. A
`resizeMaxMemory` cap and support for the `deployment` pods allocator are in
apache/spark#59180. In 4.2.0 the plugin only starts with the `direct` pods
allocator.
- Spark's own pools are not resized. Heap and `spark.memory.offHeap.size`
are fixed when the JVM starts, so the extra container memory is headroom for
allocations that no Spark pool accounts for. That is exactly the memory Comet
cannot size ahead of time.
### Why this might help Comet
- A smaller baseline overhead, with the plugin as a safety net that grows it
on demand, instead of sizing every executor for the worst case.
- Fewer exit-137 executor losses when Comet's untracked memory exceeds a
static overhead.
- It needs no Comet code to try. Comet already documents `memoryOverhead` as
the place this memory lives, so the first step is just measuring whether the
combination works.
### Open questions and likely gaps
1. **Reaction time.** The plugin polls every minute by default and grows 10%
of the limit per step. Native memory in a sort, join, or shuffle can climb
faster than that. How often does an executor die before the plugin reacts, and
does a shorter interval or larger factor fix it?
2. **What the plugin measures.** It sees the container working set from
metrics-server, which includes memory the allocator retains but could return,
and it never shrinks. A long-running application could ratchet its limit up
after one bad stage and keep it. Comet could reduce this by purging or decaying
allocator pages between plans, which depends on the allocator work in #6167 and
#4576.
3. **A stale overhead in the warning.** `CometExecIterator` computes the
container's native budget from `spark.executor.memoryOverhead` (or the derived
default) and warns when native memory exceeds it. After an in-place resize the
real limit is larger than that, so the warning can fire on an executor that is
fine. Reading the actual cgroup limit (`memory.max` on cgroup v2) instead of
the conf would be correct with and without resizing.
4. **A Comet-side trigger.** The executor knows more than metrics-server
does: `RssAnon` (proposed in #6167), `allocated`, `reserved`, and the pool
state. A Comet `SparkPlugin` could request a resize from the executor, either
by patching its own pod (needs RBAC for `pods/resize`) or by sending the
request to the driver over the plugin RPC channel and having the driver patch
it. This would react faster and use a better signal than the generic plugin, at
the cost of more code. It is worth building only if the experiment shows the
generic plugin is too slow or too coarse.
5. **Scale down.** The Spark plugin never gives memory back. Lowering a
memory limit in place is only safe once usage has dropped below the new limit,
so a Comet-aware plugin would need to coordinate with allocator purging. This
is probably out of scope for a first pass.
6. **Prerequisites and failure modes.** The cluster needs Kubernetes 1.33+
(1.35 for GA), metrics-server, and node capacity. A resize can be deferred or
infeasible if the node is full, and the failure is only logged by the plugin.
The docs should say what the user can expect when that happens.
7. **Interaction with other memory work.** This is complementary to #5116
(per-stage ResourceProfiles), which changes what is requested at stage
boundaries, while resize changes it during a stage. It also overlaps with the
circuit-breaker idea in #4576, which is about acting on RSS inside the process.
### Describe the potential solution
1. **Experiment first.** On a Kubernetes 1.35 cluster (kind or minikube is
enough) with Spark 4.2.0 and a current Comet build, run a TPC-H or TPC-DS
subset that is known to need a large untracked footprint, with
`spark.executor.memoryOverhead` set well below what the memory usage log says
is needed, in three configurations:
- low overhead, no plugin
- low overhead,
`spark.plugins=org.apache.spark.scheduler.cluster.k8s.ExecutorResizePlugin`
- correctly sized overhead, no plugin
Record whether the query completes, the number of executor losses and
exit codes, the resize events and time from threshold crossing to resize, final
limit versus peak `RssAnon`, wall-clock time, and total memory-seconds. Repeat
with a shorter `resizeInterval` and a larger `resizeFactor`.
2. **Fix the stale limit in the native memory warning** (open question 3),
independent of the experiment's outcome.
3. **Document it** in the tuning guide and Kubernetes guide if the
experiment shows it helps: when to use it, the recommended settings, the
Kubernetes version and metrics-server prerequisites, and the `direct` allocator
limit in 4.2.0.
4. **Decide on a Comet-side plugin** (open question 4) from the experiment
results.
### Additional context
- Spark: SPARK-55432 (`ExecutorResizePlugin`), SPARK-59918 (`deployment`
allocator support), [Spark 4.2.0 release
notes](https://spark.apache.org/releases/spark-release-4-2-0.html), [Kubernetes
1.35 in-place resize
GA](https://kubernetes.io/blog/2025/12/19/kubernetes-v1-35-in-place-pod-resize-ga).
- Related Comet issues: #6167 (report `RssAnon` in the memory usage log),
#4576 (allocator-level accounting and the RSS circuit breaker), #5116
(ResourceProfiles for native versus JVM stages), #6190 (Kubernetes guide
example never enables Comet).
--
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]