Filtered queries and large payloads have different cost profiles than unfiltered search
Unfiltered nearest-neighbor search is the simplest possible workload: the engine traverses the graph, computes distances, and returns the top-k. Filtered search adds a predicate that must be evaluated for every candidate the traversal visits, and the cost of that predicate depends on the filter's selectivity, on whether the field is indexed, and on the index type. A selective filter on an unindexed field forces the traversal to explore a large portion of the graph to find enough matches, which can be orders of magnitude more expensive than unfiltered search. A filter on a low-cardinality field matches most points, so it adds overhead without reducing the candidate set. A filter on an indexed field can be resolved quickly via the index, but the planner may still choose a traversal strategy that is slower than expected. A benchmark that only measures unfiltered search will not see any of this, and the production system will fail to meet its SLOs when the first filtered query arrives.
Payload size is a second dimension that unfiltered benchmarks miss. Large payloads affect the cost of retrieving points, the size of the response, and the memory footprint of the collection. If the payload includes a long text field, a large JSON document, or a vector that is returned in the response, the serialization and network cost can dominate the search cost. A benchmark that uses tiny payloads and does not request the payload in the response will underestimate the production latency significantly. The mechanism is that the payload is stored separately from the vectors and is read on demand; large payloads mean more data to read, more memory to hold, and more data to serialize. For a filtered query, the payload is also read during filter evaluation, so large payloads amplify the filter cost. The practical implication is that the benchmark must use payloads that match the production size distribution, and it must measure the full end-to-end latency including payload retrieval and serialization.
Filter selectivity: a selective filter can be orders of magnitude more expensive than an unfiltered query.
Index coverage: filtered queries on unindexed fields scan; on indexed fields they use the index.
Cardinality: low-cardinality filters add overhead without reducing the candidate set.
Payload size: large payloads increase retrieval, serialization, and memory cost.
Payload retrieval: benchmarks must request the payload if production requests it.
Combined filters: must/should/must_not combinations have different costs than single conditions.
Distribution: the query distribution and filter distribution should match production, not be uniform.
The trade-off is between benchmark realism and benchmark cost. A realistic benchmark with many filter combinations and large payloads is expensive to construct and run, but it produces numbers that predict production. A synthetic benchmark with unfiltered queries and small payloads is cheap but produces optimistic numbers. The right approach is to benchmark a small number of representative query shapes, each with a realistic filter and payload size, and to vary the selectivity across a range to see how latency scales. The common mistake is to benchmark only the happy path (unfiltered, small payloads) and to be surprised when filtered queries are slow. The second mistake is to use a uniform filter distribution when production has a skewed distribution - the tail queries may be the ones that dominate the p99. The third mistake is to forget that filter evaluation and payload retrieval can be the bottleneck rather than the vector search, so tuning the vector parameters does not help. Version note: the filter-aware HNSW behavior, the planner's choice of strategy, and the cost of payload retrieval have changed across Qdrant releases. A benchmark run on one version may not predict another, so re-run the benchmark after upgrading.
Version-dependent: the filter-aware HNSW behavior, the planner's strategy selection, and the payload retrieval cost have changed across Qdrant releases. In some versions, ef is automatically expanded under a selective filter; in others, you must set it explicitly. The query_points API is qdrant-client 1.10+; older clients used search(). Benchmark on the version you plan to deploy.
You benchmark unfiltered queries and everything looks fast. Then production adds a filter and latency triples. Explain what the benchmark missed.
A teammate says payload size does not matter because the search is on vectors. Explain why that is wrong.
You need to benchmark a workload with a mix of selective and non-selective filters. Describe how you would construct the query set and what you would measure.
Your filtered benchmark shows high p99 but low p50. Explain why and what you would tune.
Design a load test that exercises the full query space (filtered and unfiltered, small and large payloads, with and without vector retrieval) and produces a trade-off curve for tuning.
You are preparing for a production launch with a filter-heavy workload. Describe the benchmark, the SLOs, and the tuning steps you would take to meet them.
You are designing a benchmarking framework that must predict production behavior for a workload with complex filters. Describe the workload generator, the metrics, and how you validate the predictions.
A production incident is caused by a filter that was not in the benchmark. Describe the process you would put in place to prevent this class of incident.