docs: declare where the search pages apply

Search, full-text search, hybrid search, filtering, query plans and
reranking all work in the embedded library and against a deployment, and
now say so. Where the two differ, the section says how: a deployment
returns a query's whole result in one response, the synchronous Python
remote table refuses to_pandas, to_arrow and to_lance, probe widening
depends on the deployment's version, full-text indexes build in the
background and are kept current without optimize(), distributed scoring
can differ slightly, model files have to be present on the deployment,
plans carry deployment operators, and hybrid explain_plan is Python only.
Reranking runs in the application either way.
This commit is contained in:
Brendan Clement
2026-10-08 21:31:25 -07:00
parent 05c7ba4889
commit e85f1b4434
6 changed files with 69 additions and 14 deletions
+7
View File
@@ -4,6 +4,13 @@ sidebarTitle: "Reranking"
description: "Use a reranker to improve search relevance."
icon: "sort-amount-down"
keywords: ["re-ranking", "reranker", "rerank"]
availability:
oss: available
enterprise: available
summary: >-
Rerankers run in your application after the search returns, so they work
the same with the embedded library and with a deployment. Most built-in
rerankers are Python-only.
---
import {
+13 -6
View File
@@ -3,12 +3,19 @@ title: "Explain a query plan"
sidebarTitle: "Query Plans"
description: "Read the plan LanceDB resolved for a query, and measure it with analyze."
icon: "map"
availability:
oss: available
enterprise: available
summary: >-
Query plans can be explained and analyzed in the embedded library and against
a deployment. A deployment's plans include its own operators, and per-worker
metrics apply only to a deployment that distributes its queries.
---
`explain_plan` prints the plan LanceDB resolved for a query without executing
it. It answers structural questions -- did the filter push into the scan, is the
vector index being used, which columns are actually read -- and costs nothing to
run, so it is the first thing to reach for when a query behaves unexpectedly.
vector index being used, which columns are actually read -- and does not execute
the query, so it is the first thing to reach for when a query behaves unexpectedly.
To measure a query rather than read it, use
[`analyze_plan`](/search/explain), which executes it and annotates every
@@ -34,7 +41,7 @@ the number of rows available after filtering.
## Reading the Execution Plan {#reading-the-execution-plan}
To demonstrate query performance analysis, we'll use a table containing 1.2M rows sampled from the [Wikipedia dataset](https://huggingface.co/datasets/wikimedia/wikipedia). Initially, the table has no indices, allowing us to observe the impact of optimization.
To demonstrate query performance analysis, we'll use a table containing 1.2M rows sampled from the [Wikipedia dataset](https://huggingface.co/datasets/wikimedia/wikipedia). Initially, the table has no indices, allowing us to observe the impact of optimization. The table is on a deployment, so its plans include deployment operators such as `RemoteTake` and hide storage paths as `uri=***`; a local plan shows Lance's `Take` and the dataset path instead.
Let's examine a vector search query that:
@@ -126,7 +133,7 @@ CoalesceBatchesExec: target_batch_size=1024
#### 5\. Data Retrieval {#5-data-retrieval}
- `RemoteTake` is a key component of Lance's I/O cache
- `RemoteTake` is the deployment's operator for this step; a local plan shows Lance's `Take`
- Handles efficient data retrieval from remote storage locations
- Fetches specific rows and columns needed for the final output
- Optimizes network bandwidth by only retrieving required data
@@ -148,7 +155,7 @@ This plan demonstrates a basic search without index optimizations: it performs a
## Non-vector queries {#analyzing-non-vector-queries}
`explain_plan` is not vector-specific -- it is available on every query builder,
including FTS and hybrid. The most common reason to read the plan for a
including FTS, and on hybrid queries in Python. The most common reason to read the plan for a
non-vector query is to confirm whether your `where` clause pushed into the scan
(good) or ran as a separate `FilterExec` on top of the search results (often
slower, and a hint that the filter column needs a scalar index).
@@ -288,7 +295,7 @@ ProjectionExec: expr=[chunk_index@4 as chunk_index, title@5 as title, identifier
<Badge color="red">Enterprise</Badge>
When you call `analyze_plan` against a LanceDB Enterprise table, the query runs across a pool of workers. By default the returned plan aggregates each operator's metrics into a single value, matching the local single-node output. Pass a `distributed_metrics` mode when you need to see how work was split across workers:
When you call `analyze_plan` against a LanceDB Enterprise deployment that distributes its queries across a pool of workers, the query runs on those workers. By default the returned plan aggregates each operator's metrics into a single value, matching the local single-node output. Pass a `distributed_metrics` mode when you need to see how work was split across workers:
| Mode | What it shows | When to use it |
| :------------ | :---------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------ |
+7 -1
View File
@@ -3,6 +3,12 @@ title: "Metadata Filtering in LanceDB"
sidebarTitle: Filtering
description: Filter search results in LanceDB based on metadata fields.
icon: "filter"
availability:
oss: available
enterprise: available
summary: >-
Metadata filters work in the embedded library and against a deployment,
which evaluates them as part of the search.
---
LanceDB supports filtering features of query results based on metadata fields.
@@ -15,7 +21,7 @@ with filtering capabilities even on datasets containing billions of records.
On hybrid queries, the same `where(...)` filter is applied to both the vector and full-text halves of the query. The prefilter or postfilter choice controls whether that happens before each subquery scores candidates or after the subquery top-k is produced.
<Note>
The same predicates work in SQL -- see [SELECT](/sql/dql/select#filtering) and [Operators](/sql/operators).
The same predicates work in SQL, which runs on a deployment -- see [SELECT](/sql/dql/select#filtering) and [Operators](/sql/operators).
</Note>
## Chaining `where` clauses {#chaining-where-clauses}
+23 -2
View File
@@ -3,6 +3,13 @@ title: Full-Text Search (FTS)
sidebarTitle: Full-Text Search (FTS)
description: Learn how to implement full-text search in LanceDB using BM25 for keyword-based retrieval.
icon: "book"
availability:
oss: available
enterprise: available
summary: >-
Full-text search runs in the embedded library and against a deployment. On a
deployment, indexes are built in the background and kept up to date by the
deployment rather than by optimize().
---
import {
@@ -15,7 +22,7 @@ import { PyFtsBooleanQueries } from '/snippets/doc_function_examples.mdx'
LanceDB provides support for Full-Text Search via Lance, allowing you to incorporate keyword-based search (based on BM25) in your retrieval solutions.
<Note>
In SQL, full-text search is the [`fts()`](/sql/functions#fts) table function, or the [`contains_tokens`](/sql/scalar-functions#contains-tokens) predicate when you do not need a score.
In SQL, full-text search is the [`fts()`](/sql/functions#fts) table function, or the [`contains_tokens`](/sql/scalar-functions#contains-tokens) predicate when you do not need a score. [SQL](/basics/sql) runs on a deployment.
</Note>
## Basic Usage {#basic-usage}
@@ -95,6 +102,10 @@ tbl
```
</CodeGroup>
Locally the index is built before the call returns. On a deployment the call
starts a background build and returns at once; pass `wait_timeout` or call
`wait_for_index` before relying on the index.
### Full-text Search {#full-text-search}
Perform full-text search and retrieve results:
@@ -137,6 +148,10 @@ LanceDB automatically searches on the existing FTS index if the input to the sea
If a table has more than one FTS index, specify the indexed text column in the query. In Python you can use `fts_columns` or the query builder's `nearest_to_text(..., columns=...)`; in TypeScript, use `query().nearestToText(..., columns)`. The newer Lance-native FTS does not accept legacy Tantivy-only index parameters.
A deployment that distributes search across its workers scores each worker's
rows with that worker's statistics unless it is configured for global scoring,
so scores and close rankings can differ slightly from the embedded library's.
### Keeping the index up to date {#keeping-the-index-up-to-date}
Rows you add after building an FTS index aren't part of the index until you optimize the table. Until then, queries fall back to a flat scan over the unindexed fragments to keep results complete, which slows them down as the unindexed tail grows. Call `table.optimize()` to fold new rows into the existing index — it's the same operation used for vector indexes:
@@ -159,6 +174,8 @@ tbl.optimize(OptimizeAction::All).await?;
A useful rule of thumb is to call `optimize()` after roughly 100,000 row changes or 20 data-modification operations, whichever comes first. For tables with continuous ingest, schedule it on a cadence that keeps `num_unindexed_rows` (from `table.index_stats(...)`) close to zero. If you want to skip the flat scan over unindexed rows entirely — for example, on a hot read path where stale results are acceptable — call `.fast_search()` on the query so the search returns only indexed results.
On a deployment, `optimize()` is not available: the deployment adds new rows to the index itself, and until it has, queries scan the unindexed rows.
## Advanced Usage {#advanced-usage}
### Tokenize Table Data {#tokenize-table-data}
@@ -215,6 +232,10 @@ export LANCE_LANGUAGE_MODEL_HOME=/path/to/lance/language_models
For example, `jieba/default` is resolved under `<model-home>/jieba/default/...`, `lindera/ipadic` under `<model-home>/lindera/ipadic/...`, and `lindera/ko-dic` under `<model-home>/lindera/ko-dic/...`.
The model files are read where the index is built and queried. For a table on a
deployment that is the deployment, so ask its administrator whether they are
installed there.
<Note>
Built-in stop-word removal supports Danish, Dutch, English, Finnish, French, German, Hungarian, Italian, Norwegian, Portuguese, Russian, Spanish, and Swedish. If you use another stemming language, such as Arabic, Greek, Romanian, Tamil, or Turkish, set `remove_stop_words=False` or pass `custom_stop_words`.
</Note>
@@ -720,7 +741,7 @@ const multiMatchBoostingResults = await table.query()
- Create full-text search indices on text columns that will be frequently searched
- For hybrid search combining text and vectors, see our [hybrid search guide](/search/hybrid-search/)
- For performance benchmarks, check our [benchmark results](/enterprise/benchmarks/)
- For complex queries, use SQL to combine FTS with other filter conditions
- For complex queries on a deployment, use SQL to combine FTS with other filter conditions
</Card>
### Boolean Queries {#boolean-queries}
+9 -2
View File
@@ -3,6 +3,13 @@ title: Hybrid Search
sidebarTitle: "Hybrid Search"
description: Learn how to perform hybrid search in LanceDB by combining vector and full-text search techniques with reranking.
icon: "search"
availability:
oss: available
enterprise: available
summary: >-
Hybrid search runs in the embedded library and against a deployment. With a
deployment, the vector and full-text searches run there, and the SDK combines
and reranks their results in your application.
---
In certain cases, you may want to retrieve documents that are semantically similar to a given query,
@@ -341,11 +348,11 @@ await table.query()
```
</CodeGroup>
The choice gets baked into both sub-queries, so the vector and FTS halves see the filter applied the same way. Use [`explain_plan`](/search/explain#analyzing-non-vector-queries) on a hybrid query to see whether the filter pushed into the scan or ran as a separate `FilterExec` step.
The choice gets baked into both sub-queries, so the vector and FTS halves see the filter applied the same way. In Python, use [`explain_plan`](/search/explain#analyzing-non-vector-queries) on a hybrid query to see whether the filter pushed into the scan or ran as a separate `FilterExec` step.
## More on Reranking {#more-on-reranking}
You can perform hybrid search in LanceDB by combining the results of semantic and full-text search via a reranking algorithm of your choice. LanceDB comes with [**built-in rerankers**](https://docs.lancedb.com/reranking) and you can implement your own **custom reranker** as well.
You can perform hybrid search in LanceDB by combining the results of semantic and full-text search via a reranking algorithm of your choice. LanceDB comes with [**built-in rerankers**](https://docs.lancedb.com/reranking) and you can implement your own **custom reranker** as well. Reranking and score normalization run in your application, whether the table is local or on a deployment.
By default, LanceDB uses `RRFReranker()`, which uses reciprocal rank fusion score, to combine and rerank the results of semantic and full-text search. You can customize the hyperparameters as needed or write your own custom reranker. Here's how you can use any of the available rerankers:
+10 -3
View File
@@ -3,6 +3,13 @@ title: Search
sidebarTitle: Overview
description: "Comprehensive guide to all search capabilities in LanceDB including vector search, full-text search, hybrid search, and more."
icon: "list"
availability:
oss: available
enterprise: available
summary: >-
Vector, full-text and hybrid search run in the embedded library and against
a deployment. Reading very large results and probe tuning differ between the
two, as the sections below note.
---
| Feature | Description |
@@ -45,7 +52,7 @@ Three knobs materially affect query latency, memory use, and recall. Be delibera
- Quantized indexes (PQ, RQ, SQ): `refine_factor` pulls extra candidates and re-scores them on full vectors.
- HNSW-backed indexes: `ef` at search time. Start at `1.5 × k`, raise toward `10 × k` if recall is short.
- IVF candidate breadth: `nprobes` is auto-tuned; override only when a selective pre-filter leaves too few neighbors.
- IVF candidate breadth: `nprobes` is auto-tuned; override only when a selective pre-filter leaves too few neighbors. Whether a deployment widens the search this way depends on its version; one that does not searches exactly `minimum_nprobes` partitions.
</Step>
</Steps>
@@ -53,7 +60,7 @@ For hybrid search, the default `RRFReranker()` combines vector and FTS results i
#### Avoid materializing the whole table {#avoid-materializing-the-whole-table}
If you need every row in the table (for training, export, or migration), calling `to_pandas()` or `to_arrow()` will run you out of memory on any non-trivial dataset — both materialize the full table at once. The best practice is to iterate via `table.search(...)` or `table.query(...)`, which work the same way in both LanceDB OSS and Enterprise.
If you need every row in the table (for training, export, or migration), calling `to_pandas()` or `to_arrow()` will run you out of memory on any non-trivial dataset — both materialize the full table at once. Read it through queries instead: `table.search(...)`, or `table.query(...)` in async Python, TypeScript and Rust. Against a deployment, each query returns its whole result in one response, which the SDK holds in memory before the first batch, so read a large table in bounded pieces, for example with `limit` and `offset` or a filter.
In OSS, you can also stream batches through the underlying Lance dataset directly — useful when you need filter pushdown or fragment-level parallelism via `ds.scanner(...)`:
@@ -66,7 +73,7 @@ for batch in ds.to_batches(columns=["id", "text"], batch_size=10000):
<Info>
**API disparity between OSS and Enterprise.**
LanceDB OSS exposes the `to_pandas()`, `to_arrow()`, and `table.to_lance()` methods for direct Lance dataset access. Enterprise's `RemoteTable` exposes none of these. Only `table.search(...)` and `table.query(...)`. In general, it's always best to go through `search()` and `query()` to keep your code portable across both OSS and Enterprise.
A local table also gives direct Lance dataset access through `table.to_lance()`. Against a deployment, the synchronous Python table raises for `to_pandas()`, `to_arrow()` and `to_lance()`, while async Python and TypeScript run `to_arrow()` as a query over the whole table. Going through queries keeps your code portable across both OSS and Enterprise.
</Info>
#### Diagnostics {#diagnostics}