Skip to content

Add bulk vector path to columnar addBatch - #16734

Open
mayya-sharipova wants to merge 3 commits into
apache:mainfrom
mayya-sharipova:dense-vector-column-cursor
Open

mayya-sharipova wants to merge 3 commits into
apache:mainfrom
mayya-sharipova:dense-vector-column-cursor

Conversation

@mayya-sharipova

@mayya-sharipova mayya-sharipova commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Builds on #16708, which added validation. Vector columns declared DENSE now go through a bulk path, and those checks come along with it via a validating cursor, so addBatch keeps rejecting what addDocument rejects.

Column API:

  • Add VectorValuesCursor, a values cursor for DENSE columns modeled on LongValuesCursor.
  • Add VectorColumn.values().

Indexing chain:

  • Vector columns declared DENSE are handed to the writer in a single addDenseValues call. The dense count and the cursor's dimension are checked once per batch instead of per vector.
  • ValidatingVectorValuesCursor runs the Validate vector values in columnar addBatch #16708 checks as the writer consumes vectors.
  • ColumnValidation gains offset-based variants of the value checks and checkVectorCursorDimension.

Codec API:

  • Add KnnFieldVectorsWriter.addDenseValues, which defaults to calling addValue once per vector.

Builds on apache#16708, which added validation. Vector columns declared
DENSE now go through a bulk path, and those checks come along with it
via a validating cursor, so addBatch keeps rejecting what addDocument
rejects.

Column API:
- Add VectorValuesCursor<T>, a values cursor for DENSE columns
  modeled on LongValuesCursor.
- Add VectorColumn.values().

Indexing chain:
- Vector columns declared DENSE are handed to the writer in a single
  addDenseValues call. The dense count and the cursor's dimension are
  checked once per batch instead of per vector.
- ValidatingVectorValuesCursor runs the apache#16708 checks as the writer
  consumes vectors.
- ColumnValidation gains offset-based variants of the value checks and
  checkVectorCursorDimension.

Codec API:
- Add KnnFieldVectorsWriter.addDenseValues, which defaults to calling
  addValue once per vector.

@jimczi jimczi 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.

This looks good. The cursor follows the same pattern as the long columns and keeping validation inside the cursor makes sense to me. My main question is about landing the codec API without a writer that uses it, see the inline comment.

*
* @lucene.experimental
*/
public void addDenseValues(int firstDocID, VectorValuesCursor<T> values) throws IOException {

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.

Nothing overrides this yet, so the dense path still copies one vector at a time. Could we add the default flat writer as a first implementer in this PR? It would show that the fill signature is the right one before other codecs start to build on it. The HNSW and scalar quantized field writers would also need to forward to their flat delegate, otherwise an override there is never reached.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I added the default flat writer as a first implementer, and the HNSW and SQ field writers now forward to their flat delegate.

But as you see there is no much optimization done yet: filling one vector at a time. The plan is to move the flat writer to paged storage in a follow-up, where addDenseValues fills a whole page per fill call. This will be in the follow up PR.

* Implementations must consume exactly {@code values.size()} vectors.
*
* <p>The cursor may throw while it is being consumed, for example when a vector fails validation.
* In that case the documents of the whole batch are marked as deleted, but the writer must remain

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.

Can we say here that the writer should only record doc IDs after fill returns? Validation runs after the copy, so when fill throws the buffer already holds the bad vectors.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in e5f1df0

* @throws IllegalArgumentException if a vector returned by {@link #next()} does not have {@link
* #dimension()} elements
*/
public void fill(T dst, int dstOffset, int count) {

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.

This assumes the writer stores vectors in a heap array. A writer that buffers in byte buffers or off-heap would need a scratch array to use it. Maybe that is fine, but it is hard to tell without a real implementer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

For now, just heap-array. In the follow-up work, we can decide how to organize paged-storage: byte buffers or off-heap.

Addresses review feedback: give addDenseValues a real implementer so
the fill signature is exercised before other codecs build on it.

- Lucene99FlatVectorsWriter's default field writer fills each vector
  directly into a new array, one copy per vector, and records doc IDs
  and vectors only after every fill has returned.
- Lucene99HnswVectorsWriter and Lucene104ScalarQuantizedVectorsWriter
  forward to their flat delegate, then add the new nodes to the graph
  or accumulate the new vectors for the centroid.
- KnnFieldVectorsWriter.addDenseValues javadoc: record doc IDs only
  after fill returns, and forward the method from wrapping writers.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants