Skip to content

[common] Read a lone vectored range on the calling thread - #9920

Open
wombatu-kun wants to merge 2 commits into
apache:masterfrom
wombatu-kun:vectored-read-single-range-inline
Open

wombatu-kun wants to merge 2 commits into
apache:masterfrom
wombatu-kun:vectored-read-single-range-inline

Conversation

@wombatu-kun

@wombatu-kun wombatu-kun commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Purpose

readVectored already reads a single range inline, but only under sequentialReadFallback. That flag guards fallbackToReadSequence, which calls SeekableInputStream.seek, so it really answers "may I move the stream position?". A caller that shares one stream across threads has to turn the flag off, and thereby also loses the inline path: a lone 4 KiB read is submitted to IO_THREAD_POOL through a BlockingExecutor and joined back. This adds an opt-in that reads such a range on the calling thread through the same readSingleRange the pool worker would have run. A single range never coalesced with anything and never reached splitBatches, so the I/O is identical and only the hand-off is gone.

When the new branch is taken. Only on the new opt-in ReadOptions.withInlineSingleRange(true), with exactly one range and an uninterrupted calling thread. The default ReadOptions.from(readable) leaves it off, so readVectored keeps its asynchronous contract for every existing caller, and Parquet and ORC are untouched.

How often. The only caller that opts in is the vector index reader, NativeVectorGlobalIndexReader.SeekableStreamVectorIndexInput, which joins every range immediately after the call, and single-range callbacks dominate its traffic: paimon-vindex reads the index header at open, streams resident sections chunk by chunk during optimizeForSearch(), preloads the DiskANN adjacency, and reads every probed IVF cluster payload with a one-range pread. An IVF search with nprobe = 32 pays 32 hand-offs per shard per query on top of the reads themselves.

An interrupted caller keeps the executor path on purpose: FileChannel is an AbstractInterruptibleChannel, so an inline read on an interrupted thread closes the channel and breaks the stream for every other reader sharing it, while BlockingExecutor.submit fails fast on semaphore.acquire() and leaves it intact.

One 4 KiB range through LocalFileIO over a 128 MiB page-cached file at random offsets, with the vector index reader's options, 20k warm-up plus 100k measured calls per JVM, median of 3 runs before and 8 after (JDK 8):

per call before after
mean 4.09 us 1.56 us
p50 2.02 us 1.39 us
p90 10.67 us 1.98 us
p99 17.41 us 3.30 us

What is saved is the hand-off, so it is the same number of microseconds on a slower device but a smaller share of the total.

Tests

VectoredReadUtilsTest covers the opt-in from both sides: a single range is served on the calling thread with the option on, and on the executor under default options. The interrupt guard and the option copy chain are covered too, and SeekableStreamVectorIndexInputTest covers the vector reader end to end.

@JingsongLi JingsongLi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P1] Keep the public vectored-read API asynchronous\n\nLocation: ; contract: .\n\n promises asynchronous reads, but the new single-range branch invokes on the caller thread. Any non-vector-index caller with one range now blocks unexpectedly; was a seek-safety switch, not a scheduling opt-in. Preserve the default asynchronous contract and add an explicit inline option used only by the vector reader that immediately joins its future.

@JingsongLi JingsongLi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Correction — the previous review was garbled by command formatting.

[P1] Keep the public vectored-read API asynchronous

Location: paimon-common/src/main/java/org/apache/paimon/fs/VectoredReadUtils.java:79-82; contract: paimon-common/src/main/java/org/apache/paimon/fs/VectoredReadable.java:70-77.

VectoredReadable.readVectored promises asynchronous reads, but the new single-range branch invokes readSingleRange on the caller thread. Any non-vector-index caller with one range now blocks unexpectedly; sequentialReadFallback was a seek-safety switch, not a scheduling opt-in. Preserve the default asynchronous contract and add an explicit inline option used only by the vector reader that immediately joins its future.

@wombatu-kun

Copy link
Copy Markdown
Contributor Author

Done 837e145 - gated on the new ReadOptions.withInlineSingleRange, which only the vector index reader sets; ReadOptions.from leaves it off.

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.

2 participants