Repository navigation
fix(cache): release memory a liquid array holds beyond its charge - #61
Conversation
The cache charges an in-memory liquid entry by its Vortex nbytes(), the sum of its buffers' lengths. Two kinds of compressor output keep more memory alive than that: - A constant string column stores a scalar that is a zero-copy slice of the whole source views or data buffer (vortex-data/vortex#10599, fixed upstream in #11382 after our pinned revision). - An FSST codes buffer grows by doubling and is frozen with its spare capacity (vortex-data/vortex#11420). Both are freed only with the entry, so the cache can hold close to twice what it counts. Round-trip each compressed array through the IPC serialization the disk tier already uses, which detaches it from both allocations.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
📊 Benchmark ComparisonCurrent:
Compared Liquid vs DataFusionDefault on the same runner |
|
The TPC-DS failure is unrelated to this change, and it reproduced on a rerun. It fails in "Setup TPC-DS data" before any Rust runs. The runner now has Python 3.14, whose argparse rejects |
What
Each array
LiquidArray::from_arrow_arraycompresses is round-tripped throughto_bytes/from_bytes, the IPC serialization the disk tier already uses. That detaches the entry from its source column's buffers and from the compressor's spare capacity.Why
The cache charges an in-memory liquid entry by Vortex
nbytes(), which sums buffer lengths. Two kinds of compressor output hold more than that:ConstantArraywhose scalar is a zero-copy slice of the source column's whole views buffer (strings of 12 bytes or fewer) or data buffer. An 8192-row column is charged a few bytes but holds about 128 KiB. Reported upstream as Writer holds every uncompressed chunk of a constant string column until the file ends (ConstantArray scalar slices its chunk; BufferedStrategy counts nbytes) vortex-data/vortex#10599 and fixed in fix(compressor): copy constant string and binary values out of the source buffer vortex-data/vortex#11382, which is newer than the Vortex revision this repo pins.Both are freed only when the entry is, so a full cache can hold much more than its budget.
Effect
Measured over 200 batches × 25 columns of a real 540 MB Parquet file (log-style data with many string columns):
The round trip took 135 ms against 3,820 ms of compression for the same batches (about 3.5%). Most of the remaining gap is per-node overhead (symbol tables, tree nodes) that
nbytes()also does not count.Tests
constant_strings_do_not_pin_the_source_buffers: after compressing a constant column and dropping the source, the source's views and data buffers are uniquely owned again. Fails without the fix.fsst_codes_growth_allocation_is_released: keeps a handle to the compressor's FSST codes buffer (len 544,535, capacity 1,048,577 in this fixture), compacts, and requires that handle to be uniquely owned again. Fails without the fix.Both pass with the fix, along with
cargo test --workspace --exclude dev-tools.The charge stays approximate:
nbytes()still leaves out per-node overhead such as tree nodes and decoded FSST symbol tables.Once the Vortex pin includes #11382 and a fix for #11420, this round trip can be reconsidered.
This change was investigated and written with AI assistance (Claude Code).