Approximate indexes

Updated at:
Copy as MD

By default, pgvector performs exact nearest neighbor searches with perfect recall. To increase search speed, add an approximate index — this trades a small amount of recall for significantly higher throughput. PolarDB for PostgreSQL supports two index types: Hierarchical Navigable Small Worlds (HNSW) and Inverted File with Flat Compression (IVFFlat).

Choose an index type

Start with HNSW. It delivers better query performance at any given recall level and doesn't require training data to build. Use IVFFlat only if your workload has specific constraints, such as tighter memory budgets or very infrequent queries on static datasets.

HNSWIVFFlat
AlgorithmMultilayer graphPartitioned lists
Build timeSlowerFaster
Memory usageHigherLower
Query performanceBetter speed-recall tradeoffLower speed-recall tradeoff
Requires training dataNo — build on empty tablesYes — build after loading data

HNSW

Create an HNSW index

Create a separate index for each distance function your queries use:

DistanceOperator class
Euclidean (L2)vector_l2_ops
Inner productvector_ip_ops
Cosinevector_cosine_ops
Manhattan (L1)vector_l1_ops
Hammingbit_hamming_ops
Jaccardbit_jaccard_ops

Euclidean distance (L2)

CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);
Note

Use the operator class that matches your vector type. For halfvec, use halfvec_l2_ops instead of vector_l2_ops. For sparsevec, use sparsevec_l2_ops.

Inner product

CREATE INDEX ON items USING hnsw (embedding vector_ip_ops);

Cosine distance

CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);

Manhattan distance (L1)

CREATE INDEX ON items USING hnsw (embedding vector_l1_ops);

Hamming distance

CREATE INDEX ON items USING hnsw (embedding bit_hamming_ops);

Jaccard distance

CREATE INDEX ON items USING hnsw (embedding bit_jaccard_ops);

Supported vector types:

TypeDescriptionLimit
vectorStandard float vectorsUp to 2,000 dimensions
halfvecHalf-precision vectorsUp to 4,000 dimensions
sparsevecSparse vectorsUp to 1,000 non-zero elements
bitBinary vectorsUp to 64,000 dimensions

Index creation options

ParameterDefaultDescription
m16Maximum connections per layer. Higher values improve recall but increase memory usage and build time.
ef_construction64Size of the dynamic candidate list during graph construction. Higher values improve recall but slow down indexing.
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops) WITH (m = 16, ef_construction = 64);

Query options

ParameterDefaultDescription
hnsw.ef_search40Size of the dynamic candidate list during search. Higher values improve recall but slow down queries.

Set hnsw.ef_search for the current session:

SET hnsw.ef_search = 100;

To apply it to a single query only, use SET LOCAL inside a transaction:

BEGIN;
SET LOCAL hnsw.ef_search = 100;
SELECT ...
COMMIT;

IVFFlat

IVFFlat divides vectors into lists and searches the subset of lists closest to the query vector. Build the index after loading your data — the quality of the list partitioning depends on the data distribution.

Get good recall

Follow these three steps:

  1. Create the index after the table has data. IVFFlat requires training data to partition vectors effectively.

  2. Choose the right number of lists:

    • Up to 1 million rows: lists = rows / 1000

    • Over 1 million rows: lists = sqrt(rows)

  3. Set the number of probes at query time: Use probes = sqrt(lists) as a starting point. More probes improve recall but slow down queries.

Create an IVFFlat index

Create a separate index for each distance function your queries use:

DistanceOperator class
Euclidean (L2)vector_l2_ops
Inner productvector_ip_ops
Cosinevector_cosine_ops
Hammingbit_hamming_ops

Euclidean distance (L2)

CREATE INDEX ON items USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);
Note

Use the operator class that matches your vector type. For example, use halfvec_l2_ops for halfvec vectors.

Inner product

CREATE INDEX ON items USING ivfflat (embedding vector_ip_ops) WITH (lists = 100);

Cosine distance

CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);

Hamming distance

CREATE INDEX ON items USING ivfflat (embedding bit_hamming_ops) WITH (lists = 100);

Supported vector types:

TypeDescriptionLimit
vectorStandard float vectorsUp to 2,000 dimensions
halfvecHalf-precision vectorsUp to 4,000 dimensions
bitBinary vectorsUp to 64,000 dimensions

Query options

ParameterDefaultDescription
ivfflat.probes1Number of lists to search. Higher values improve recall but slow down queries. Setting probes equal to lists performs an exact search, but the query planner will not use the index.

Apply probes to a single query using SET LOCAL:

BEGIN;
SET LOCAL ivfflat.probes = 10;
SELECT ...
COMMIT;

Speed up index building

Two parameters control build speed:

ParameterDefaultDescription
maintenance_work_memCluster-dependentMaximum memory for index building. When the HNSW graph fits within this limit, building is faster. Adjust based on your cluster specifications — set a value that avoids memory exhaustion.
max_parallel_maintenance_workers2Number of parallel workers for index creation. To use more workers, first increase max_parallel_workers (default: 8).
SET max_parallel_maintenance_workers = 7; -- plus leader thread

If maintenance_work_mem is too low for your HNSW index, the database logs:

NOTICE: hnsw graph no longer fits into maintenance_work_mem after 100000 tuples
DETAIL: Building takes significantly more time.
HINT: Increase maintenance_work_mem to speed up building.

Creating an index after loading your initial data is faster than building on an empty table (for both index types). Index parameters also significantly affect build time.

Monitor index building progress

Query pg_stat_progress_create_index to track progress during index creation.

HNSW build phases:

  1. initializing

  2. loading tuples

IVFFlat build phases:

  1. initializing

  2. performing k-means

  3. assigning tuples

  4. loading tuples

-- HNSW progress
SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;

-- IVFFlat progress
SELECT phase, round(100.0 * tuples_done / nullif(tuples_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
The % column is updated only during the loading tuples phase.