Skip to content

ORC-2224:Coalesce vectored read ranges honoring minSeekSize and read single-range batches synchronously - #2718

Open
BsoBird wants to merge 1 commit into
apache:mainfrom
BsoBird:vectored-read-opt
Open

BsoBird wants to merge 1 commit into
apache:mainfrom
BsoBird:vectored-read-opt

Conversation

@BsoBird

@BsoBird BsoBird commented Oct 12, 2026

Copy link
Copy Markdown

What changes were proposed in this pull request?

  • Refactor RecordReaderUtils.readDiskRangesVectored to coalesce BufferChunks through a new pure coalesceRanges helper that merges contiguous/overlapping ranges and, when minSeekSize > 0, also folds in small gaps (nextOffset - currentEnd <= minSeekSize), matching the sync path's findSingleRead semantics so vectored I/O no longer regresses on minSeekSize-bounded workloads
  • Enforce a hard per-range cap of IOUtils.MAX_ARRAY_SIZE and compute merged spans in long arithmetic, flushing the in-flight FileRange when it would exceed the cap so offsets near Integer.MAX_VALUE no longer wrap to negative/truncated int lengths
  • Treat already-filled chunks (hasData() == true) as merge barriers so the vectored path never re-reads data that is already in memory, and backfill original chunks from merged range buffers with a single O(chunks + ranges) forward pass
  • Short-circuit single-range batches to a synchronous positioned readFully instead of submitting one FileRange to readVectored, avoiding executor task + CompletableFuture overhead where no parallelism is possible
  • Reject a negative minSeekSize up front (IllegalArgumentException) instead of failing later with a misleading error
  • Add 14 unit tests covering gap-0 merge, gap <= / > minSeekSize, hasData boundaries, the size cap, negative input rejection, the single-range short-circuit, and minSeekSize=0 equivalence; rework testHadoopVectoredIO to keep exercising readVectored (a full scan now coalesces to one range)

The non-vectored (sync) read path is unchanged. With default configuration (minSeekSize=0) exactly the same bytes are read as before.

Why are the changes needed?

When vectored I/O is active (Hadoop >= 3.3.5, zero-copy disabled), the vectored read path builds one FileRange per BufferChunk with no merging:

  1. orc.min.disk.seek.size is silently ignored. The sync path merges ranges whose gap is within minSeekSize (findSingleRead); the vectored path does not. TestMinSeekSize.readAlternateColumnsWMinSeekSize fails on Hadoop 3.4.1 (reads 5.26% of the file instead of ~106%); it passes on Hadoop 3.3.4, where vectored I/O is disabled — confirming the vectored path as the cause.
  2. Measurable read-path regression vs the sync path (and ORC 1.5.3) on files with many small stripes. Per-range async dispatch overhead dominates when ranges are tiny and numerous.

Local benchmark (2M rows, 1954 stripes of ~1k rows, ZLIB/NONE, warm page cache, Hadoop 3.4.1, median of 15 iterations, before → after):

Mode ZLIB before ZLIB after NONE before NONE after
full scan 216.9 ms 187.8 ms 97.3 ms 73.3 ms
projection 129.7 ms 105.4 ms 51.7 ms 32.9 ms
filter (SARG) 51.3 ms 28.2 ms 43.9 ms 21.5 ms
projection + filter 34.1 ms 16.8 ms 32.8 ms 14.9 ms

Read bytes and row counts are identical in every cell; after the fix the vectored path matches the non-vectored path (and 1.5.3) on this workload.

How was this patch tested?

  • TestMinSeekSize now passes with Hadoop 3.4.1 (previously failing; see above for reproduction via -Dhadoop.version=3.3.4 vs 3.4.1).
  • 14 new tests in TestRecordReaderUtils for coalesceRanges (positive + negative cases listed above) and a new TestRecordReaderImpl.testHadoopVectoredIOSingleRangeUsesSyncRead asserting single-range batches never call readVectored.
  • Full java/core suite on main: 1558 tests, 0 failures, 0 errors (21 skipped): cd java && ./mvnw -pl core test
  • Benchmark reproduction: read a 1954-stripe ORC file (ZLIB and NONE) 15 times per mode (scan / projection / SARG filter / projection+filter) with Hadoop 3.4.1 on the classpath; verify identical read_bytes and rows_read between vectored and non-vectored paths.

Was this patch authored or co-authored using generative AI tooling?

co-authored-by: Kimi K3 via OpenCode

…tolerance

When vectored I/O is active (Hadoop >= 3.3.5, zero-copy disabled),
readDiskRangesVectored built one FileRange per BufferChunk with no
merging: orc.min.disk.seek.size was silently ignored (TestMinSeekSize
fails on Hadoop >= 3.3.5, reading 5.26% of the file instead of ~106%),
its tolerance was not applied (gap bytes stayed resident in range
buffers), contiguous/overlapping ranges paid per-range async overhead,
and single-range batches paid readVectored dispatch cost with no
parallelism benefit.

- Coalesce BufferChunks through a new pure coalesceRanges helper that
  merges contiguous/overlapping ranges and, when minSeekSize > 0,
  folds in small gaps (nextOffset - currentEnd <= minSeekSize),
  matching the sync path's findSingleRead semantics so vectored I/O
  no longer regresses on minSeekSize-bounded workloads
- Enforce a hard per-range cap and compute merged spans in long
  arithmetic, flushing the in-flight FileRange when it would exceed
  the cap so offsets near Integer.MAX_VALUE no longer wrap to
  negative/truncated int lengths
- Treat already-filled chunks (hasData()) as merge barriers so the
  vectored path never re-reads in-memory data, and backfill original
  chunks with a single O(chunks + ranges) forward pass
- Apply minSeekSizeTolerance as a post-read memory bound: merged
  ranges whose extra-bytes fraction exceeds the tolerance are
  compacted into tightly-sized buffers, mirroring the sync path's
  ChunkReader.populateChunksReduceSize
- Short-circuit single-range batches to a synchronous positioned
  readFully instead of submitting one FileRange to readVectored
- Reference RecordReaderUtils.MAX_ARRAY_SIZE (moved from IOUtils by
  ORC-2049) so the code compiles on main

With default configuration (minSeekSize=0, minSeekSizeTolerance=0.0)
exactly the same bytes are read as before.

Local benchmark (2M rows, 1954 stripes of ~1k rows, ZLIB/NONE, warm
page cache, Hadoop 3.4.1, median of 15 iterations, ORC 2.1.2 before ->
after): full scan 216.9 -> 187.8 ms, projection 129.7 -> 105.4 ms,
SARG filter 51.3 -> 28.2 ms, projection+filter 34.1 -> 16.8 ms;
identical read bytes and row counts in every cell.

Tests: TestMinSeekSize now passes on Hadoop 3.4.1; 18 new unit tests
in TestRecordReaderUtils; TestRecordReaderImpl.testHadoopVectoredIO
updated (full scans now coalesce to a single range) plus a new test
asserting single-range batches never call readVectored; full java/core
suite 1562 tests, 0 failures, 0 errors (21 skipped) on main.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant