NEWVectors or files. Pick a path.Start →
    Search & Discovery
    8 min read
    Updated 2026-10-19

    Why Does My Vector Search Return Fewer Results When I Add a Filter?

    Most vector indexes find candidates first and filter afterwards, so a narrow filter leaves fewer results than you asked for, or a full page that misses the true nearest matches. How to check filtered recall on your own data, what to change in pgvector, Qdrant and Weaviate, and our own before and after numbers on 1 million vectors.

    Vector Search
    Filtering
    Recall
    pgvector
    Qdrant
    Weaviate
    Debugging

    Why does my vector search return fewer results when I add a filter?



    Most vector indexes find candidates first and apply the filter afterwards. The index hands back its usual few dozen nearest neighbours, the filter throws away the ones that do not match, and whatever is left is your result. A filter that matches 10 percent of your data keeps about 10 percent of those candidates, so a request for 10 results can come back with 4, or with 10 that are not the true nearest matches. pgvector's documentation gives the arithmetic: with HNSW and the default hnsw.ef_search of 40, a condition that matches 10 percent of rows returns about 4 rows on average (pgvector README).

    The fix is to make the search filter-aware: scan more of the index until enough matches turn up, walk the graph through matching points only, or skip the index and compare against every matching vector when the filter is narrow enough. Which one you can use depends on your database. The sections below cover the cause, a check you can run on your own data and the setting to change in pgvector, Qdrant and Weaviate.

    Two symptoms, one cause



    A filtered search fails in one of two ways, and the second is harder to spot:

    1. Fewer results than you asked for. You set a limit of 10 and get 3. Easy to notice, and common in pgvector with post-filtering. 2. A full page of the wrong results. You get 10 results, all matching the filter, but some of the true 10 nearest matches are missing. Systems that keep searching until they have k matches, without reading the parts of the index where the true neighbours sit, produce this one. Nothing in the response says the list is incomplete.

    Both come from the same place. An approximate index only reads part of your data on each query: an HNSW search visits a bounded candidate list, and an IVF search reads the few clusters nearest the query (nprobe or probes). When a filter is added on top, the candidates the index happened to read are the only ones the filter can keep. If the matching vectors sit elsewhere in the index, the search never sees them.

    The narrower the filter, the worse it gets. A filter that matches half your data loses a little recall. A filter that matches 0.1 percent of it, such as one customer's footage in a shared index or one product category, can lose most of it.

    How to check whether your filtered search is missing results



    Compare the index's answer with an exact answer on the same filter. Take a sample of real queries and filters, run each through your normal search, then compute the true top k by scoring every vector that matches the filter. The share of the true top k that your search returned is filtered recall.
    import numpy as np
    
    def exact_filtered_top_k(vectors, mask, query, k=10):
        """True top k among the rows where mask is True. Assumes normalized vectors (cosine)."""
        rows = np.flatnonzero(mask)
        scores = vectors[rows] @ query
        return rows[np.argsort(-scores)[:k]].tolist()
    
    def filtered_recall(returned_ids, vectors, mask, query, k=10):
        truth = set(exact_filtered_top_k(vectors, mask, query, k))
        return len(truth & set(returned_ids[:k])) / k
    Group the results by how selective the filter is, for example under 1 percent, 1 to 10 percent, and above 10 percent of rows. A search that scores 0.95 unfiltered and 0.4 under a narrow filter has this problem. Count how often the result list is shorter than the limit too, since that is the first symptom above.

    How to fix it in pgvector, Qdrant and Weaviate



    DatabaseWhat it does by defaultWhat to change
    pgvectorFilters after the index scan. Results are capped by hnsw.ef_search (40 by default) or ivfflat.probes, so a filtered query can return fewer rows (pgvector README)From 0.8.0, set hnsw.iterative_scan (or ivfflat.iterative_scan) to strict_order or relaxed_order, which keeps scanning until enough rows match or hnsw.max_scan_tuples (20,000 by default) is reached. Index the filter column so Postgres can choose an exact scan, and use partial indexes or partitioning when you filter by a few distinct values (pgvector README)
    QdrantFilters during the HNSW search. Uses the payload index to estimate how many points match and switches to a full scan below full_scan_threshold (Qdrant indexing)Create payload indexes on filtered fields before loading data, because the extra filter-aware graph edges are only built after the payload index exists; an index added later needs the HNSW index rebuilt. For several strict filters combined, which can still leave the graph disconnected, Qdrant offers the ACORN search algorithm from v1.16.0 (Qdrant indexing)
    WeaviateBuilds the filter first and searches with it, using the ACORN filter strategy by default from v1.34 (Weaviate filtering)Set the flat-search cutoff so very restrictive filters switch to a brute-force search over the matching objects. Weaviate notes that as a filter gets more restrictive, an HNSW traversal approaches the cost of a brute-force search anyway (Weaviate filtering)


    Three things help whatever database you use:

  1. Index the fields you filter on. Without a field index the database cannot tell how many rows a filter admits, so it cannot pick an exact scan for the narrow ones.
  2. Split data you always filter one way. If every query filters by tenant, a separate index or partition per tenant turns the filter into routing.
  3. Measure by selectivity. One recall number averaged over all your queries hides the narrow filters, where the loss is.


  4. What we measured when we fixed this in our own index



    Mixpeek's vector store, MVS, searches by reading the partitions nearest the query, an IVF-style layout. Until October 2026 a filtered search read the usual nearest 16 partitions and stopped at the first 10 matches. We measured it against an exact filtered scan on a test set of 1 million 768-dimension vectors in 73 partitions, 100 queries per filter, in-process on one machine:

    Filter admitsExact top 10 beforeExact top 10 afterMedian latency beforeMedian latency after
    0.1% of vectors38 of 100 queries100 of 1002.1 ms1.4 ms
    1%69 of 100100 of 1002.4 ms3.9 ms
    50%86 of 100100 of 10034.0 ms39.7 ms


    At 0.1 percent the old search returned the true 10 nearest matches on fewer than 4 queries in 10, and every one of those responses still looked complete. Even the broad 50 percent filter missed on 14 queries in 100.

    The fix has two parts. A filter that admits up to 250,000 vectors is answered by scoring every matching vector exactly, when those partitions are loaded with their full-precision vectors. A broader filter widens the search in proportion to how selective it is, up to 8 times the normal number of partitions. Partitions that are not loaded keep the widened search. In this test the narrowest filter got faster: scoring its 1,000 matching vectors exactly took less time than reading 16 partitions.

    These figures come from one harness on one test set, run in-process on one machine. Read them as the size of the effect; production latency depends on your data, hardware and index settings. The check in the section above is how to find your own numbers.

    Why a short or incomplete result list matters more for agents



    A person scrolling search results can notice that a known item is missing. An agent cannot. It takes the result list as the facts, and a filtered search that silently dropped the true best match gives it a confident answer built on the wrong evidence. Mixpeek's position is that agents acting on the world need verifiable facts derived from real assets, and a result list with a hidden gap fails that test. For an agent's retrieval, test filtered recall at the selectivities its tools actually use, and treat a list shorter than the limit as a reason to widen the search.

    Where Mixpeek fits



    Mixpeek reads your files where they already live, in S3, GCS or any bucket, and turns what's in them into data your software can search, classify and moderate. Filters on a Mixpeek retriever run inside the vector search described above. See filters in the docs, the technique explainer on pre-filtering, post-filtering and in-place filtering, MVS for the vector store itself, and the best vector databases to compare options.

    Frequently Asked Questions



    Why does pgvector return fewer rows than my LIMIT?



    With an HNSW or IVFFlat index, pgvector applies the WHERE clause after the index scan, so the result is capped by hnsw.ef_search (40 by default) or ivfflat.probes and then filtered. A condition matching 10 percent of rows returns about 4 rows with the defaults. Turn on iterative index scans in pgvector 0.8.0 or later, raise hnsw.ef_search, or index the filter column so Postgres can choose an exact scan (pgvector README).

    Is pre-filtering or post-filtering better for vector search?



    Filtering before or during the search keeps recall when the filter is narrow. Post-filtering is simpler and works when the filter matches most of the data, since few candidates get thrown away. Qdrant and Weaviate both filter during the search and switch to an exact scan when a filter admits few enough vectors (Qdrant indexing; Weaviate filtering).

    What is filtered recall?



    The share of the true top k results among the vectors that match the filter that a search actually returns. Compute the truth by scoring every matching vector exactly, as in the snippet above, and report it separately for narrow and broad filters.

    Does increasing ef_search or nprobe fix filtered search?



    It helps, because the search reads more of the index before filtering, and it costs latency on every query. A narrow filter can need a very large value to recover recall. An exact scan over the matching vectors is usually cheaper for those.

    How many results can a narrow filter lose?



    In our measurement on 1 million vectors, a search that read a fixed 16 of 73 partitions returned the exact top 10 on 38 of 100 queries when the filter admitted 0.1 percent of vectors, and on 69 of 100 at 1 percent. Your own numbers depend on the data and the index settings; measure them with the check above.

    Sources



  5. pgvector README: filtering and iterative index scans
  6. Qdrant documentation: indexing, filterable HNSW and ACORN
  7. Weaviate documentation: filtering
  8. MVS filtered search measurement: in-process harness on a 1M x 768 test set with 73 partitions and a base of 16 probed partitions, 100 queries per selectivity, October 2026 (Mixpeek engineering).
  9. Managed Mixpeek

    Put multimodal search to work

    Connect a bucket and Mixpeek runs the whole multimodal search pipeline for you: extraction, indexing, and search over your own objects. No models to wire up, nothing to host.

    Start with Managed
    MVS · bring your own

    Already have vectors?

    Keep your embeddings on your own cloud and run dense, sparse, and BM25 search directly on object storage. From $25/mo.

    Start with MVS

    Run this on your own data

    Point Mixpeek at the storage you already have and search your video, images, audio, and documents the way this guide describes. Build starts at $25/mo for up to 1M vectors.

    Search your own archiveRead Docs

    Related guides

    Search & Discovery

    Why Do My Search Results Get Worse as I Add More Data?

    Search that worked on ten thousand items and fails on ten million usually has one of five causes: a candidate budget that is a count rather than a fraction, near-duplicates filling the top ten, a coarse index trained on data that no longer looks like yours, a score floor calibrated on a smaller corpus, or a filter starving the candidate pool. This explains each mechanism, the single test that identifies it, and the order to run the tests in.

    Read guide →
    Search & Discovery

    Why Can't My Search Find Exact Part Numbers, Codes or Names?

    Semantic search compares meanings, and a part number, an error code or an unusual surname carries almost none, so the index returns records that look alike instead of the one that matches. Run a keyword (BM25) search beside the vector search, merge the two lists by rank, and use an exact filter when the identifier lives in its own field.

    Read guide →
    Search & Discovery

    How Do AI Agents Search Big Datasets by Navigating Clusters? (Hierarchical Cluster Search)

    Flat vector search returns top-k against one query vector, which breaks down when an agent does not know the right query, the corpus is huge and diverse, or the task is exploratory. Agentic hierarchical cluster search gives the agent a map instead: a cluster hierarchy (themes -> sub-clusters -> records) it navigates coarse-to-fine, scoring its goal against a few dozen centroids and drilling into the matching branch before running a precise retrieval at the leaf. When it beats flat ANN, the navigation loop, the cost math, the honest limits, and how to build it from clustering + composite clustering + a cluster-scoped retriever.

    Read guide →