FANNG1 opened a new pull request, #69596:
URL: https://github.com/apache/doris/pull/69596

   ### What problem does this PR solve?
   
   Issue Number: close #69573
   
   Related PR: None
   
   Problem Summary:
   
   When a Lance vector index covers only part of a dataset, FE plans a mixed 
set of splits: one per
   physical index segment, which BE searches with `use_index=true`, plus one 
flat split per uncovered
   fragment, which BE searches with `use_index=false`. A Doris TopN above the 
scan merges the union of
   their candidates by `_distance`.
   
   Those two kinds of split did not report `_distance` on the same scale. A 
flat split computes exact
   distances. An index split on a quantized index (`IVF_PQ`, `IVF_SQ`, 
`IVF_RQ`, `IVF_HNSW_PQ`,
   `IVF_HNSW_SQ`) computes them from the quantized codes unless refinement is 
enabled, and Doris never
   enabled it on its own: `refine_factor` reached lance-c only when the user 
passed it, and the single
   forced `set_refine_factor(1)` covered multi-vector queries. In the pinned 
lance-c, a single-vector
   scan without `refine_factor` makes no `scanner.refine()` call at all. So a 
plain single-vector
   `vector_search` over a partially indexed table sorted approximate distances 
against exact ones in
   one TopN, and the `_distance` column handed to the user mixed two scales.
   
   Measured on the fixture this PR adds (row_id 1..512 indexed by `vec_idx`, 
row_id 513..640 appended
   uncovered, `vec = [row_id, row_id + 1, row_id + 2, row_id + 3]` so the 
squared L2 distance between
   rows r and n is exactly `4 * (n - r)^2`), with query `[512.25, 513.25, 
514.25, 515.25]`:
   
   | row_id | fragment | before | after | exact |
   |---|---|---|---|---|
   | 512 | 0, indexed | 1.2808 | 0.25 | 0.25 |
   | 513 | 1, flat | 2.25 | 2.25 | 2.25 |
   | 511 | 0, indexed | 4.3113 | 6.25 | 6.25 |
   | 514 | 1, flat | 12.25 | 12.25 | 12.25 |
   | 510 | 0, indexed | 25.25 | 20.25 | 20.25 |
   | 515 | 1, flat | 30.25 | 30.25 | 30.25 |
   
   The indexed rows were reported 1.03 too high, 1.94 too low and 5.0 too high 
while the rows from the
   uncovered fragment in the same result were exact. Whether the merged order 
also changes depends on
   whether that error exceeds the gap to the neighbouring exactly scored row; 
the error is a property
   of the trained codebook rather than of the data shape, so what is 
deterministic is that the
   distances are wrong, not that a particular pair swaps.
   
   This is not a Lance defect. Lance defines the approximation and offers 
`refine_factor` as the
   remedy, and it treats putting every arm of a merge on one scale as the 
caller's job - see
   lance-format/lance#9849, which fixed the same class of bug inside Lance's 
own LSM vector planner.
   Lance cannot fix it here, because Doris creates the arms: each index split's 
fragment range equals
   its segment's coverage, so each split's internal uncovered-fragment fallback 
is empty and Lance
   never sees a mixed scan. lance-format/lance-spark#860 hit the same thing 
when it moved
   `VECTOR_SEARCH` onto Spark tasks, and resolved it the same way.
   
   The fix: when a vector plan contains both index-segment splits and 
uncovered-fragment splits, and
   the selected index does not store full vectors, FE marks that plan's 
index-segment splits with a new
   `TLanceFileDesc.refine_vector_distances` (field id 7, free and consistent on 
branch-4.2, branch-4.1,
   branch-4.0, branch-3.1 and HEAD). BE applies it in 
`_configure_vector_search` as
   `set_refine_factor(1)`, before the user options block, so a larger explicit 
`refine_factor` still
   wins, and before the split's own result limit. `IVF_FLAT` and 
`IVF_HNSW_FLAT` are exempt because
   they keep the stored vectors; every other type, including the generic 
`VECTOR` an older manifest
   reports without details, is rescored. A split carrying the flag without an 
index segment is rejected
   rather than silently honored. `EXPLAIN` reports the decision as 
`lanceSearchRefineDistances`.
   
   Refinement rescores the candidates the index already chose and does not 
widen that choice, so this
   makes the distances and their merge correct without turning ANN candidate 
selection into an
   exhaustive search.
   
   ### Release note
   
   Fixed Lance `vector_search` returning distances on two different scales, and 
an order that could be
   distorted, when a quantized vector index (`IVF_PQ`, `IVF_SQ`, `IVF_RQ`, 
`IVF_HNSW_PQ`,
   `IVF_HNSW_SQ`) covered only part of the dataset. Such a query now rescores 
the indexed candidates
   against the stored vectors, at the cost of one additional read of those 
vectors. `EXPLAIN` shows
   this as `lanceSearchRefineDistances`.
   
   ### Check List (For Author)
   
   - Test: Unit Test
       - FE: `LanceScanNodeTest` extended with the index-type x coverage matrix 
and the
         `use_index=false` case; run and passing (40 tests, 0 failures).
       - BE: `lance_reader_test.cpp` extended with a forced-refinement case 
over the existing
         `distance_range_sq.lance` fixture and a guard case. Their expected 
distances were read off
         that fixture directly with pylance 7: the query quantizes onto the 
bucket row 0 holds, so an
         unrefined candidate reports the squared distance to that bucket 
instead of to the query.
         Written but not yet run - the BE unit test build for this change is 
still in progress, which
         is why this opens as a draft.
       - Regression: a new `external_table_p0/lance` suite and the fixture it 
needs are prepared but
         held back from this commit, because its `.out` must be generated by a 
test run against the
         docker Lance environment rather than written by hand.
   - Behavior changed: Yes. A single-vector search on a partially covered 
quantized index now returns
     exact distances and the correctly ordered merge; it previously returned 
quantized distances for
     the indexed rows. The cost is one read of the stored vectors for the 
candidates of each index
     split. Fully covered plans, `IVF_FLAT` / `IVF_HNSW_FLAT` plans, 
`use_index=false` and multi-vector
     queries are unchanged.
   - Does this need documentation: Yes. `docs/lance-multivector-search.md` is 
updated in this commit;
     the Lance catalog page on the website should get the same note about 
`refine_factor` and partially
     covered quantized indexes (no doc PR yet).
   
   
   ### Why this targets branch-4.1
   
   The Lance connector exists only on `branch-4.1` and `branch-4.2`; master 
carries the Thrift
   placeholders but no connector, so there is no master commit to backport 
from. `fe/.../datasource/lance/**`
   and `be/src/format_v2/table/lance_reader.cpp` are byte-identical between the 
two branches and
   `TLanceFileDesc` has the same field ids on both, so the same change 
cherry-picks to `branch-4.2`
   without conflict. I will open that PR once this one is agreed.
   
   ### Draft status
   
   Opened as a draft because the BE unit tests added here are written but have 
not been run yet - the
   BE unit test build is still compiling locally. I will mark this ready for 
review once
   `LanceTableReaderVectorSearchTest.*` passes and I have posted the end-to-end 
evidence promised on
   the issue (`EXPLAIN` plus the three `(row_id, _distance)` result sets from 
the docker Lance
   environment).
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to