RAG & Retrieval Systems Series — Presentation 02

Vector Databases — Recall, Latency & Scale

HNSW, IVF, PQ, scalar & binary quantisation; pgvector, Qdrant, Weaviate, LanceDB, Pinecone, Milvus — the full decision space for 1 k to 1 B vectors.

HNSW IVF + PQ Quantisation Filtered Search pgvector Qdrant Milvus
Embed → Quantise → Index (HNSW/IVF) → Filter → ANN Search → Re-score → Top-k
00

Topics We'll Cover

01

The Vector DB Landscape

Vector databases are not a single category. They differ fundamentally on four axes: storage architecture, indexing algorithm, filter capability, and operational model. Before choosing, classify your workload:

Pure vector stores

Pinecone, Weaviate Standalone, Qdrant. Purpose-built. Best recall and filter latency. No SQL. Operational complexity: separate service, separate backup, separate auth.

Embedded / in-process

LanceDB (DuckDB lineage), ChromaDB, Faiss (raw library). Zero extra infra. LanceDB stores data in Parquet via Lance format on S3/GCS — serverless-friendly. Faiss is a library, not a server.

Postgres extensions

pgvector (HNSW + IVFFLAT), pgvecto.rs (Rust, faster build). Use if you already run Postgres — one less service, full ACID, SQL JOINs across metadata and vectors. 10–20% slower recall than native vector DBs at >1 M vectors.

Milvus / Zilliz — the scale champion

Milvus 2.x is the open-source choice for >100 M vectors: disaggregated storage/compute via S3 + etcd + Pulsar, GPU-accelerated IVF-PQ and RAFT-CAGRA indexes, horizontal scaling to billions of vectors. Zilliz Cloud is the managed version. If you are below 10 M vectors and don't need GPU indexing, Milvus's operational complexity is not warranted.

02

ANN Algorithms — IVF, HNSW, PQ, Scalar & Binary Quantisation

All ANN algorithms trade recall (fraction of true nearest neighbours returned) against latency and memory. The tradeoff surface has a characteristic knee; good production systems sit just above the knee.

AlgorithmIndex build timeQuery latencyMemoryIncremental adds?
IVF-FlatFast (k-means)Medium — scans nprobe clustersFull float32 vectorsYes, but nlist rebuild recommended
IVF-PQMedium (k-means + PQ training)Fast — compressed distance8–32× compression vs float32Yes
HNSWSlow (graph build O(N log N))Very fast — graph traversalFull vectors + graph overhead (~50%)Yes, amortised O(log N)
Scalar quant (SQ8)FastFast — int8 dot-product4× compression vs float32Yes
Binary quantVery fastVery fast — popcount32× compressionYes
Binary quantisation with rescoring

Qdrant's binary quantisation compresses each float32 dimension to a single bit (sign only). The 32× size reduction means the entire index may fit in RAM. Precision loss is recovered via rescoring: ANN returns 4× more candidates from binary search, then full-precision float32 vectors are loaded for the final scoring pass. Net result: >40× memory reduction with <5% recall drop at >96% recall@10 on most benchmarks.

03

HNSW Deep Dive — Layers, ef_construction, ef_search, M

HNSW (Hierarchical Navigable Small World, Malkov & Yashunin 2016) is a layered proximity graph. Understanding its three key parameters is essential for production tuning.

Layer 2 (sparse, long-range) Layer 1 Layer 0 (dense, all nodes) Green path = greedy search descending layers toward query vector
ParameterEffectTypical rangeRule of thumb
MMax outgoing edges per node. Controls graph connectivity and memory overhead.8–6416 for general retrieval; 32+ for high-recall applications
ef_constructionSize of the dynamic candidate list during index build. Higher = better graph, slower build.100–400200 is a safe default; double for production quality
ef_searchSize of the dynamic candidate list during query. Controls the recall–latency tradeoff at query time.50–400Start at 2×k (where k is the number of results wanted)
Memory formula for HNSW

HNSW memory ≈ N × (dim × 4 bytes + M × 2 × 8 bytes). For 1 M documents at 768 dims and M=16: 1M × (3072 + 256) bytes ≈ 3.2 GB. The graph pointer overhead is around 8% at M=16; at M=64 it rises to 25%. pgvector's HNSW uses hnsw.m and hnsw.ef_search as session parameters.

