Parallel O_DIRECT rerank reads for larger-than-RAM KNN search - #16656
Open
goankur wants to merge 1 commit into
Open
Parallel O_DIRECT rerank reads for larger-than-RAM KNN search#16656goankur wants to merge 1 commit into
goankur wants to merge 1 commit into
Conversation
goankur
force-pushed
the
odirect-parallel-rerank
branch
from
September 10, 2026 00:38
2dc4210 to
c41a499
Compare
Add FlatVectorsReader.readRawVectors to fetch a rerank shortlist as one batch, and ParallelVectorReadable so an IndexInput can service that batch concurrently. New SelectiveDirectIODirectory (misc) opens only .vec with O_DIRECT, and FLOAT32 vector data is 4KB page-aligned so each vector is a single block.
goankur
force-pushed
the
odirect-parallel-rerank
branch
from
September 12, 2026 20:18
c41a499 to
17f1cf1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Title:
(Implemented with AI, with Human in the loop)
Parallel
O_DIRECTfull-precision rerank reads for larger-than-RAM KNN searchDescription (Reviewed and Edited by Human)
Two-phase KNN — quantized (BBQ) graph search in RAM, then full-precision fp32 rerank of the
shortlist — keeps recall high while shrinking the resident footprint. But the rerank reads the fp32
vectors one at a time, and once the index exceeds RAM those reads hit disk, where serializing a
query's few-hundred-vector shortlist dominates p99.
This PR:
FlatVectorsReader.readRawVectors(field, ords, count, out)(core,codecs/hnsw): anoverridable batch read, defaulting to
false("no batch support").Lucene99FlatVectorsReaderimplements it and
Lucene104ScalarQuantizedVectorsReaderdelegates to its raw reader.ParallelVectorReadable(core,store): an optionalIndexInputcapability to fetch manyfixed-size float vectors at scattered positions as one batch, possibly in parallel. Positions are
relative to the input (like
RandomAccessInput), and the vectors are written back to back into oneflat
float[]so the hot path allocates one buffer per query rather than one array per candidate.RescoreTopNQuery/FullPrecisionFloatVectorSimilarityValuesSource: the full-precisionrescorer maps its shortlist to ordinals and calls
readRawVectors, reranking against the mainfield's own raw fp32 (no duplicate rerank field, halving the index). When the reader cannot batch,
it returns null and the existing per-document path handles it.
Lucene99FlatVectorsWriter: 4 KB page-aligns FLOAT32 vector data so each vector is a singleblock instead of straddling two. The offset is self-describing, so existing indexes still read
correctly — they just pay the extra block.
SelectiveDirectIODirectory(misc): opens.vecwithO_DIRECT(via the JDK'sExtendedOpenOption.DIRECT— pure JDK, no JNI) and implements the capability, fetching theshortlist through a caller-owned read
Executorsized independently of the searcher'sexecutor. Everything else — HNSW graph, quantized codes, metadata — keeps using the mmap delegate
and stays page-cached.
Benchmark setup
measured with
fioat 136k random-4KB read IOPS / 1.1 GB/s.maxConn 64 / beamWidth 250; 102 GB index.
graph and quantized codes stay resident while every rerank read hits the SSD — ~10× the cap.
configuration; page cache dropped before each run.
Results
Single-stream, oversample 5 / fanout 100.
The baseline moves ~67 MB per query where only 2 MB is needed (~34× amplification): the kernel's
128 KB read-ahead fetches neighbors around every 4 KB vector, and
MADV_RANDOMdoes not stopblock-device read-ahead.
O_DIRECTremoves that; alignment then removes the second block per vector.Under concurrent load (open-loop, Poisson arrivals, bounded 32-thread pool + 32-deep queue with
load shedding, latency from intended arrival, 120 s per rate), the highest rate holding
p99 ≤ 50 ms with ≤ 0.1% shed is ~90 QPS at oversample 5 / fanout 100 and ~130 QPS at the
recall-tuned oversample 3.5 / fanout 25 (recall 0.937).
Notes for reviewers
readRawVectorssits onFlatVectorsReaderso ordinal→positionarithmetic stays inside the codec that owns the layout.
ExecutoronSelectiveDirectIODirectory, decoupled from the searcher's executor, so that enough reads can be kept in flight to saturate the device without increasing search parallelism.useDirectIOadds.vecto whateverDirectIODirectoryalready routes through direct I/Orather than replacing it, so the inherited merge behavior is preserved.
IndexInput.readFloats) rather than a generalbyte-oriented batch read, deliberately, to avoid a decode copy on the rerank hot path. Happy to
generalize if preferred.
by directory choice and changes no default.
TestSelectiveDirectIODirectorycovers parallel and serial batch reads, shuffled positions and theempty shortlist.