Adding pgvector to PostgreSQL does not make vector similarity search fast on its own. Without an index, every query scans the whole table and computes an exact distance for each row. The index types are what buy you speed, and pgvector ships two of them. If you're using Drizzle ORM, I also wrote about how to set up pgvector with Drizzle including schema definition and index creation.
The two pgvector index types are HNSW (Hierarchical Navigable Small World) and IVFFlat (Inverted File Flat). Both are approximate, so both trade some recall for speed. They differ in where that trade lands.
HNSW arrived in pgvector 0.5.0. It usually returns better recall at a given query speed than IVFFlat, and it pays for that with a slower build. Three things to know:
To create an HNSW index, you'd use something like this:
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);You can tune HNSW performance with two main parameters:
m: The max number of connections per layer (default is 16)ef_construction: The size of the dynamic candidate list for constructing the graph (default is 64)For example:
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops) WITH (m = 16, ef_construction = 64);During queries, you can adjust the ef_search parameter to balance between speed and recall:
SET hnsw.ef_search = 100;IVFFlat is the older method and takes a different approach:
The shortest IVFFlat create index example takes the defaults and needs data in the table first:
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops);Set lists yourself to control how many partitions the index builds:
CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);The key to good performance with IVFFlat is choosing the right number of lists. A good rule of thumb is:
rows / 1000sqrt(rows)During queries, you can adjust the number of probes to balance between speed and recall:
SET ivfflat.probes = 10;Four guidelines cover most cases:
You can also create both index types on the same column and compare their performance on your own data.
The pgvector halfvec type stores each dimension as a 16-bit float instead of a 32-bit one. Casting to it inside the index expression halves the index size and raises the dimension ceiling to 4,000:
CREATE INDEX ON items USING hnsw ((embedding::halfvec(3)) halfvec_l2_ops);The column itself stays vector, so only the index pays the precision cost. That matters most on large datasets with high-dimensional embeddings.
pgvector offers two main indexing options: HNSW and IVFFlat. HNSW is faster for queries but slower to build, while IVFFlat is quicker to build and better for large result sets. Choose HNSW for speed and IVFFlat for flexibility with large datasets. Don't forget to experiment with parameters like ef_search for HNSW and probes for IVFFlat to fine-tune performance. Not sure which distance operator to pair with your index? Here's what <->, <=>, and <#> actually mean.
Occasional notes on software, tools, and things I learn. No spam.
Unsubscribe anytime.