Concepts
Database
A database is one file, by convention with the .rvec extension. It holds any number of named collections.
- Opening loads everything into memory. Searches never touch the disk.
- Changes stay in memory until you save.
save()writes the whole database to a temporary file, flushes it to disk and renames it over the original, so the file on disk is always either the old version or the new one, never a mix. In Python,with rv.Database(...) as db:saves when the block ends without an exception. - One writer. Two processes that open the same file and both save will overwrite each other's changes. Share a database between processes only for reading.
Collections
A collection is a set of records with a fixed dimension and metric, plus its own HNSW index. Choose them when you create the collection; they cannot change later.
| Setting | Default | Meaning |
|---|---|---|
dim |
required | Vector length, 1 to 65,536 |
metric |
cosine |
cosine, l2 or dot (see below) |
m |
16 | Links per node on upper layers of the graph; layer 0 allows 2 * m. 2 to 256 |
ef_construction |
200 | Candidate list size while building. Higher builds a better graph, more slowly |
ef_search |
64 | Default candidate list size while searching. Higher finds more true neighbours, more slowly |
Collection names are 1 to 255 bytes of UTF-8.
Records
A record has:
- an id: any string, unique within the collection. Inserting a record with an existing id replaces it (upsert).
- a vector of
dimfinitefloat32values. NaN and infinity are rejected; forcosine, the zero vector is rejected too. - optional metadata: any JSON value, usually an object (in Python, a
dictof strings, numbers, booleans,None, lists and nested dicts). Metadata is returned with search results; object fields can be used in filters.
Metrics
Every metric is a distance: smaller is closer, and results are sorted by increasing distance.
| Metric | Distance | Notes |
|---|---|---|
cosine |
1 − cos(a, b), from 0 to 2 |
Vectors are normalized when stored, so get() returns the normalized vector. Use for most text embeddings |
l2 |
squared Euclidean distance | Not the square root: compare distances, don't read them as lengths |
dot |
negative inner product | For models trained for maximum inner product search. Distances can be negative |
For cosine, similarity is 1 - distance.
The HNSW index
Each collection keeps a hierarchical navigable small world graph. A search starts at the top layer, descends greedily, and on the bottom layer explores the ef closest candidates it has found so far. The k best of those are returned.
eftrades speed for recall. A largerefvisits more nodes and finds more of the true nearest neighbours. It must be at leastk; a smaller value is raised tok. Passef=per search, or set the collection'sef_search. Inspecting and tuning shows how to choose it.- Exact search (
exact=True) scans every record. It always returns the true nearest neighbours and is the baseline for measuring recall. - Batch inserts (
upsert_many) build index links on all CPU cores. A batch is atomic: if any vector is invalid, nothing is written. Withthreads=1the result is identical to inserting records one at a time. - Deletes remove a record from results immediately, but its node stays in the graph as a waypoint until
compact()rebuilds the collection. Replacing a record works the same way.
Threads
In Python, searches and other reads release the GIL and run in parallel from several threads. Writes (upsert, upsert_many, delete, compact, create_collection, drop_collection) wait until running reads finish and then run alone. upsert_many and compact use all cores by themselves.
In Rust, Collection is Sync: share &Collection between threads for searching, and use a lock of your choice around writes.