Conversation
… the container diagram The 'What the container sees' diagram grouped consumers by whether Spark can see them, which forces one box per (region, accountant) pair and cannot show that a native reservation is charged against spark.memory.offHeap.size while occupying native heap. Redraw it with physical regions on the left, budget authorities on the right, and an arrow between them. Also adds the consumers the old diagram left out: the JVM heap split into Spark and Comet objects, and C libraries that allocate outside Rust's global allocator.
Forcing white and light-grey subgraph fills left the subgraph titles white on white when GitHub renders the fence in dark mode; only the classDef nodes carried an explicit text colour. Drop the five style directives so the cluster background and its label track the viewer's theme, and move the unaccounted class from pale yellow to orange, which the default light theme's pale yellow cluster background was swallowing.
mermaid reserves a single line of height for a cluster label, so a title that wraps is drawn over the first node inside the cluster. mmdc wraps later than the browser-side renderer does, so the SVG check passed while the published page overlapped. Cut the five titles to two or three words, move what they said into the paragraph above, and set wrappingWidth so the node labels stop breaking mid-phrase.
|
Checked the redraw against the sources. Separating region from authority is the right axis and worth landing. Four things I would change first. 1. The reservation-ceiling label is false for one pool type and imprecise for the other.
Neither is usually the binding constraint. 2. 3. The single 4. Scope is missing. The pool, the reservation and the fair share are all per task attempt ( Two things that predate this PR but sit next to the diagram:
An alternative diagram covering all of the above is in the next comment. |
|
An alternative to the diagram in this PR, covering the four points above. %%{init: {'flowchart': {'wrappingWidth': 320}}}%%
flowchart LR
subgraph CG["Executor container, cgroup memory.max"]
direction TB
subgraph NAT["Native heap, Rust"]
direction TB
NRES["Declared reservations<br>sort, aggregate, joins, shuffle writer"]
NUND["Undeclared<br>kernels, builders, Parquet buffers, FileMetadataCache, tokio, FFI batches"]
NOPAQ["Outside GlobalAlloc<br>libzstd, aws-lc-sys, libhdfs, fragmentation, padding"]
end
subgraph OFF["JVM off-heap, Unsafe and Java Arrow"]
direction TB
TUNG["Spark Tungsten pages"]
JSH["Comet shuffle pages"]
ARW["CometArrowAllocator, no limit"]
end
subgraph HEAP["JVM heap, spark.executor.memory"]
direction TB
UNIF["Unified region, spark.memory.fraction 0.6"]
USER["User memory, the other 0.4<br>Comet plans and vectors, bounded only by -Xmx"]
end
subgraph REST["Neither heap"]
direction TB
NONHEAP["JVM non-heap<br>metaspace, code cache, stacks, Netty"]
PAGEC["Page cache from spill and shuffle files"]
end
end
subgraph GATE["Admission gate"]
direction TB
G1["Comet, fair_unified only<br>memory_limit / num_consumers"]
G2["Spark, per task attempt<br>pool / numActiveTasks"]
G0["No gate"]
end
subgraph BUD["Budget"]
direction TB
OFFEX["Off-heap execution pool, per executor"]
OFFST["Off-heap storage pool, storageFraction"]
ONPOOL["On-heap execution and storage pools"]
NOONE["No budget<br>memoryOverhead is the only slack"]
end
NRES -->|"try_grow over JNI"| G1
G1 --> G2
TUNG --> G2
JSH --> G2
UNIF -->|"on-heap mode"| G2
G2 --> OFFEX
G2 --> ONPOOL
OFFEX <-->|"borrow, evict"| OFFST
NUND --> G0
NOPAQ --> G0
ARW --> G0
USER --> G0
NONHEAP --> G0
PAGEC --> G0
G0 --> NOONE
classDef budgeted fill:#c7ecd0,stroke:#2f6b46,color:#000
classDef gate fill:#fff2b2,stroke:#8a7420,color:#000
classDef unbudgeted fill:#f6c69a,stroke:#8a4b1f,color:#000
class NRES,TUNG,JSH,UNIF,OFFEX,OFFST,ONPOOL budgeted
class G1,G2 gate
class NUND,NOPAQ,ARW,USER,NONHEAP,PAGEC,G0,NOONE unbudgeted
Three columns, and every byte takes the same shape of path: a physical region, the gate that admits it, the budget it draws down. Making "no gate" an explicit node is what keeps the arrows aligned, and it also states the thing the current diagram can only imply. What it adds over the version in the PR:
Two things deliberately left to prose rather than drawn. The native heap splits three ways by which counter can see it: reservations show in the pool and in If that is still too much for one figure, the natural split is to move the gate and budget columns into a separate "what a single reservation passes through" diagram under Where Comet's budget comes from, and leave this one region-to-authority only. I have not rendered the fence. |
Which issue does this PR close?
N/A — no issue; this is a documentation-only redraw.
Rationale for this change
The "What the container sees" diagram in the memory management guide groups consumers by whether Spark can see them. That forces one box per (region, accountant) pair, and the one relationship it cannot express is the one that makes Comet's memory model confusing: a native reservation is charged against
spark.memory.offHeap.sizewhile occupying native heap. Physical location and accounting authority are independent axes, and a diagram that collapses them into one encourages the reading that native memory is budgeted byspark.executor.memoryOverhead— which it is not.memoryOverheadis a container-sizing term, not a Comet budget; Comet's ceiling isspark.memory.offHeap.size * spark.comet.exec.memoryPool.fraction.What changes are included in this PR?
Redraws the diagram with physical regions on the left, budget authorities on the right, and arrows between them, so a byte's region and its budget can differ and you can see it.
Also picks up what the old diagram omitted:
aws-lc-sys), which the "Accounting gap" section already discusses,NativeMemoryConsumer.spill()returns0.Colour is redundant with the arrow target, so the diagram survives greyscale printing and colour-blind readers. The subgraph backgrounds are left to the mermaid theme so the fence stays legible in GitHub's dark mode as well as light.
%%{init: {'flowchart': {'wrappingWidth': 420}}}%% flowchart LR subgraph POD["Executor container"] direction TB subgraph NAT["Native heap"] direction TB NRES["Declared operator reservations<br>sort, grouped aggregate, joins, shuffle writer<br>ceiling = spark.comet.exec.memoryPool.fraction of spark.memory.offHeap.size"] NUND["Everything else Rust allocates<br>expression kernels, array builders, decompression,<br>Parquet and object_store metadata, tokio,<br>C libraries outside Rust's global allocator"] end subgraph OFF["JVM off-heap"] direction TB TUNG["Spark Tungsten pages"] JSH["Comet JVM shuffle pages<br>CometUnifiedShuffleMemoryAllocator"] ARW["Comet JVM Arrow buffers<br>CometArrowAllocator: a RootAllocator with no limit"] end subgraph HEAP["JVM heap"] direction TB SOBJ["Spark objects"] COBJ["Comet objects: plans, vectors, iterators"] end subgraph REST["Rest of the process"] direction TB NONHEAP["JVM non-heap<br>metaspace, code cache, thread stacks, Netty"] MISC["Page cache from spill files<br>fragmentation, padding, jemalloc retained pages"] end end OFFPOOL["Spark off-heap execution pool<br>TaskMemoryManager, spark.memory.offHeap.size<br>inside the container limit, not headroom on top of it"] ONPOOL["JVM heap accounting<br>unified memory manager, spark.executor.memory"] NOONE["Accounted by nobody<br>no budget, no backpressure;<br>spark.executor.memoryOverhead is the only slack"] NRES -->|"charged over JNI by CometTaskMemoryManager"| OFFPOOL TUNG --> OFFPOOL JSH --> OFFPOOL SOBJ --> ONPOOL COBJ --> ONPOOL NUND --> NOONE ARW --> NOONE NONHEAP --> NOONE MISC --> NOONE classDef acct fill:#c7ecd0,stroke:#2f6b46,color:#000 classDef heapacct fill:#cfe4fb,stroke:#2f5680,color:#000 classDef unacct fill:#f6c69a,stroke:#8a4b1f,color:#000 class NRES,TUNG,JSH,OFFPOOL acct class SOBJ,COBJ,ONPOOL heapacct class NUND,ARW,NONHEAP,MISC,NOONE unacctThe fence above is the diagram as committed, rendered by GitHub. An SVG pasted into a body is not rendered — GitHub's image proxy will not serve
image/svg+xml— so this is the closest thing to the published image.How are these changes tested?
Documentation only, no code paths touched.
The diagram was extracted from the committed markdown and rendered with the pinned
@mermaid-js/mermaid-cli@11.17.0anddocs/puppeteer-config.json, the same pairdocs/source/conf.pygives the site build, so it draws rather than publishing as a diagram-shaped hole (#6062).dev/ci/check-mermaid.pyin preflight is the authoritative check; it cannot run on this macOS box, where Chrome times out launching under the script's piped stdio and all three fences fail identically, including the two this PR does not touch.npx prettier docs/source/contributor-guide/memory_management.md --checkpasses.