Vector Databases Explained: Indexes, Filtering and Scale
How vector databases actually work — similarity metrics, HNSW and IVF index tradeoffs, filtered search, quantization and sharding — plus a survey of the main options.
Updated 12 min read
A vector database stores embeddings and finds the ones closest to a query vector. Strip away the marketing and every product in the category is the same three components: a distance function, an approximate nearest neighbor index, and a storage layer that also handles metadata filtering.
Understanding those three explains nearly every performance and cost difference between the options, and it explains why the choice matters less than most teams expect.
Which similarity metric should you use?
The operational rule is short: use the metric the embedding model was trained with, which for most modern text embedding models is cosine similarity. If you normalize vectors to unit length at ingest, cosine and inner product become interchangeable — identical rankings, different numbers. Normalizing is generally worth it because inner product is cheaper to compute.
An embedding model maps text, images or audio to a fixed-length array of floats — commonly a few hundred to a few thousand dimensions. The training objective arranges the space so that semantically related inputs land near each other.
Three distance measures are in general use.
Cosine similarity measures the angle between vectors, ignoring magnitude. It is the default for text embeddings because document length should not affect semantic similarity.
Inner product (dot product) accounts for both angle and magnitude. Some models are trained for this explicitly. On vectors normalized to unit length, inner product and cosine give identical rankings.
Euclidean distance measures straight-line distance. Common for image embeddings and, on normalized vectors, monotonically related to cosine similarity — same ranking, different numbers.
How much memory do the vectors need?
Dimensionality drives cost. A float32 vector consumes four bytes per dimension, so one million 1536-dimension vectors is about 6 GB of raw data before any index structure — and a graph index adds substantial overhead on top of that. Truncating the vector is the cheapest lever available for cutting both numbers.
Several current embedding models are trained with nested representations that let you truncate the vector — taking the first 512 of 1536 dimensions, for example — with graceful rather than catastrophic quality loss. This is the cheapest lever available for cutting memory and search cost, and it is underused. Test truncation against your retrieval evaluation set before assuming you need the full width.
Which index type should you choose?
Start with HNSW: it gives the best recall-per-latency of the mainstream options and is the default in most vector databases. Below roughly 100,000 vectors, skip the index and use flat brute-force search. When memory becomes the binding constraint, move to IVF with product quantization, or add scalar or binary quantization on top of HNSW.
Exact nearest neighbor search compares the query against every vector. That is O(N) and, with modern SIMD instructions, genuinely fine up to a few hundred thousand vectors. Beyond that you trade a small amount of recall for a large amount of speed.
Flat (brute force)
No index. Compare against everything. Perfect recall, linear cost, zero build time, no tuning.
Use it below roughly 100,000 vectors, for any collection that changes constantly, and as the ground truth when measuring another index’s recall. Skipping this last use is a common mistake — you cannot know your ANN index’s recall without an exact baseline to compare against.
IVF (inverted file)
Cluster the vectors with k-means into nlist cells and record each cell’s centroid. At query time, find the nearest nprobe centroids and search only vectors in those cells.
Fast to build, modest memory overhead, and recall is tunable at query time by raising nprobe. The weaknesses are that it requires a training pass over representative data, that quality degrades as the distribution drifts away from what it was trained on, and that vectors near cell boundaries get missed.
IVF combined with product quantization is the standard configuration for very large collections where memory is the binding constraint.
HNSW (hierarchical navigable small world)
A multi-layer proximity graph. Upper layers are sparse and connect distant regions; lower layers are dense and local. Search enters at the top, greedily walks toward the query, and descends a layer at a time — coarse navigation followed by fine refinement.
HNSW gives the best recall-per-latency of the mainstream options and is the default in most vector databases. Three parameters govern it:
- M — connections per node. Higher improves recall and increases memory.
- efConstruction — candidate list size during build. Higher means better graph quality and slower builds.
- efSearch — candidate list size at query time. The main runtime recall/latency dial.
The costs are real. Memory overhead is significant on top of the vectors themselves, index construction is slow for large collections, and deletions are soft — nodes are marked rather than removed, so heavy churn degrades the graph until it is rebuilt.
How much does quantization save?
Compression applied to the vectors themselves, usually combined with an index. Scalar quantization cuts memory by four with small recall loss. Product quantization compresses by an order of magnitude or more with real recall loss. Binary quantization is thirty-two times smaller and recall drops substantially, so it is paired with oversampling and rescoring.
Scalar quantization maps each float32 dimension to an int8, cutting memory by four with small recall loss. Close to a free win and a sensible default above a few million vectors.
Product quantization splits the vector into subvectors and replaces each with a codebook index. Compression of an order of magnitude or more, with real recall loss, so it is nearly always paired with a rescoring pass over full-precision vectors.
Binary quantization reduces each dimension to one bit — thirty-two times smaller, and distance becomes a Hamming computation the CPU does extremely quickly. Recall drops substantially, so it is used with oversampling: retrieve several hundred candidates from the binary index, then rescore against full vectors. On high-dimensional embeddings the combination often preserves most of the original recall at a fraction of the memory.
Disk-based indexes
DiskANN-style graph indexes keep the graph on SSD with a compressed representation in memory, enabling billion-scale collections on hardware that could not hold them in RAM. Latency is higher than in-memory HNSW but the cost curve is dramatically better. Several systems now expose this, including as an extension to Postgres.
Index comparison
| Index | Recall | Query latency | Memory | Build time | Handles updates | Use when |
|---|---|---|---|---|---|---|
| Flat | Exact | Linear in N | Vectors only | None | Trivially | Under ~100k vectors, or as ground truth |
| IVF | Tunable, good | Low | Low overhead | Fast, needs training | Moderate | Large collections, memory constrained |
| IVF + PQ | Moderate, needs rescoring | Very low | Very low | Fast | Moderate | Billion-scale on limited RAM |
| HNSW | Very high | Very low | High overhead | Slow | Poor for deletes | The default for most workloads |
| HNSW + scalar quant | High | Very low | Reduced ~4x | Slow | Poor for deletes | Millions of vectors, cost-sensitive |
| Binary + rescore | Good with oversampling | Extremely low | Reduced ~32x | Moderate | Moderate | High-dimensional, very large, latency-critical |
| Disk-based graph | High | Moderate | Low RAM, high disk | Slow | Moderate | Billion-scale on commodity hardware |
Why does filtering break vector search?
Because the ANN index is built over the whole collection, not over the filtered subset. Post-filtering retrieves the top k by similarity and then discards results failing the filter, so a selective filter can return almost nothing. Pre-filtering is correct but expensive. Integrated filtering applies the predicate during traversal and is what the better systems do.
Pure similarity search is a solved problem. Similarity search constrained by metadata — this tenant, published after this date, status active, department engineering — is where systems differ most, and where naive implementations fail badly.
Post-filtering searches for the top k by similarity, then discards results failing the filter. Fast, and it silently breaks. Ask for ten results with a filter matching one percent of the corpus and you may get zero, because none of the hundred nearest neighbors happened to match.
Pre-filtering determines the matching subset first, then searches only within it. Correct, and expensive when the subset is large, because ANN indexes are built over the whole collection and cannot be trivially restricted.
Integrated filtering applies the predicate during graph traversal, so the search only visits qualifying nodes. This is what the better systems do, and it is a genuine engineering differentiator. The complication is graph connectivity: if the filter is highly selective, the qualifying nodes may not be reachable from each other, and the search stalls. Mature implementations detect this and fall back to exact search over the filtered subset, which is fast precisely because the subset is small.
When evaluating a vector database, test filtered queries at several selectivity levels — one percent, ten percent, fifty percent — not just unfiltered ones. Unfiltered benchmarks are close to meaningless for production RAG, where nearly every query carries a permission or recency constraint.
What is hybrid search?
Dense retrieval blurs exact tokens; keyword retrieval nails them. Production retrieval runs both and fuses the results, typically with reciprocal rank fusion. Systems with native hybrid support — a single query returning fused results — save you from maintaining two stores and reconciling them. Our guide to retrieval-augmented generation covers why this matters more than any index-level optimization.
Scale and operations
Sharding and replication
Sharding splits the collection across nodes; each shard holds an independent index, queries fan out, and results merge. It addresses collections too large for one machine. Replication copies shards for throughput and availability. The two are orthogonal and both are usually needed.
The subtlety is that ANN recall is affected by sharding: each shard returns its local top k, and the merged result is not necessarily the global top k. Requesting more results per shard than you need mitigates this.
Index builds are a batch job
Building an HNSW index over tens of millions of vectors takes hours and saturates CPU. Treat it as a pipeline stage, not an online operation. Systems that separate storage from compute let you build on ephemeral capacity and serve from cheaper nodes.
How should you isolate tenants?
Three approaches, with different failure modes. A metadata field per tenant is simplest and depends entirely on filtered search performing well. A separate collection or namespace per tenant gives clean isolation and stops scaling somewhere in the thousands of tenants. Partitioned indexes within one collection are the middle ground several systems now offer specifically for this.
Pick deliberately. Retrofitting tenant isolation onto a shared index after a compliance review is a painful migration.
Freshness
Some systems index synchronously on write; others batch. If your application writes a document and immediately queries for it — common in agent workflows — verify the visibility guarantee rather than assuming it.
What are the main options?
pgvector for teams already running Postgres, Qdrant when metadata filtering is central, Weaviate when you want the database to own the embedding step, Pinecone when you want no operational burden, Milvus at genuinely large scale, Chroma for prototypes, and LanceDB when your data lives in object storage. Each is described below.
pgvector adds vector types and HNSW/IVFFlat indexes to Postgres. Its advantage is not raw speed but locality: vectors sit in the same transactional database as your relational data, so filters and joins are ordinary SQL and there is no second system to keep in sync. Companion extensions add DiskANN-style indexing and streaming filtered search. For most teams under ten million vectors who already run Postgres, this is the correct default.
Qdrant is a Rust-based dedicated engine with strong filtered search, a full range of quantization options including binary, on-disk storage modes and a clean API. A good choice when metadata filtering is central and you want predictable behavior under selective queries.
Weaviate is a Go-based engine with native hybrid search, a module system for embedding generation at ingest, and mature multi-tenancy. Attractive when you want the database to own the embedding step.
Pinecone is fully managed and proprietary, with a serverless model that separates storage from compute and bills by usage. You give up control and portability; you get no operational burden. For teams without infrastructure engineers, that is often the right trade.
Milvus is a distributed system built for very large deployments, with the widest range of index types including GPU-accelerated options. The most operationally complex option, and appropriate at genuinely large scale or via its managed service.
Chroma targets development ergonomics — embedded mode, minimal setup, a simple API. Excellent for prototypes and small applications, and most teams migrate as scale grows.
LanceDB is embedded and built on a columnar file format designed for object storage, giving versioning, zero-copy reads and good multimodal support. Compelling when data lives in S3 and you want to avoid running a server.
Adjacent systems worth knowing
Also worth knowing: Elasticsearch and OpenSearch combine vector search with mature BM25 and are the obvious answer if you already operate them; Vespa offers the most sophisticated ranking and hybrid capabilities at the cost of a steep learning curve; FAISS is a library rather than a database and remains the reference implementation for index research; and several object-storage-native services now offer very low cost per stored vector for workloads that tolerate higher latency.
How do you choose a vector database?
Answer four questions in order: how many vectors you will realistically hold in eighteen months, whether you already run Postgres, how selective your filters are, and who operates the thing. Under a million vectors almost anything works, so optimize for operational simplicity. If you already run Postgres and are in the first two size bands, pgvector is usually the answer.
1. How many vectors, realistically, in eighteen months? Under a million, almost anything works — optimize for operational simplicity. One to fifty million is the mainstream band where pgvector and the dedicated engines all compete. Above that, you are choosing between distributed systems and disk-based indexes.
2. Do you already run Postgres? If yes, and you are in the first two bands, use pgvector unless you have a specific reason not to. Eliminating a second datastore, its sync pipeline and its failure modes is worth more than a marginal latency improvement.
3. How selective are your filters? If every query carries a tenant id, permission scope or date range that eliminates most of the corpus, filtered search performance is your primary criterion. Benchmark it directly at your real selectivity.
4. Who operates it? Without dedicated infrastructure capacity, take the managed option. The total cost of an unmanaged cluster is not the license.
What not to optimize for
Two things not to optimize for. Benchmark charts published by vendors measure unfiltered queries on uniform synthetic data and will not predict your workload. And index type matters far less than retrieval design — chunking, hybrid search and reranking move quality much more than swapping HNSW parameters.
-- pgvector: index, then query with a metadata filter in one statement
CREATE TABLE chunks (
id bigserial PRIMARY KEY,
document_id bigint NOT NULL,
tenant_id bigint NOT NULL,
published date NOT NULL,
content text NOT NULL,
embedding vector(1024) NOT NULL
);
CREATE INDEX ON chunks USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
CREATE INDEX ON chunks (tenant_id, published);
SET hnsw.ef_search = 100;
SELECT id, content, 1 - (embedding <=> $1) AS similarity
FROM chunks
WHERE tenant_id = $2
AND published >= now() - interval '18 months'
ORDER BY embedding <=> $1
LIMIT 20;
The <=> operator is cosine distance, so 1 - distance gives similarity. Note that ef_search is a session setting: raise it to trade latency for recall on important queries and lower it for bulk work.
# Measuring recall against an exact baseline — do this before tuning anything
def recall_at_k(index, exact, queries, k: int = 10) -> float:
hits = 0
for q in queries:
approx = set(index.search(q, k=k))
truth = set(exact.search(q, k=k))
hits += len(approx & truth) / k
return hits / len(queries)
# Sweep the runtime parameter and pick the knee of the curve
for ef in (32, 64, 128, 256, 512):
index.set_ef_search(ef)
print(ef, recall_at_k(index, exact_index, sample_queries))
Run that sweep on your own data. Published defaults are chosen for benchmark datasets whose distribution is nothing like a corpus of support tickets or contracts.
The bottom line
A vector database is an ANN index with metadata handling attached. Choose the metric your embedding model was trained with, start with HNSW, add scalar or binary quantization when memory becomes the constraint, and measure recall against an exact baseline rather than trusting defaults.
Weight filtered search performance heavily, because production queries are almost always filtered, and weight operational simplicity above marginal latency. If you already run Postgres and are under ten million vectors, pgvector is very likely the answer.
Then spend the time you saved on retrieval quality. The index rarely determines whether your system returns the right passage — chunking, hybrid search and reranking do, as do the agent architectures built on top of them.
More in the vector databases category, and see our OpenAI API tutorial for generating the embeddings themselves.
Choosing an AI stack for your team?
We help companies pick the right AI tools, wire them into existing systems and avoid the ones that quietly do not scale. Tell us what you are trying to build and we will tell you what we would use — no charge for the conversation.
Join the discussion
Comments are not enabled on this article yet. Reach the editorial desk directly with corrections or additions.
Contact the editors