fix: count pool overcommit as untracked memory in the native memory usage log - #6271
Conversation
…sage log The executor's memory usage log reports allocated and reserved, and both the memory tuning guide and the container warning read the difference as native memory that has to fit outside spark.memory.offHeap.size. Since apache#6128 a pool's reserved() also includes overcommit, the bytes it records when Spark grants less than a grow asked for. Spark's off-heap pool does not account for those bytes, so while a pool was overcommitted the untracked figure and the warning's footprint both came out low by the amount of the overcommit. The log's reserved figure now leaves out each pool's overcommit, read through the wrappers that create_memory_pool puts around the Comet pools. Tracing's comet_memory_reserved_total is unchanged. Closes apache#6260.
sunchao
left a comment
There was a problem hiding this comment.
Summary
- Prior state and problem: The memory log subtracted overcommitted reservations even though Spark had not granted those bytes, understating untracked memory and the container warning’s footprint.
- Design approach: Subtract each pool’s overcommit from the reservations reported by
getMemoryUsage, while preserving full reservation totals for tracing. - Correctness / compatibility analysis: The calculation matches Spark’s grant-based accounting in sources for 3.4.3, 3.5.9, 4.0.4, 4.1.3 and
branch-4.2. Both unified pool types are handled. Pool deduplication, reservation behavior and JNI result layout remain intact. No introduced P1/P2 issues found within this review. - Key design decisions: Registry entries precede the optional logging wrapper, so the downcasts reach production pools. Saturating subtraction handles concurrent sampling without underflow. Additional work consists of wrapper lookups and an atomic read during periodic sampling, with no additional reservation-path JNI calls.
- Implementation sketch:
create_poolshares construction between production and fake-Spark tests. Small accessors expose overcommit through the existing wrappers without introducing another accounting layer. - Behavioral changes worth calling out: During overcommit, logged
reserveddecreases and calculated untracked memory increases. Tracing retains its previous meaning. Documentation and warning wording explain the distinction. - Suggested improvements: None meeting the P1/P2 reporting threshold.
Reviewed the complete nine-file diff from base 605051ad239ef704f5f25d67910a446a6b6d7c70 to head 236efa250d13ad9b69f53306dbf342ecd687b6b0. Applied review-comet-pr, review-comet-memory-pr and review-comet-ffi-pr. The PR is not a draft. Snapshot and live discussion checks found no existing reviews, comments or threads.
Exact-head CI: 27 checks passed, 29 skipped, none failed or pending. Inspected logs confirm all three new regression tests passed within 1,740 passing Rust tests. The execution job passed 1,097 tests, including the existing memory-log and warning tests.
Validation limits: The focused local Cargo test failed before execution because hdfs-sys could not find jni.h in the installed Java runtime. Runtime validation therefore relies on the inspected exact-head CI logs. Spark’s SQL suites, Iceberg suites and macOS checks were skipped. No project code was changed.
Resolve the conflicts with the JVM Arrow figures now in the memory usage log. The container warning and the memory tuning guide describe untracked memory as what Spark does not account for, native and JVM Arrow, and the warning's scaladoc keeps both the JVM Arrow and the overcommit reasoning.
Resolve the conflict with apache#6261 on the memory_pools import in jni_api.rs by importing both overcommit and PlanMemoryPool. The registry still holds the pool that create_memory_pool returns rather than the PlanMemoryPool that wraps it, so the memory usage log still reads each pool's overcommit through the task-shared and tracking wrappers.
Which issue does this PR close?
Closes #6260.
Rationale for this change
The executor's memory usage log reports
allocatedandreserved. The memory tuning guide reads the difference as native memory that has to fit outsidespark.memory.offHeap.size, and the container warning adds the same difference to Spark's off-heap usage.reservedwas the sum of the pools'reserved(), and since #6128 that also includes overcommit, the bytes a pool records when Spark grants less than agrowasked for. Those bytes are real allocations, but Spark's off-heap pool does not account for them, so the log subtracted them as if it did. While any pool was overcommitted, the untracked figure and the warning's footprint both came out low by the amount of the overcommit.What changes are included in this PR?
reservedfigure leaves out each pool's overcommit, so it counts only what Spark has granted. The overcommitted bytes are still inallocated, soallocated - reservednow counts them as untracked. The formula, the log line and the tuning guide's sizing recipe don't change, and the container warning picks up the fix without a change of its own.memory_pools::overcommitreads a pool's overcommit through the wrappers thatcreate_memory_poolputs around the Comet pools: the task-shared pool, then DataFusion'sTrackConsumersPool, using DataFusion'sdowncast_refondyn MemoryPool.create_memory_poolnow hands off to acreate_poolthat takes the connection to Spark as a closure, so that tests build pools the way production does, against a fake Spark. That replaces the two pools' JNI constructors.comet_memory_reserved_totalstill includes overcommit. Tracing compares it againstnative_allocatedto find allocations that no pool reserved, and a pool did reserve these.reservedbullet and the memory management guide's overcommit bullet say the same.#6250 changes the same formula, to
allocated + (jvm - imported) - reserved. It needs nothing for this, sincereservedis the figure that changed. The two only conflict in text, in the warning's scaladoc and the first line of its message, and in the tuning guide next to the block #6250 rewrites, so whichever lands second needs a small rebase.How are these changes tested?
memory_usage_leaves_out_what_spark_did_not_grantinjni_api.rsregisters agreedy_unifiedpool built through the same path ascreate_memory_pool, with a fake Spark that grants 4096 bytes. A 6144-bytegrowleaves the log's figure at 4096 while tracing's total is 6144, and shrinking the reservation repays the overcommit before the log's figure moves.overcommit_is_read_through_the_wrappers_of_each_pool_typecoversgreedy_unifiedandfair_unified, anda_pool_that_takes_nothing_from_spark_has_no_overcommitcovers the unbounded pool used in on-heap mode.reserved()as before, not looking through the task-shared pool, and dropping the fair pool case.CometExecIteratorLifecycleSuitetests of the log and the warning pass unchanged, as do the rest of thedatafusion-cometlib tests.