Vector Index Cheat Sheet

Quick reference. Pair with lessons/0002-vector-search-at-scale.html.

Algorithm families at a glance

AlgorithmMental modelBuild costQuery costMemory
Exact k-NN (brute force) Compare against all vectors 0 O(N · d) O(N · d)
HNSW Multi-layer graph; navigate "social network" O(N · log N) O(log N) O(N · d + N · M)
IVF (flat) Cluster vectors; search only nearest clusters O(N · k) (k-means) O((N/nlist) · nprobe) O(N · d)
IVF + PQ IVF + compressed vectors (~50 bytes each) IVF + codebook build Faster than IVF (cache-friendly) O(N · d / 64)

LanceDB index types (v0.33)

Config classComposed ofBest for
IvfFlatIVF + no compressionHighest recall, more memory
IvfPqIVF + Product QuantizationBillion-scale, memory-bound
IvfRqIVF + RaBitQMax compression
IvfSqIVF + Scalar QuantizationLight compression, fast
IvfHnswFlatIVF + HNSW + no compressionHighest recall, low latency
IvfHnswPqIVF + HNSW + PQLow latency + low memory
IvfHnswSqIVF + HNSW + Scalar QuantizationDefault recommendation: best latency/recall balance

API quick reference

Create an index

from lancedb.pydantic import IvfPq, IvfHnswSq

# IVF + PQ
table.create_index("vector", config=IvfPq(
    num_partitions=256,    # ~ rows // 4096
    num_sub_vectors=96,    # ~ dim // 8
    metric="cosine",
))

# IVF + HNSW + SQ (default recommendation for 100k–10M)
table.create_index("vector", config=IvfHnswSq(
    num_partitions=256,    # ~ rows // 4,096
    m=16,
    ef_construction=150,
    metric="l2",
))

Search (with or without index)

# With index, ANN is used automatically
results = table.search(query_vec).limit(10).to_list()

# Force exact k-NN (bypass index, useful for ground-truth benchmarking)
results = table.search(query_vec).bypass_vector_index().limit(10).to_list()

Parameter cheat sheet

ParamAlgorithmPhaseEffect of raising
MHNSWBuild↑ recall, ↑ memory, ↑ build time
ef_constructionHNSWBuild↑ graph quality, ↑ build time
ef_searchHNSWQuery↑ recall, ↓ query speed
nlistIVFBuild↑ query speed (up to a point), ↑ build time
nprobeIVFQuery↑ recall, ↓ query speed
num_partitionsIVFBuildSame as nlist
num_sub_vectorsPQBuild↑ recall, ↑ memory

When to use what (decision tree)

< 100k rows
Don't create an index. Exact k-NN is faster overall and uses less memory.
100k – 10M rows
IvfHnswSq with default params. Tune M and ef_construction if recall is low.
10M+ rows, memory-bound
IvfPq or IvfRq. Accept 3–5% recall drop for 30–60× memory savings.
Filtered queries (where(...))
HNSW-backed indexes can be slower with filters. IvfPq or IvfRq handle filters more predictably.

The "magic triangle"

You can only pick two of three:

Index parameter tuning is the art of finding the best 2-of-3 point for your workload.

Recall benchmark recipe

import random
import lancedb
from rag_pipeline import Embedder

embedder = Embedder()
db = lancedb.connect("data/lancedb")
table = db.open_table("rag_chunks")

# Sample 50 random query texts from your real index
contents = table.to_pandas()["content"].tolist()
samples = random.sample(contents, 50)

recalls = []
for text in samples:
    qv = embedder.embed_one(text)

    # Ground truth via exact k-NN
    gt = table.search(qv).bypass_vector_index().limit(10).to_list()
    gt_ids = {r["id"] for r in gt}

    # Approximate
    ann = table.search(qv).limit(10).to_list()
    ann_ids = {r["id"] for r in ann}

    recalls.append(len(gt_ids & ann_ids) / len(gt_ids))

print(f"recall@10 mean = {sum(recalls) / len(recalls):.3f}")
print(f"recall@10 min  = {min(recalls):.3f}")

Targets: mean ≥ 0.95, min ≥ 0.85. If min is too low, the index is missing queries that the user's intuition would say "should obviously work." Raise ef_search (HNSW) or nprobe (IVF) until you hit your target.

Glossary

TermMeaning
Exact k-NNBrute-force scan of every vector. O(N) per query. Default in LanceDB without an index.
ANNApproximate Nearest Neighbor. Trades recall for speed.
Recall@kFraction of the true top-k that the index returned.
HNSWHierarchical Navigable Small World. Graph-based ANN.
IVFInverted File Index. Cluster-based ANN.
PQProduct Quantization. Lossy vector compression.
QuantizerThe compression strategy inside an index: Flat (none), SQ (scalar), PQ (product), RQ (RaBitQ).
nprobeHow many IVF clusters to search at query time.
nlistHow many IVF clusters to build at index time.
CentroidThe "center" of an IVF cluster. Vectors assigned to the cluster are near it.
Cosine vs L2Both are distance metrics. Cosine is direction-only; L2 is straight-line. With normalized vectors, they agree up to scale.
bypass_vector_index()LanceDB method to force exact k-NN even when an index exists. Use it for ground-truth benchmarking.