Principle:Lance format Lance Hybrid Search Composition
| Knowledge Sources | |
|---|---|
| Domains | Information_Retrieval, Full_Text_Search |
| Last Updated | 2026-02-08 19:00 GMT |
Overview
Hybrid search composition is the technique of combining full-text search (BM25 scoring) with vector similarity search (distance scoring) in a single query, where one modality acts as the primary search and the other acts as a filter or secondary scorer.
Description
Modern information retrieval systems often benefit from combining keyword-based (lexical) search with vector-based (semantic) search. Lance supports this through the filter_query mechanism on the Scanner, which allows one search modality to be used as a filter for the other. This produces results that satisfy both lexical and semantic relevance criteria.
The composition uses the QueryFilter enum, which can be either Fts(FullTextSearchQuery) or Vector(Query). The Scanner determines which search modality is the "primary" source (based on full_text_search vs. nearest) and uses the filter_query as the secondary constraint.
Usage
Use hybrid search composition when:
- You want to find documents that are both semantically similar to a query vector AND contain specific keywords
- You want to use BM25 scores to filter or re-rank vector search results
- You want to use vector distance to filter or re-rank full-text search results
- You need to combine scores from both modalities for a unified ranking
Theoretical Basis
Composition Patterns
Lance supports three distinct composition patterns, selected automatically based on which search modality is primary and how prefiltering is configured.
Pattern 1: FTS Primary + Vector Filter
When the primary search is full-text and a vector query is provided as a filter:
[Vector Search (ANN/KNN)] -- runs first to get candidate row IDs with distance scores
|
v
[FlatMatchQueryExec or HashJoinExec] -- applies FTS scoring on vector candidates
|
v
Results with both _distance and _score
The vector search runs first to produce candidate row IDs. Then a flat FTS operation (without using the inverted index) scores those candidates, or a HashJoinExec joins the vector results with FTS index results. The prefilter=true setting enables this index-based path.
Pattern 2: Vector Primary + FTS Filter
When the primary search is vector similarity and a full-text query is provided as a filter:
[FTS Index Search] -- runs first to get candidate row IDs with BM25 scores
|
v
[Take vector column] -- fetches vector data for FTS candidates
|
v
[Flat KNN] -- computes distance scores on the FTS-filtered candidates
|
v
Results with both _distance and _score
The FTS search runs first using the inverted index. The resulting row IDs are used to fetch the vector column, and then a flat (brute-force) KNN search computes distances on only the FTS-matched rows.
Pattern 3: Join-Based Composition
For general compound queries or when both modalities need independent scoring:
[Vector Search] [FTS Index Search]
| |
v v
{_rowid, _distance} {_rowid, _score}
| |
+-----------+-------------+
|
v
[HashJoinExec (Inner Join on _rowid)]
|
v
{_rowid, _distance, _score}
|
v
[ProjectionExec (deduplicate _rowid)]
|
v
Final Results
This pattern uses a HashJoinExec with JoinType::Inner to merge results from both searches, retaining only rows that appear in both result sets. The join key is _rowid. After joining, a projection removes the duplicate _rowid column.
Prefilter vs. Postfilter
The prefilter setting on the Scanner controls whether the filter query runs before or after the primary search:
- prefilter=true (default): The filter query runs first and produces candidate row IDs. The primary search then operates only on those candidates. This is more efficient but requires the filter to run to completion before the primary search begins.
- prefilter=false: Both searches run independently and results are intersected afterward. This can be useful when both indexes are large and running independently is faster than sequential execution.
Score Combination
Lance does not automatically combine the BM25 _score and vector _distance into a single ranking score. Both values are returned in the output, allowing the application layer to implement custom fusion strategies such as:
- Reciprocal Rank Fusion (RRF)
- Weighted linear combination
- Re-ranking with a cross-encoder model
- Threshold-based filtering on one score followed by sorting on the other