Plain vector search in Qdrant returns in a few milliseconds. Add a filter (only this tenant's documents, only lang = "en", only products in stock) and the same query takes seconds, times out, or returns fewer results than you asked for.
Filtered vector search is where most Qdrant performance problems start. The fix is nearly always payload indexes, created before you load the data.
Why filters are hard for vector search
An HNSW index is a graph over vectors. Search walks from node to node towards the query vector. A filter removes nodes from consideration:
- Filtering after the search (take the top 100, then drop non-matching ones) returns too few results when the filter is selective.
- Filtering during the search can break up the graph: if 99% of nodes are excluded, the walk gets stuck and misses good matches.
Qdrant handles this with a query planner and a filterable HNSW: for indexed payload fields, it adds extra graph links between points that share values, so the graph stays connected under filters. It picks between graph search and a direct payload-index lookup based on how many points the filter matches. All of that depends on payload indexes.
Without payload indexes
If the field you filter on isn't indexed, Qdrant has to check the condition point by point. On large collections that's close to a full scan, which is where the multi-second queries come from.
Create payload indexes, and do it first
from qdrant_client import QdrantClient, models
client = QdrantClient("http://localhost:6333")
client.create_collection(
"docs",
vectors_config=models.VectorParams(size=1024, distance=models.Distance.COSINE),
)
# Index every field you filter on BEFORE uploading points
client.create_payload_index("docs", "tenant_id",
field_schema=models.KeywordIndexParams(type="keyword", is_tenant=))
client.create_payload_index(, , field_schema=)
client.create_payload_index(, , field_schema=)
client.create_payload_index(, , field_schema=)
