Conversation
…ry usage Build the alloc-accounting allocator wrapper by default (jemalloc stays opt-in) and have each executor periodically log its native memory usage while Comet native plans run: Comet native memory usage: allocated 5412.3 MiB, reserved 3890.0 MiB (16 native plans, 8 memory pools) The first CometExecIterator on an executor starts one daemon thread that logs a single line per interval for the whole executor, plus one more after the last plan finishes. It samples on a timer rather than between batches because a plan rooted at a native shuffle writer, or fed by native scans, can spend its whole run inside one executePlan call. The interval is spark.comet.memory.logInterval (default 10s, 0 disables), read from the executor's SparkConf. The new Native.getMemoryUsage JNI call reads only the allocation counter and the pool registry, and reads pool reservations after releasing the registry lock: CometFairMemoryPool holds its own lock across the JNI acquire from Spark, which can wait for a finishing task whose releasePlan needs the registry lock. Also add a tuning guide section on using the log to size spark.executor.memoryOverhead, update the tracing and memory management guides, and switch the rust-test CI step to cover the jemalloc arm and the build without the feature.
…gistry lock Remove the alloc-accounting cargo feature: the accounting allocator now wraps whichever backend the build selects, unconditionally. The alloc_overhead bench measures the wrapper against the bare backend in one binary, since there is no longer a build without it. releasePlan read the remaining pools' reservations while holding the pool registry lock on every plan, not only when tracing. With fair_unified that can deadlock: the pool holds its lock across a Spark acquire that waits on another task, whose releasePlan needs the registry lock. Every reader now copies the pools out and reads them after releasing it, and releasePlan reads nothing unless tracing. A malformed spark.comet.memory.logInterval now disables the log with a warning instead of failing every Comet task, and a value set only in the session is reported as ignored. The log also warns when the executor's native footprint (untracked native memory plus Spark's off-heap memory in use) exceeds spark.memory.offHeap.size plus the memory overhead.
The fraction does not reserve room in Spark's off-heap pool: greedy_unified ignores it, and fair_unified applies it per task, where Spark's own per-task limit is tighter. Point the warning and the tuning guide at the memory overhead instead.
sunchao
approved these changes
Sep 23, 2026
sunchao
left a comment
Member
There was a problem hiding this comment.
Summary
Reviewed f6526ba759761848d7d2faac93221c9e284cae31 using the Comet FFI and memory review guides. No reproducible introduced or materially worsened P1/P2 findings were identified. Full integration and workload-performance validation are still incomplete.
- Prior state and problem: Native allocation accounting was opt-in, and observing it required tracing. Pool reservations alone do not describe all native allocations, so executor OOM investigations lacked a readily available allocation measurement. Registry readers also held the registry lock while querying pool reservations.
- Design approach: The selected Rust allocator is always wrapped for accounting. A new JNI snapshot reports allocation, distinct-pool reservations, pool count, and plan count. A single executor daemon periodically logs those figures and evaluates an advisory native-memory threshold.
- Correctness / compatibility analysis: The snapshot reads process-wide state rather than execution-context pointers. Pool references are cloned under the registry lock, and reservations are read after releasing that lock. Unregistering no longer reads reservations outside tracing. The new JNI entry point uses the existing exception/panic boundary. This change does not alter expression support levels, query semantics, or Spark memory acquisition rules. The allocator module's five unit tests passed in an isolated harness against the reviewed source, and
git diff --checkpassed. Full native/JVM integration and workload benchmarks were not run locally. Native-library and Rust CI jobs were still pending at review completion. Completed checks were successful, while macOS, benchmark, Spark SQL, and Iceberg jobs were skipped. - Key design decisions: Accounting applies to every allocator backend, even when memory logging is disabled. Logging uses executor configuration and a timer so long native calls can still be sampled. Shared pools are counted once, and reporting does not enforce a new memory budget.
- Implementation sketch:
AccountingAllocatorupdates the allocation balance. The pool registry produces a deduplicated snapshot forNative.getMemoryUsage.CometExecIteratorstarts the scheduler and formats the sampled values, with focused native and Scala tests covering accounting, counts, lock scope, configuration, and message construction. - Behavioral changes worth calling out: The
alloc-accountingCargo feature is removed because accounting is unconditional.spark.comet.memory.logIntervaldefaults to 10 seconds and is read from the executor configuration. Setting it to zero disables periodic logging. The reported balance is an allocation measure, not RSS, and the warning remains advisory. - Suggested improvements: No P1/P2 review comments proposed.
andygrove
marked this pull request as ready for review
September 23, 2026 19:45
andygrove
enabled auto-merge
September 23, 2026 19:50
This was referenced Sep 23, 2026
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Sep 23, 2026
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?
Part of #4576.
Rationale for this change
Comet's memory pools only see what operators reserve. When an executor is OOM-killed, nothing in its logs says how much native memory Comet was actually holding. #5934 added an allocator wrapper that counts what the Rust allocator hands out, but it was an opt-in cargo feature that only reported through tracing, so the number was never there when needed.
This PR makes the count part of every build and logs it next to the pool reservations. Every executor log then shows the gap between the two, which is what
spark.executor.memoryOverheadhas to cover. The executor also warns when that gap looks larger than its container allows.What changes are included in this PR?
Allocation accounting is always on. The
alloc-accountingcargo feature is removed, and the accounting allocator now wraps whichever backend the build selects (system, jemalloc or mimalloc).alloc_overheadmeasures the wrapper against the bare backend in one binary, since there is no longer a build without it. The rust-test CI step that exercised the feature combinations now lints and tests the jemalloc backend instead.Executor memory usage log. The first
CometExecIteratoron an executor starts one daemon thread. While native plans are running, it logs one line per interval for the whole executor, plus one more after the last plan finishes:executePlancall. For example, a plan rooted at a native shuffle writer consumes all of its input before returning.Native.getMemoryUsage, which reads only the allocation counter and the pool registry, never an execution context. It counts a pool shared by several plans once.spark.comet.memory.logInterval(default 10s, 0 disables). One log serves every session on the executor, so the setting is read from the executor's SparkConf.Container warning. The log thread warns, once each time the threshold is crossed, when untracked native memory (
allocated - reserved) plus Spark's off-heap memory in use (which includes Comet's reservations) exceedsspark.memory.offHeap.sizeplus the executor's memory overhead. The overhead is computed the way Spark sizes the default container:spark.executor.memoryOverheadif set, otherwisespark.executor.memoryOverheadFactorofspark.executor.memorywith a minimum ofspark.executor.minMemoryOverhead. The warning is skipped in local mode and in on-heap mode.Pool registry lock fix.
releasePlanread the remaining pools' reservations while holding the pool registry lock on every plan, not only when tracing was on.CometFairMemoryPoolholds its own lock across the JNI acquire from Spark, which Spark can park until another task frees memory. A finishing task frees its reservations only afterreleasePlanhas taken the registry lock to unregister, so this could deadlock underfair_unified. Every reader now copies the pools out under the lock and reads their reservations after releasing it: the memory log, tracing, and the per-thread total. Unregistering no longer reads any reservation, andreleasePlanreads the remaining total only when tracing.Docs. The tuning guide gets a section on sizing
spark.executor.memoryOverheadfrom the log. The tracing and memory management contributor guides are updated now that the counter is always present.How are these changes tested?
Native unit tests cover:
CometExecIteratorLifecycleSuitecovers:TPC-H SF100 on a 32-core Linux box (Spark 4.1.1, 2 executors x 8 cores, glibc allocator, no jemalloc), 3 runs of each build alternated against the base commit:
perfprofile attributes this to the wrapper's thread-local accesses, which go through__tls_get_addrbecauselibcomet.sois loaded withdlopen. This is tracked in perf: allocation accounting wrapper costs ~5% on TPC-H Q21 from thread-local lookups in the dlopened library #6165 for a separate PR. No other query is outside noise.The self-review for this revision was done with the
review-comet-prskill.