04

IVF + PQ — Compression for 100M+ Vectors

Inverted File Index (IVF) partitions the vector space into Voronoi cells via k-means. Product Quantisation (PQ, Jégou et al. 2011) compresses each vector by splitting it into M sub-vectors and quantising each sub-vector against a codebook of 256 centroids, yielding M bytes per vector.

1. k-means training: nlist=4096 cluster centroids from a sample
↓
2. Assign each document vector to its nearest centroid (inverted list)
↓
3. PQ-compress each residual (vector − centroid): 768d → 96 bytes
↓
4. At query time: scan nprobe=64 lists, compute PQ ADC distances
↓
5. Re-rank top candidates with full float32 vectors (optional)
ScenarionlistnprobePQ bytesRecall@10RAM for 100M × 768d
Fast/low recall40963264~80%~6.4 GB
Balanced409612896~92%~9.6 GB
High recall8192256192~97%~19.2 GB
Baseline (IVF-Flat)4096128n/a (full)~99%307 GB
FAISS index string syntax

FAISS uses a compact DSL to specify index types. IVF4096,PQ96 means IVF with 4096 clusters and PQ with 96 bytes. IVF4096_HNSW32,PQ96 uses an HNSW index over the centroids instead of exhaustive centroid search — faster for nlist > 1000. OPQ96_192,IVF4096,PQ96 adds an orthogonal rotation preprocessing step that improves PQ reconstruction accuracy by ~1–2 NDCG points at no runtime cost.

05

Filtered Search — Pre/Post-Filter, Metadata Indexing

Most production RAG queries include at least one metadata filter: user_id, doc_type, date_range, tenant_id. The strategy for applying these filters determines recall and latency far more than the ANN algorithm choice.

Post-filter (naive)

Run ANN unrestricted; filter results afterward. Simple to implement. Fails when selectivity is high (e.g. 0.1% of documents match filter): ANN returns k results, all may be filtered out, requiring fallback to brute-force scan. Never use for high-selectivity filters.

Pre-filter (brute force)

Fetch all doc IDs matching the metadata filter; run exact nearest-neighbour only within that set. Correct by definition. Expensive when the matching set is large. Qdrant's payload indexes (B-tree or hash) make this efficient for selectivities > 10%.

Hybrid HNSW + filter

Qdrant's implementation: traverse the HNSW graph normally, but skip nodes that fail the payload filter. Requires that the filter predicate be checkable in O(1). Best of both worlds when filter selectivity is 1–50%. Configurable via m_payload_m and full_scan_threshold.

Qdrant filtered search (Python client v1.9)
from qdrant_client import QdrantClient, models

client = QdrantClient("localhost", port=6333)

results = client.query_points(
    collection_name="docs",
    query=query_vector,          # 1024-dim float list
    query_filter=models.Filter(
        must=[
            models.FieldCondition(
                key="tenant_id",
                match=models.MatchValue(value="acme-corp"),
            ),
            models.FieldCondition(
                key="doc_date",
                range=models.Range(gte=1704067200),  # 2024-01-01 Unix
            ),
        ]
    ),
    limit=20,
    with_payload=True,
)
06

Updates, Deletes & Snapshots

Most vector DBs are append-optimised. Understanding the mutation model prevents data-loss incidents and painful re-index operations.

SystemDelete strategyUpdate strategySnapshot / backup
QdrantSoft-delete via tombstone in the WAL; compacted on segment merge. O(1) delete API.Upsert by ID: delete old + insert new in one atomic operation.PUT /collections/{name}/snapshots → tar.gz. Supports S3 upload directly.
WeaviateImmediate delete; segment compaction in background (HNSW lazy delete).PUT /v1/objects/{id}: full document replacement.Backup API to S3/GCS/Azure Blob; incremental since v1.23.
MilvusMark-and-compact; auto-compaction threshold configurable.Delete + insert (no in-place). Use upsert() API in pymilvus ≥ 2.3.Milvus Backup tool; etcd + S3 for metadata + segments.
pgvectorStandard Postgres MVCC. No special handling.Standard UPDATE. HNSW index updated in place; VACUUM reclaims space.Standard Postgres WAL / pg_dump / PITR.
LanceDBAppend-only Lance format; deletions written as separate delete files merged lazily.table.merge_insert() in LanceDB 0.5+.Lance files are Parquet-like; back up with standard object-store sync.
The stale-index trap

