Skip to content

perf(scan): intra-file decode parallelism — sub-split large chunk spans#62

Merged
lukekim merged 2 commits into
spiceai-53from
lukim/scan-split-large-chunks
Jun 12, 2026
Merged

perf(scan): intra-file decode parallelism — sub-split large chunk spans#62
lukekim merged 2 commits into
spiceai-53from
lukim/scan-split-large-chunks

Conversation

@lukekim

@lukekim lukekim commented Jun 12, 2026

Copy link
Copy Markdown

What

SplitBy::Layout now sub-divides any span between adjacent chunk boundaries wider than IDEAL_SPLIT_SIZE (100k rows) into evenly sized row-range splits, so a file with few large chunks (e.g. a single flat layout, or byte-targeted int columns that coalesce to ~262k rows/chunk) decodes across multiple cores instead of one.

Correctness

Subdivision only inserts points strictly between existing adjacent boundaries — it never moves or removes one — so the half-open ranges consumers derive (tuple_windows) remain a contiguous, non-overlapping, exact partition of the same rows. Spans at or below the cap pass through untouched (fast-path no-op). All three boundary consumers (RepeatedScan, VortexFile::splits(), the DataFusion repartitioner) operate on arbitrary ranges; sub-chunk ranges were already exercised by SplitBy::RowCount. The arithmetic saturates at u64::MAX.

Observable behavior

For files whose merged (projected-column) chunk boundaries leave spans > 100k rows, VortexFile::splits() (incl. Python bindings) returns more, smaller ranges; scans emit smaller batches; DataFusion gets real repartitioning where a single-chunk file previously collapsed to one partition. Fine-grained files (e.g. ~8k-row string chunks from the default 1 MiB block target) are untouched.

Testing

  • Unit/property/overflow tests for subdivide_large_spans (no-op, large single chunk, mixed gaps, exact-coverage property, u64::MAX boundary).
  • E2E: 250k-row single flat chunk → splits all ≤ 100k, contiguous, exact endpoints; full + filtered scans match the unsplit data.
  • E2E (rstest): fixed-size SplitBy::RowCount scans (unaligned 33,333 and exceeds-file 300,000 cases) — previously only covered at the boundary-math level.
  • E2E: ~120-byte string column via the default write strategy keeps its natural ~8k-row chunk splits (all ≤ 16,384 rows, far below the cap).
  • cargo nextest run -p vortex-layout -p vortex-file — 170 passed. cargo clippy --all-targets — no diagnostics in changed files.

Split out of #60 (second of two independent changes; companion: #61).

SplitBy::Layout now sub-divides any span between adjacent chunk boundaries
wider than IDEAL_SPLIT_SIZE (100k rows) into evenly sized row-range splits,
so files with few large chunks decode across multiple cores. Subdivision
only inserts points strictly between existing adjacent boundaries: the
half-open ranges consumers derive remain a contiguous, non-overlapping,
exact partition of the same rows. The arithmetic saturates at u64::MAX.

Tests: unit/property/overflow coverage for the subdivision helper, an
end-to-end test that a 250k-row single flat chunk scans correctly across
sub-divided splits with bounded split sizes, an rstest-parameterized
end-to-end test for fixed-size SplitBy::RowCount scans (previously only
covered at the boundary-math level), and an end-to-end test that ~120-byte
string columns written with the default strategy keep their natural ~8k-row
chunk splits untouched by the cap.

Signed-off-by: Luke Kim <80174+lukekim@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves scan-time parallelism by enhancing SplitBy::Layout to further subdivide large inter-chunk row spans into smaller row-range splits (capped at ~100k rows), enabling multi-core decode even for files with very few large chunks.

Changes:

  • Added subdivide_large_spans to insert additional split boundaries inside large adjacent chunk spans for SplitBy::Layout.
  • Updated SplitBy::Layout documentation and behavior to apply the sub-splitting cap.
  • Added E2E tests in vortex-file validating subdivision on a single large flat chunk, regression coverage for SplitBy::RowCount, and ensuring fine-grained string chunking remains small; introduced rstest as a dev dependency.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.

File Description
vortex-layout/src/scan/split_by.rs Implements span subdivision for SplitBy::Layout and adds unit/property/overflow tests.
vortex-file/src/tests.rs Adds end-to-end coverage for split subdivision behavior and SplitBy::RowCount scan correctness.
vortex-file/Cargo.toml Adds rstest as a dev-dependency for parameterized tests.
Cargo.lock Updates lockfile for the added dev-dependency.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread vortex-file/src/tests.rs
Comment thread vortex-file/src/tests.rs Outdated
@lukekim lukekim self-assigned this Jun 12, 2026
Introduce a test-local MAX_SPLIT_ROWS mirroring the private IDEAL_SPLIT_SIZE
instead of repeating 100_000, and bound the string-chunk test relative to the
cap (< MAX_SPLIT_ROWS / 4) rather than pinning the current ~8k repartition
default.

Signed-off-by: Luke Kim <80174+lukekim@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/performance A performance improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants