Filtering

Updated at:

Nearest neighbor queries without filtering scan the entire vector index, which is slow and returns irrelevant results when only a subset of rows applies. Adding a WHERE clause and indexing the filter column narrows the search scope and improves query performance.

Exact indexes

Create an index on the filter column to enable fast, exact nearest neighbor search. The following index types are supported:

  • B-tree (default)

  • Hash

  • GiST

  • SP-GiST

  • GIN

  • BRIN

For queries that filter on multiple columns, create a multicolumn index.

Exact indexes are suitable for conditions that match a low percentage of rows.

Approximate indexes

With approximate indexes, filtering is applied after the index scan. If the filter matches 10% of rows and an HNSW index is used with the default hnsw.ef_search of 40, only four rows match on average. To get more results, increase the hnsw.ef_search value.

Use approximate indexes when a large percentage of rows match the filter condition. For a very low match percentage, exact indexes perform better.

Iterative index scans

Starting from version 0.8.0, iterative index scans automatically scan more of the index until enough matching results are found or until the limits set by hnsw.max_scan_tuples or ivfflat.max_probes are reached.

Check your extension version

SELECT * FROM pg_extension WHERE extname = 'vector';

Check the extversion field in the result. Example 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';

Enable iterative index scans

Two sort ordering modes are available:

  • strict_order: sorts results precisely by distance.

  • relaxed_order: allows results to be slightly out of distance order for better recall. Use a materialized CTE to re-sort the results strictly after retrieval.

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;

To get strictly sorted results in relaxed_order mode, wrap the query in a materialized CTE and re-sort outside it:

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;

For queries that also filter by distance, place the distance filter outside the CTE and other filters inside for best performance:

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;

Iterative index scan parameters

Scanning a larger portion of an approximate index is costly. Use the following parameters to control when a scan stops.

Index typeParameterDefaultDescription
HNSWhnsw.max_scan_tuples20,000Maximum number of tuples to scan during a query.
HNSWhnsw.scan_mem_multiplier1Maximum memory multiplier allowed during the execution of the HNSW algorithm. Works with hnsw.max_scan_tuples to determine the maximum memory allowed during candidate scanning. If increasing hnsw.max_scan_tuples does not improve recall, increase this value instead.
IVFFlativfflat.max_probes—Maximum number of probes during a query. If this value is less than ivfflat.probes, ivfflat.probes is used.