Repository navigation
perf(spark): append rows straight into the Arrow batch (FileFormat connector) - #45
Merged
gitRasheed merged 6 commits intoOct 30, 2026
Merged
Conversation
VortexOutputWriter copied every row into a list and converted the list to Arrow per value when the batch was full. Each row is now appended straight into the Arrow vectors through one typed field writer per column, so strings are copied once and decimals of up to 18 digits are written from the unscaled long. Vectors start at 64 slots and grow, so 1-row batches no longer allocate Arrow's default capacity. The schema is exported once per writer and released on close; each batch used to export it again and never release it. A write that fails part-way through a row marks the writer failed: later writes throw, and close releases everything and throws instead of finishing the misaligned batch. Memory still allocated when the allocator closes now fails close instead of being logged at debug. Signed-off-by: gitRasheed <[email protected]>
…ailing The previous commit made close fail when the writer's allocator still held memory. Native code can release a batch after close has finished the file, and failing then would fail a task whose output is complete. Close now logs a warning and leaves the allocator open, so a late release is still freed: Arrow marks an allocator closed even when its close throws, and with assertions on, releasing into it throws and keeps the memory. Tests still assert that nothing is left, through the allocator() accessor. Signed-off-by: gitRasheed <[email protected]>
Each batch used to export the schema again and never release it. The writer now exports it once and releases it on close, so the test passes; it reads the allocator through the package-private accessor instead of reflection. Signed-off-by: gitRasheed <[email protected]>
The native writer holds one-row batches until it has enough bytes to write; with 10,000 rows that was every batch, about 1.2M Arrow buffer ledgers, and the 512 MB test JVM ran out of heap. 2,000 rows still cover every null pattern, and a batch size of 300 still crosses batch boundaries and grows the vectors past their first 64 slots. Signed-off-by: gitRasheed <[email protected]>
Spark's concurrent partition writer closes a task's writers one after another when the task aborts and stops at the first close that throws. close() rethrew an earlier write failure, so the writers after the failed one were never closed and kept their Arrow memory, streams and native writers. After a write failure, close() now releases everything and returns, logging any cleanup failure. The truncated file still can't be committed: Spark commits only after every write returned, and write() refuses every row once one has failed. Signed-off-by: gitRasheed <[email protected]>
The compat suites compiled only test/java, so the Scala format the abort test writes through was missing and the write failed to find its data source before opening any writer. With the format present, Spark 4.2 hung instead: Arrow's out-of-memory exception doesn't serialize, and the SerializationDebugger Spark falls back to needs java.io opened, which Spark's launcher does but the test JVM didn't. Its failed initialization killed the executor thread before it reported the task failure. The test now reports the exception it caught when no writer opened. Signed-off-by: gitRasheed <[email protected]>
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.
Summary
The FileFormat writer copied every row into a list and converted the list to Arrow per value when a batch filled, re-exported the Arrow schema for every batch without releasing it, and could strand other partition writers when one write failed. Now each row is appended straight into the Arrow vectors, the schema is exported once per writer, and a failed writer lets Spark close every other writer.
Changes
ArrowFieldWriter: one typed writer per field, with struct and map children resolved once; strings are copied once from the row into the Arrow buffer, decimals of up to 18 digits are written from the unscaled long.VortexOutputWriterappends each row into vectors that start at min(batch, 64) slots and grow. The schema is exported once and released on close; a batch native never took is released on the Java side.close()releases everything without throwing, so Spark's concurrent partition writer, which stops at the first close that throws, still closes the other writers. The truncated file can't be committed: Spark commits only after every write returned normally.java.ioas Spark's launcher does (Spark 4.2's serialization debugger needs it).Testing
VortexOutputWriterMemoryTest(2,980 B left after close), the row-reuse test (binary values from a reused row read back wrong),VortexOutputWriterAbortTest(28 healthy partition writers stranded, 36,344 B), and the batch-size-1 nested-nulls round trip (out of direct memory).checkpasses on Spark 3.5/2.12, 3.5/2.13, 4.0, and the 4.1 and 4.2 compat runs.Known: the native writer still holds imported batches until it has 8,192 rows and buffers per column, so memory grows with rows per file for small batch sizes.
Checked locally.
I used AI tools to help write and review this change.