Solve filter-aware traversal; reject post-filtering and pre-filtering with brute force
The core problem is: given a filter that matches a subset of points, find the top-k nearest neighbors among the matching points, without scanning the entire collection and without missing relevant results. The naive approaches are two. The first is post-filtering: run the ANN search without the filter, retrieve the top-k, then discard the ones that do not match. This fails because the top-k may contain few or no matching points, especially for selective filters, so the result set is incomplete or empty. It also wastes work on non-matching candidates. The second is pre-filtering with brute force: find all the points that match the filter (via the payload index), then compute the exact distances to all of them, then take the top-k. This is correct but expensive: if the filter matches a large fraction of the collection, the brute-force scan is a full scan. The correct solution is a filter-aware traversal: modify the HNSW search so that the filter is evaluated during the graph traversal, and the traversal continues until it has found enough matching points to return the top-k. This is what Qdrant does, and it is the core problem that the design must solve.
The mechanism that makes filter-aware traversal work is that the traversal maintains a candidate list and a result list, and the result list is populated only with points that pass the filter. The traversal continues until the candidate list is exhausted (no unexplored neighbor is closer than the worst result) or the result list has k matching points. If the filter is selective, the traversal has to explore more of the graph to find enough matching points, so the effective search breadth increases. The payload index helps by identifying the matching set and by pruning segments that cannot match. The naive approaches fail because they do not integrate the filter into the traversal: post-filtering decouples the filter from the search, so it can miss matching points that are not in the top-k; pre-filtering with brute force decouples the search from the graph, so it loses the sub-linear property. The filter-aware traversal is the only approach that preserves both correctness and sub-linear search. The design must also handle the case where the filter matches a very large fraction of the collection: in that case, the filter-aware traversal behaves like an unfiltered search, and the payload index may not help. And the case where the filter matches a very small fraction: in that case, the traversal must explore a large portion of the graph, and the cost can approach a full scan, which is why the planner may choose a pre-filtering strategy in that case.
Core problem: top-k nearest neighbors among the points that match a filter.
Naive post-filtering: retrieve top-k, then discard non-matching; fails for selective filters.
Naive pre-filtering with brute force: find matching points, then exact scan; fails for non-selective filters.
Filter-aware traversal: evaluate the filter during the graph traversal, continue until k matches.
Payload index: identifies the matching set, prunes segments, guides the traversal.
Selectivity: selective filters force more exploration; non-selective filters add overhead without reducing candidates.
Planner choice: for very selective filters, pre-filtering may be cheaper; for non-selective, filter-aware traversal.
Correctness: the filter-aware traversal returns exactly the top-k among the matching points.
The trade-off is between correctness, latency, and the cost of the index. The filter-aware traversal preserves correctness but can be slow for selective filters. The payload index enables pruning but costs memory and slows writes. The planner's choice between filter-aware traversal and pre-filtering is a cost-based decision that depends on the selectivity and the index availability. The common mistakes are: (1) implementing post-filtering, which is wrong for selective filters; (2) implementing pre-filtering with brute force, which is correct but slow; (3) not using a payload index, so the traversal cannot prune; (4) not handling the very-selective case, where the traversal cost approaches a full scan; (5) not measuring the filter-aware traversal under different selectivities. Version note: the filter-aware traversal and the planner's behavior have evolved across Qdrant releases. The exact implementation and the performance characteristics differ. If you are designing a system from scratch, the filter-aware traversal is the core idea, but the details of the index and the planner are where the engineering effort goes.
Version-dependent: the filter-aware traversal and the planner's behavior have evolved across Qdrant releases. The exact implementation and the performance characteristics differ. If you are designing a system from scratch, the filter-aware traversal is the core idea, but the details of the index and the planner are where the engineering effort goes.
You are designing filtered search and your first idea is post-filtering. Explain why it fails.
A teammate suggests brute-force pre-filtering. Explain when that is correct but slow.
You implement filter-aware traversal and a selective filter is slow. Diagnose the cause and propose a fix.
Your filter matches 90 percent of the collection. Explain whether the filter-aware traversal helps or hurts.
Design the cost model for a planner that chooses between filter-aware traversal and pre-filtering, and describe the statistics it needs.
You need to support a filter that combines a keyword match and a range condition. Describe how the traversal and the indexes interact.
Derive the expected cost of a filter-aware traversal as a function of filter selectivity, graph degree, and ef. Where does it become cheaper to pre-filter?
You are designing the filtered search for a new vector database from scratch. Describe the data structures, the traversal, and the planner.