> For the complete documentation index, see [llms.txt](https://docs.telys.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.telys.ai/understand-telys/search-internals-ivf.md).

# Search internals

Telys search has two modes. Small collections are scanned **exactly** — every vector is compared, so recall is 100% and there is nothing to tune. Large collections use an **IVF** (inverted-file) index that scans only the most promising slices, trading a controllable amount of recall for a large latency drop. This page covers both modes, the threshold between them, the `build_ivf` and `target_recall` knobs, and how tuning sets them for you. Correct results need none of this; scale needs it.

## Exact search comes first

For a small collection, the fastest correct thing to do is compare the query against every stored vector. That is what Telys does by default:

```python
res = col.search_text("how do I cancel my plan", top_k=5)
print(res["ids"])
print(res["scores"])
```

Exact search always returns the true nearest neighbors. Below the **exact-crossover** row count Telys stays in this mode — an approximate index would only add overhead. There is no recall knob because recall is already perfect.

## IVF for scale

An IVF index partitions the vectors into `nlist` cells, each with a centroid. At query time Telys compares the query to the centroids, picks the closest `nprobe` cells, and scans only the vectors inside them. Scanning a handful of cells instead of everything is what makes large-scale search fast.

```
       all vectors                         query
   ┌──────────────────┐                      │
   │  •  •   •    •    │                      ▼
   │ • [c1] •  [c2] •  │   1. compare query to nlist centroids (c1..cN)
   │   •  •    •  •    │   2. pick the nprobe closest cells
   │ [c3]  •  • [c4]•  │   3. scan only the vectors inside those cells
   └──────────────────┘   4. return top_k from what was scanned
     nlist cells
```

The tradeoff is direct: a larger `nprobe` scans more cells, so recall climbs toward exact but latency rises with it. `nlist` controls how finely the space is partitioned. You rarely set these by hand — express a *recall target* and let tuning choose them.

## Building the index

`build_ivf` constructs the IVF index for a collection:

```python
col.build_ivf(min_rows=20000, target_recall=0.98)
```

* `min_rows` (default `20000`) — the collection must have at least this many rows before an IVF is built. Under this threshold, exact search is used and this call is effectively a no-op.
* `target_recall` (default `0.98`) — the recall the index is built to hit. Telys sizes `nlist`/`nprobe` to reach roughly this fraction of the exact result.

## The target\_recall knob

`target_recall` is the single dial that trades recall against latency. It has a persisted default (set via tuning) and can be overridden per query:

```python
# Faster, slightly lower recall — probe fewer cells for this one query.
fast = col.search(query_vec, top_k=10, target_recall=0.90)

# Use the collection's default recall (no override).
normal = col.search(query_vec, top_k=10)
```

Higher `target_recall` → more cells probed → results closer to exact → higher latency. Lower `target_recall` → fewer cells → faster → some true neighbors may be missed. Because exact search always runs below the crossover, small collections ignore this knob entirely.

{% hint style="info" %}
`search` validates that `len(vector) == dim`. Use `search_text` to embed a query string with the collection's embedder before searching. Both accept `top_k`, `where`, `explain`, `target_recall`, and `with_metadata`.
{% endhint %}

## Per-partition indexes

Physical partitions come from `partition_by` (compose a single key with `scope_key(...)`; a list/tuple uses only its first element). IVF is built and probed **per partition** — each gets its own `nlist`/`nprobe`, and a scoped query touches only that partition's cells, so one tenant never pays for another's data. See [Partitions & scope keys](/understand-telys/partitions-scope-key.md).

## How tuning sets all of this

You normally do not set `nlist`, `nprobe`, or the crossover by hand. A tuning pass measures your workload, produces a `TuningPlan`, and `apply_tuning` writes it into the collection:

```python
plan = col.tune()                 # produces a TuningPlan (needs a Tuner)
stats = col.apply_tuning(plan)    # sets default_target_recall + exact-crossover,
                                  # builds/uses per-partition IVF, sets nprobe
col.save()                        # persists default_target_recall and the applied tuner
```

`apply_tuning` sets the default target recall, the exact-crossover row count, builds or uses the per-partition IVF, and sets `nprobe`. The plan's `ivf`, `target_recall`, and `exact_crossover_rows` fields record exactly what was chosen. See [Tuner & TuningPlan](/tuning/tuner-and-plan.md).

## Seeing what search did

Pass `explain=True` to get an `explain` block describing how the query was executed — which mode ran and what was probed:

```python
res = col.search_text("cancel my plan", top_k=5, explain=True)
print(res["explain"])
```

{% hint style="warning" %}
`RemoteCollection` (over `RemoteTelys`) is a subset of the local collection: it has **no** `build_ivf`, and its `search` has **no** `target_recall`. Build and tune the index on the serving host, then query it remotely. See [Connect with RemoteTelys](/self-hosting/connecting-remote.md).
{% endhint %}

## Next steps

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Build the IVF index</strong></td><td>A step-by-step guide to running build_ivf on a collection.</td><td><a href="/pages/vAtnT9NtTA0el7skR3vX">/pages/vAtnT9NtTA0el7skR3vX</a></td></tr><tr><td><strong>Why tune</strong></td><td>When tuning helps and what it changes about search.</td><td><a href="/pages/dUb8SpYWDKmPq4apsKnQ">/pages/dUb8SpYWDKmPq4apsKnQ</a></td></tr><tr><td><strong>Tuning &#x26; tradeoffs</strong></td><td>The recall-vs-latency picture across a whole collection.</td><td><a href="/pages/tVrk8vG4vvlGOMGKSaut">/pages/tVrk8vG4vvlGOMGKSaut</a></td></tr></tbody></table>

Next: if results look thin, see [No results / poor recall](/troubleshooting/search-and-recall.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.telys.ai/understand-telys/search-internals-ivf.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
