You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: bound VortexWriter's global-dict retained memory to avoid OOM on huge files
VortexWriter buffers a global-dict candidate column's raw chunk data in
memory from its first chunk until close() -- a shared dictionary can only
be built once every chunk has been seen. On a huge, wide file (e.g. an
18.5M-row / 38-string-column real-world Parquet import) any column that
looks low-cardinality on an early chunk but never gets demoted keeps its
entire column pinned in the heap; with dozens of such columns the total
reaches several GB and the import throws OutOfMemoryError, independent of
how much heap is available (memory scales with file size x column count,
not with a bounded chunk size).
Add an aggregate retained-bytes budget (256 MB, GLOBAL_DICT_MAX_RETAINED_BYTES)
across all buffering dict-candidate columns. Each chunk appended to a
candidate updates a per-column and running-total estimate (estimateRetainedBytes);
crossing the budget demotes the largest-retained columns -- flushing their
already-buffered chunks as ordinary per-chunk segments and dropping them from
future global-dict candidacy -- until back under budget. This bounds writer
memory by the budget rather than by total file size, while still giving most
columns their shared dictionary in the common case.
Regression test (GlobalDictUtf8Test#retainedBytesBudgetExceeded_utf8_demotesToPerChunkChunkedLayout)
reproduces the bug shape at small scale via a test-only budget seam
(setDictRetainedBudgetForTest): a column with genuinely low, constant
cardinality (so the existing cardinality-ratio fallback never fires) whose
raw bytes alone cross a lowered budget, forcing mid-file demotion. Asserts
the demoted column lands as a plain per-chunk Chunked layout (not Dict) and
that every value still round-trips correctly across the demotion boundary.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
10
10
### Fixed
11
11
12
+
-`VortexWriter` no longer buffers a global-dictionary candidate column's raw data for the whole file, so importing a huge Parquet file (e.g. an 18.5M-row dataset) no longer exhausts the heap; a column whose retained bytes exceed a fixed budget is demoted to per-chunk encoding. ([a3b921b5](https://github.com/dfa1/vortex-java/commit/a3b921b5))
12
13
- A chunked `List` column spanning several flat chunks now decodes into a single stitched array instead of throwing a raw `ClassCastException`; any other unhandled dtype now fails with `VortexException`. ([#268](https://github.com/dfa1/vortex-java/issues/268))
13
14
- A chunked `Utf8`/`Binary` column with an entirely-null chunk (`NullArray`) no longer throws `chunk is not a VarBinArray`; the chunk materializes as an all-null run. ([#269](https://github.com/dfa1/vortex-java/issues/269))
14
15
- A chunked `List` column with an entirely-null chunk (`NullArray`) no longer throws `chunk is not a ListArray`; the chunk's rows become zero-length lists with out-of-band nulls. ([#269](https://github.com/dfa1/vortex-java/issues/269))
0 commit comments