When you add a WHERE clause to a nearest neighbor query, the filtering strategy you choose has a direct impact on recall and performance. This page describes the available strategies and when to use each one.
Choose a filtering strategy
| Condition | Strategy |
|---|---|
| Matches a low percentage of rows | Exact index on the filter column |
| Matches a high percentage of rows | Approximate index (HNSW or IVFFlat) |
Exact indexes
Create an exact index on the filter column to get fast, precise nearest neighbor results. Supported index types:
B-tree (default)
Hash
GiST
SP-GiST
GIN
BRIN
For queries involving multiple columns, create a multicolumn index.
Exact indexes work best when the filter condition matches a low percentage of rows. For high-selectivity filters, use an approximate index instead.
Approximate indexes
With an approximate index (Hierarchical Navigable Small World (HNSW) or IVFFlat), filtering is applied *after* the index scan — not during it. The index returns its top candidates first, and only then are those candidates filtered by your WHERE clause.
For example: if your condition matches 10% of rows and you use an HNSW index with the default hnsw.ef_search of 40, only about 4 rows will match on average. If that is not enough, increase hnsw.ef_search.
For better recall, use iterative index scans, which continue scanning the index until enough matching rows are found.
Iterative index scans
Iterative index scans (available from version 0.8.0) automatically scan more of the index until enough filtered results are found, or until the limits set by hnsw.max_scan_tuples or ivfflat.max_probes are reached.
Check the extension version
Run the following query to check the installed version:
SELECT * FROM pg_extension WHERE extname = 'vector';The extversion field shows the current version. Sample output:
oid | extname | extowner | extnamespace | extrelocatable | extversion | extconfig | extcondition
-------+---------+----------+--------------+----------------+------------+-----------+--------------
17941 | vector | 17865 | 2200 | t | 0.8.0 | |
(1 row)If the version is earlier than 0.8.0, upgrade the extension:
ALTER EXTENSION vector UPDATE TO '0.8.0';Sorting modes
Iterative index scans support two sorting modes:
`strict_order`: results are sorted precisely by distance.
`relaxed_order`: results may be slightly out of distance-based order, but recall is better. Use a materialized CTE to enforce strict ordering when needed.
Enable iterative index scans
Strict ordering
-- HNSW index
SET hnsw.iterative_scan = strict_order;
-- IVFFlat index
SET ivfflat.iterative_scan = strict_order;Relaxed ordering
-- HNSW index
SET hnsw.iterative_scan = relaxed_order;
-- IVFFlat index
SET ivfflat.iterative_scan = relaxed_order;Get strict ordering in relaxed mode
In relaxed_order mode, use a materialized CTE to sort the final results precisely by distance:
WITH relaxed_results AS MATERIALIZED (
SELECT id, embedding <-> '[1,2,3]' AS distance FROM items WHERE id = 1 ORDER BY distance LIMIT 5
) SELECT * FROM relaxed_results ORDER BY distance;Filter by distance
For queries that filter by distance, use a materialized CTE and place the distance filter *outside* the CTE. Put all other filters *inside* the CTE:
WITH nearest_results AS MATERIALIZED (
SELECT id, embedding <-> '[1,2,3]' AS distance FROM items ORDER BY distance LIMIT 5
) SELECT * FROM nearest_results WHERE distance < 5 ORDER BY distance;This placement gives the best performance due to how the PostgreSQL executor processes the query.
Iterative index scan parameters
Scanning a large portion of an approximate index is costly. Use the following parameters to control when the scan stops:
| Index type | Parameter | Default | Description |
|---|---|---|---|
| HNSW | hnsw.max_scan_tuples | 20000 | Maximum number of tuples scanned during a query. |
| HNSW | hnsw.scan_mem_multiplier | 1 | Maximum memory multiplier during HNSW candidate scanning. Works with hnsw.max_scan_tuples to cap memory usage. If increasing hnsw.max_scan_tuples does not improve recall, increase this value. |
| IVFFlat | ivfflat.max_probes | — | Maximum number of probes during a query. If this value is less than ivfflat.probes, ivfflat.probes is used. |