HNSW does not support efficient in-place deletion. Qdrant and Weaviate both use a lazy strategy: deleted node IDs are tombstoned in memory and the graph retains stale edges until a segment rewrite is triggered. If you delete >20% of a collection, trigger compaction manually or your recall will degrade as searches traverse dead nodes. Qdrant: POST /collections/{name}/points/delete + monitor optimizers_status.

07

pgvector in Depth — IVFFLAT vs HNSW on Postgres

pgvector (v0.7, 2024) added HNSW support, making it a serious option for <5 M vector workloads already running Postgres. The two index types behave very differently under load.

IVFFLAT

Build: CREATE INDEX ... USING ivfflat (embedding vector_cosine_ops) WITH (lists=256). Fast build, slow query (scan probes lists). Set SET ivfflat.probes = 10 per session. Recommend building after bulk load: SET maintenance_work_mem = '2GB'. Rule of thumb: lists = sqrt(N) for N ≤ 1 M; lists = N/1000 for N > 1 M.

HNSW

Build: CREATE INDEX ... USING hnsw (embedding vector_cosine_ops) WITH (m=16, ef_construction=64). Slower build, faster and higher-recall query. Uses hnsw.ef_search. Memory during build: pgvector's HNSW build requires ~2–4 GB for 1 M × 768d. Set maintenance_work_mem = '4GB'. Index is stored in Postgres heap pages — no separate file.

pgvector HNSW setup and query (Postgres 16)
-- Extension + table
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE documents (
    id       bigserial PRIMARY KEY,
    content  text,
    metadata jsonb,
    embedding vector(768)
);

-- Build HNSW index after bulk load
SET maintenance_work_mem = '4GB';
CREATE INDEX docs_embedding_hnsw
    ON documents
    USING hnsw (embedding vector_cosine_ops)
    WITH (m=16, ef_construction=128);

-- Query: top-10 nearest with metadata filter
SET hnsw.ef_search = 80;
SELECT id, content, 1 - (embedding <=> '[...]'::vector) AS score
FROM  documents
WHERE metadata->>'tenant' = 'acme'
ORDER BY embedding <=> '[...]'::vector
LIMIT 10;
08

Choosing a Vector DB — Decision Matrix

There is no universally best choice. The right system depends on scale, latency SLA, filter complexity, and what you already run in production.

SystemSweet spotIndex typesManaged optionFilter qualityNotable gap
pgvector<5 M; already on PostgresHNSW, IVFFLATRDS/Supabase/NeonFull SQL WHERESlower than native VDBs; no binary quant
Qdrant1 k – 100 M; strong filter needsHNSW + binary/scalar quantQdrant CloudExcellent (payload index)No GPU indexing
Weaviate1 k – 50 M; multi-modal, hybridHNSW, flatWeaviate CloudGood (BM25 hybrid built-in)Heavier RAM footprint
LanceDBServerless; S3-native; analyticsIVF-PQ, HNSWLanceDB CloudSQL via DuckDBNo real-time sub-millisecond queries
PineconeZero-ops; variable loadProprietary (HNSW-like)SaaS onlyMetadata filtersVendor lock-in; no self-host
Milvus>100 M; GPU workloadsIVF-PQ, HNSW, GPU RAFTZilliz CloudGood (scalar index)Operational complexity
Start with pgvector, graduate to Qdrant

For teams starting fresh: use pgvector on your existing Postgres. When you hit >5 M documents or need binary quantisation, migrate to Qdrant. LanceDB is the right default for data-science teams using DuckDB and storing data on S3. Pinecone is justified only if you have strict zero-ops requirements and can accept the cost and lock-in.

09

What to Take Away

Where to next

Deck 03 covers hybrid search — combining BM25 sparse retrieval with dense vectors via Reciprocal Rank Fusion — and reranking pipelines including BGE-reranker-v2-m3, Cohere Rerank 3.5, and ColBERT-as-reranker via PLAID.