Repository navigation
[core][spark] Reduce Data Evolution metadata memory in scans and commits - #11156
Open
JustinBinber wants to merge 2 commits into
Open
JustinBinber wants to merge 2 commits into
JustinBinber wants to merge 2 commits into
Conversation
Add opt-in off, lightweight and streaming metadata modes for many-file Data Evolution operations. Reduce eager decoding and copies, plan complete RowID groups incrementally, replay file metadata and reconcile compact conflict summaries before one atomic Snapshot publication. Keep the feature disabled by default, preserve compatibility paths and document remaining memory and local-disk limits.
JustinBinber
marked this pull request as ready for review
November 14, 2026 17:13
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.
Purpose
Spark Data Evolution MERGE and read planning can exhaust Driver heap with many small column files. Metadata accumulates across scan planning, split serialization, task-result decoding and commit preparation; removing one eager collection alone can move the failure later in the same SQL.
Add an opt-in table mode,
data-evolution.metadata-memory-optimization.mode=off|lightweight|streaming(defaultoff). Lightweight reduces eager decoding, copies and overlapping object lifetimes. Streaming additionally sorts/replays metadata, incrementally plans complete RowID dependency groups, and uses compact summaries/external identity reconciliation in the actual commit path. Partial-write tasks produce messages on demand. One atomic Snapshot and existing compatibility contracts are preserved.data-evolution.metadata-memory-optimization.max-disk-sizelimits each metadata store; it is not a global process disk budget.Scope: the planning-to-commit chain required by the reproduced Data Evolution MERGE failure, plus the separately validated controlled wide-table read-planning case. This does not establish repair of the separate production three-million-file SELECT/LIMIT incident or constant memory. Task arrays, retained result bytes, largest messages/groups, RowID summaries and callback/deletion-vector/diagnostic fallback paths remain boundaries.
Capacity and exact correctness
Fixed clean comparison base:
b7cfb397de334d56b8c3eaeb60a2cfa9c9cab627. Candidate:3d64be92a161a12130ede4f2719fb4df4a1b8844. Runtime production/tests/build inputs are unchanged from frozenf17caf4cc27eaa87927fe0fb28385462aecb373d; the later commit only regenerates configuration HTML. Normal existing artifacts are reused with applicable-input and runtime-origin checks, not claimed rebuilt for that documentation commit.With 524288 real two-row base files, Driver 1 GiB and two 512 MiB Executors, clean upstream fails with Driver heap OOM during scan planning and publishes no Snapshot. Same-input/same-resource streaming completes the full MERGE in 740.262 s and publishes only Snapshot 513. No speedup is calculated against the failure. A separate unmodified-upstream oracle (4 GiB Driver/two 1 GiB Executors, disclosed separately) passes five exact bidirectional checks: full rows, unchanged source, old key/RowID pairs, complete RowID domain and inserted RowID domain (1153433 result rows). Seed/source and earlier Snapshot bytes remain unchanged.
Successful-case performance
One 4-core/16-GiB Ubuntu host, Spark 3.5.9/Scala 2.12.18, JDK17 SQL processes; this is not a multiple-worker-node benchmark. At each scale all 16 measured MERGEs and five-check oracles pass. Four balanced Williams blocks, fixed inputs/SQL/heaps/split settings, fresh private copy and JVM per arm, no concurrent build/archive/analysis during timed blocks. Oracles use a separate 2-GiB Driver and are outside timing. Cache state is not identical/cold; balancing reduces but does not eliminate carryover. All samples are retained.
These four samples per mode do not establish p95, significance or general throughput gains. Streaming's first paired 32-file block is +11.42%; its small-case sampled Driver heap is higher. Larger successful-case gains coexist with increased local writes and commit work. The linked results include every sample, CPU/deserialization/GC, heap/RSS, commit duration, shuffle/Spark spill and common-live process I/O. Spark spill excludes connector-local sort/replay; process I/O is sampled and not exact SQL/connector-only I/O, and raw parent/child counters must not be summed. Exact isolated encoding/planning CPU is unavailable.
Separate controlled SELECT/LIMIT acceptance
Frozen wide input: 32768 real two-row base files, 259 business columns, one 128-column partial update, Snapshot 33 and 72089 result rows. The reader projects all business columns plus
_ROW_ID, and verifies actual values, NULLs, domains and counts. At the same 512-MiB Driver/two 512-MiB Executors, clean fixed-b7 fails both LIMIT 20 and full read with Driver Java heap OOM in Manifest decoding/decompression propagated through read planning. Streaming completes LIMIT 20 (9.573720134 s) and full read (82.889682472 s), without Snapshot/input changes. This is an independent controlled case, not a diagnosis of the production exit137 incident. Five exact bidirectional checks run separately with a 2-GiB Driver through each actual tested reader. The initial streaming controller stopped at an unstarted exact-oracle disk preflight; its successful reads were retained and an exact-only continuation completed. No measurement heap was raised and no query was retried.Successful-case timing gives ALL modes a 2-GiB Driver and two 512-MiB Executors. Exactly two reverse-order blocks (
U F S L/L S F U), fresh JVM/query, same frozen physical input and settings, serial execution without backup/cleanup/build/parsing during either whole block. Data inodes and warm OS caches are reused; order reversal does not equalize caches or fully balance carryover. All 16 queries pass the common consumer; independent exact checks are reused once per mode, outside timing. Two samples per mode/query support descriptive ranges only, not significance or p95. Timers include the common validation consumer.Streaming's LIMIT midpoint is +28.88% (about +2.17 s); full read is +2.75%. These negative timing costs are retained. Full-read sampled Driver heap midpoint is approximately 1093.73 MiB upstream / 539.50 MiB lightweight / 381.00 MiB streaming; sampled peaks can miss real peaks. Streaming common-live Driver writes increase by about 120 MiB in both query types. They include runtime/log work and are not exact connector-only bytes. All query event logs report zero Spark shuffle/spill; that does not exclude connector-local sort/replay. Default off has LIMIT +2.38% / full -0.26% midpoint observations, not proof of zero overhead. All raw read samples, CPU/GC/deserialization, per-JVM heap/RSS/I/O, correctness bindings and limitations are included. No throughput claim is made from this single-host experiment.
Validation and reproduction
The controlled SELECT/LIMIT acceptance above is independent of MERGE acceptance. The production three-million-file exit137 incident remains unclassified and is not claimed fixed.