Skip materializing splitless chunk children during split collection - #9271
Conversation
acf6eef to
e09ea5d
Compare
37a60b9 to
fb7d366
Compare
Merging this PR will improve performance by ×8.6
Performance Changes
Tip Curious why this is faster? Comment Comparing Footnotes
|
Polar Signals Profiling ResultsLatest Run
Powered by Polar Signals Cloud |
Benchmarks: TPC-H SF=1 on NVME 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed / ns (1.003x ➖, 0↑ 0↓)
datafusion / parquet / ns (0.985x ➖, 2↑ 2↓)
duckdb / vortex-file-compressed / ns (0.958x ➖, 4↑ 0↓)
duckdb / parquet / ns (0.997x ➖, 0↑ 0↓)
File Size Changes (9 files changed, -43.9% overall, 0↑ 9↓)
Totals:
|
Benchmarks: Clickbench Sorted on NVME 📖Verdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed / ns (0.890x ✅, 3↑ 0↓)
datafusion / parquet / ns (0.964x ➖, 2↑ 1↓)
duckdb / vortex-file-compressed / ns (0.989x ➖, 1↑ 1↓)
duckdb / parquet / ns (1.002x ➖, 0↑ 0↓)
File Size Changes (201 files changed, -42.8% overall, 51↑ 150↓)
Totals:
|
Benchmarks: TPC-H SF=1 on S3 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed / ns (0.833x ➖, 4↑ 0↓)
datafusion / parquet / ns (1.064x ➖, 0↑ 0↓)
duckdb / vortex-file-compressed / ns (1.038x ➖, 0↑ 3↓)
duckdb / parquet / ns (1.036x ➖, 0↑ 0↓)
|
Benchmarks: FineWeb NVMe 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed / ns (0.993x ➖, 0↑ 0↓)
datafusion / parquet / ns (0.976x ➖, 1↑ 0↓)
duckdb / vortex-file-compressed / ns (0.986x ➖, 1↑ 0↓)
duckdb / parquet / ns (1.001x ➖, 0↑ 0↓)
File Size Changes (2 files changed, -46.3% overall, 0↑ 2↓)
Totals:
|
Benchmarks: PolarSignals Profiling 📖Vortex (geomean): 1.005x ➖ datafusion / vortex-file-compressed / ns (1.005x ➖, 1↑ 1↓)
No file size changes detected. |
Benchmarks: TPC-H SF=10 on NVME 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed / ns (0.998x ➖, 0↑ 0↓)
datafusion / parquet / ns (1.006x ➖, 0↑ 0↓)
duckdb / vortex-file-compressed / ns (1.046x ➖, 0↑ 2↓)
duckdb / parquet / ns (0.987x ➖, 1↑ 0↓)
File Size Changes (9 files changed, -44.0% overall, 0↑ 9↓)
Totals:
|
Benchmarks: Clickbench on NVME 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed / ns (0.986x ➖, 2↑ 1↓)
datafusion / parquet / ns (0.999x ➖, 0↑ 0↓)
duckdb / vortex-file-compressed / ns (1.001x ➖, 1↑ 0↓)
duckdb / parquet / ns (1.011x ➖, 1↑ 3↓)
File Size Changes (101 files changed, -39.2% overall, 0↑ 101↓)
Totals:
|
Benchmarks: Statistical and Population Genetics 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
duckdb / vortex-file-compressed / ns (0.970x ➖, 2↑ 1↓)
duckdb / parquet / ns (0.997x ➖, 0↑ 0↓)
File Size Changes (2 files changed, -32.3% overall, 0↑ 2↓)
Totals:
|
Benchmarks: TPC-DS SF=1 on NVME 📖Verdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed / ns (1.010x ➖, 0↑ 0↓)
datafusion / parquet / ns (0.996x ➖, 1↑ 0↓)
duckdb / vortex-file-compressed / ns (1.019x ➖, 3↑ 8↓)
duckdb / parquet / ns (1.002x ➖, 2↑ 4↓)
File Size Changes (25 files changed, -43.5% overall, 0↑ 25↓)
Totals:
|
Benchmarks: Random Access 📖Verdict: Likely improvement (low confidence) How to read Verdict and Engines
random-access / vortex-file-compressed / ns (0.848x ✅, 4↑ 0↓)
random-access / parquet / ns (1.006x ➖, 0↑ 0↓)
random-access / lance / ns (0.998x ➖, 0↑ 0↓)
|
Benchmarks: FineWeb S3 📖Verdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed / ns (1.955x ❌, 0↑ 6↓)
datafusion / parquet / ns (0.991x ➖, 0↑ 0↓)
duckdb / vortex-file-compressed / ns (0.975x ➖, 0↑ 0↓)
duckdb / parquet / ns (1.275x ➖, 0↑ 1↓)
|
Benchmarks SplitBy::Layout over written files (struct -> chunked -> flat
per column) across column and chunk counts:
- cold: fresh reader tree per iteration, as the first scan over a file
- warm: reused reader tree
- cold_single_column: narrow field mask over a wide schema
- {cold,warm}_misaligned: per-column chunk granularities so no two
columns share interior chunk boundaries
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Adam Gutglick <adam@spiraldb.com>
Signed-off-by: Adam Gutglick <adam@spiraldb.com>
Split collection previously built a full Layout + LayoutReader for every chunk of every column just so flat chunks could re-register the chunk-end boundary the parent already knows from its chunk offsets. - VTable::registers_interior_splits() (default true; flat returns false) lets a layout declare that its readers only ever push the end of the requested range, forwarded through LayoutVTablePlugin and DynLayout. - LayoutChildren::child_has_no_interior_splits(idx) answers that without materializing the child: owned children ask the vtable, viewed children resolve only the flatbuffer encoding tag against the registry, with a single-slot memo since siblings almost always share one encoding. - ChunkedReader caches a lazy per-chunk classification; when no chunk has interior splits it bulk-extends boundaries straight from chunk_offsets. - RowSplits drops consecutive identical ascending runs (columns with aligned chunk boundaries) and skips the final sort when a single run survives. StructReader pushes its end boundary after the field walk so the common case stays one ascending run. - LazyReaderChildren::new_uniform avoids a DType and name clone per chunk at reader construction. Split-collection bench medians (64 columns x 256 chunks): cold 916us -> 181us, warm 247us -> 13us; with fully misaligned columns cold 1223us -> 321us, warm 387us -> 146us. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Adam Gutglick <adam@spiraldb.com>
e09ea5d to
7274546
Compare
fb7d366 to
0ada06f
Compare
Benchmarks: Compression 📖vortex / vortex-file-compressed / ns (1.002x ➖, 0↑ 1↓)
vortex / vortex-file-compressed / bytes (1.000x ➖, 0↑ 0↓)
vortex / vortex-file-compressed / ratio (1.001x ➖, 0↑ 0↓)
vortex / parquet / ns (1.001x ➖, 0↑ 0↓)
vortex / parquet / bytes (1.000x ➖, 0↑ 0↓)
|
Signed-off-by: Adam Gutglick <adam@spiraldb.com>
Signed-off-by: Adam Gutglick <adam@spiraldb.com>
| pub struct RowSplits { | ||
| splits: Vec<u64>, | ||
| /// Start index of the run currently being appended. | ||
| run_start: usize, | ||
| /// Start index of the surviving run immediately before `run_start`. | ||
| prev_run_start: usize, |
There was a problem hiding this comment.
I think you want a sorted; Vec<Vec>, Vec unsorted
then do n-way merge after?
There was a problem hiding this comment.
benchmark it and seems like the plan sort_unstable just beats it
Signed-off-by: Adam Gutglick <adam@spiraldb.com>
acbf808 to
1d9a244
Compare
Signed-off-by: Adam Gutglick <adam@spiraldb.com>
Signed-off-by: Adam Gutglick <adam@spiraldb.com>
873aa08 to
98aa23a
Compare
Signed-off-by: Adam Gutglick <adam@spiraldb.com>
0.84.0 carries vortex-data/vortex#9271, which stops split collection from building a `Layout` and a `LayoutReader` for every chunk of every column just to recover the chunk boundaries the parent already knows. That setup cost is what a short query on a wide file pays: on 0.83.0, `SELECT * ... LIMIT 1` over a 100-column file takes 0.31 s at 1 000 000 rows and 0.70 s at 4 000 000, against 0.15 s when a single column is selected, so it grows with chunks times columns rather than with the size of the result. Reported by @m7kss1, who measured 8.5 s for `SELECT * FROM hits LIMIT 1` on a file of 100 000 000 rows. The only API change that reaches this crate is that `ScanBuilder::with_projection` and `with_filter` now take a `BoundExpression`, so the projection and the pushed filter are bound to the file's type first. Binding type-checks the whole expression in one walk, which is where a column that is not in the file, or a comparison between two types that cannot be compared, is caught now. The vendored crates are in ClickHouse/rust_vendor#79. The lock file was updated with `cargo update -p vortex --precise 0.84.0` rather than regenerated, so nothing outside the `vortex` dependency tree moves: 37 `vortex-*` crates, `fastlanes` 0.5.2 -> 0.6.1, `fsst-rs` 0.5.11 -> 0.6.0, `onpair` 0.1.1 -> 0.2.0, `pastey` added, `core_detect` dropped.
What changes are included in this PR?
Split collection previously built a full Layout + LayoutReader for every chunk of every column just so flat chunks could re-register the chunk-end boundary the parent already knows from its chunk offsets.
VTable::registers_interior_splits()(default true; flat returns false) lets a layout declare that its readers only ever push the end of the requested range, forwarded through LayoutVTablePlugin and DynLayout.LayoutChildren::child_has_no_interior_splits(idx)answers that without materializing the child: owned children ask the vtable, viewed children resolve only the flatbuffer encoding tag against the registry, with a single-slot memo since siblings almost always share one encoding.ChunkedReadercaches a lazy per-chunk classification; when no chunk has interior splits it bulk-extends boundaries straight from chunk_offsets.RowSplitsdrops consecutive identical ascending runs (columns with aligned chunk boundaries) and skips the final sort when a single run survives. StructReader pushes its end boundary after the field walk so the common case stays one ascending run.LazyReaderChildren::new_uniformavoids a DType and name clone per chunk at reader construction.Split-collection bench medians (64 columns x 256 chunks): cold 916us ->
181us, warm 247us -> 13us; with fully misaligned columns cold 1223us -> 321us, warm 387us -> 146us.