indexing_threshold decides when a segment gets an HNSW graph
indexing_threshold is an optimizer setting that controls the point count above which the optimizer builds an HNSW graph for a segment. Below the threshold, the segment is left unindexed and queries fall back to a brute-force scan over it. The reason is that HNSW is an approximation with overhead, and for small segments a linear scan is both faster and exact. Scanning a few thousand vectors is a handful of microseconds, while traversing a graph has a constant overhead per query that is not worth paying at that scale. So the threshold exists to make the small-collection case fast and exact, and to make the large-collection case scale. It is set globally on the collection, but it is applied per segment by the optimizer.
The mechanism is that the optimizer evaluates each segment's point count against the threshold. If the segment is below, no graph is built. If it is above, the graph is built. Because segments are created and merged over time, a collection can contain a mix of indexed and unindexed segments, and a query has to handle both - the coordinator runs HNSW on the indexed segments and brute force on the unindexed ones, then merges. There is a related but distinct setting, full_scan_threshold, which is a search-time setting: below that point count, the entire search uses brute force even if a graph exists, because the graph overhead is not worth it. The two thresholds are sometimes confused. indexing_threshold governs when a graph is built; full_scan_threshold governs when a graph is used. Both exist because the crossover points are different - building a graph has a one-time cost, while using it has a per-query cost.
indexing_threshold (optimizer): point count above which the optimizer builds an HNSW graph for a segment.
full_scan_threshold (search): point count below which search uses brute force regardless of whether a graph exists.
Small collections: stay below both thresholds, so no graph is ever built and every query is exact and fast.
Large collections: segments exceed the threshold, graphs are built, and queries use approximate search.
Mixed state: a collection can have both indexed and unindexed segments at the same time, especially during and after a bulk load.
The trade-off is exactness and simplicity for small collections against scalability for large ones. For a collection of a few thousand vectors, leaving indexing_threshold at its default means the collection is never indexed and every query is a full scan - which is both exact and fast, so there is nothing to tune. For a collection of millions, the threshold determines how quickly the optimizer indexes new data and how much unindexed data can accumulate. Raising it reduces optimizer churn during ingest but leaves more data unindexed, which increases query latency for any query that touches an unindexed segment. Lowering it makes queries more consistent but increases optimizer activity. The common mistake is treating indexing_threshold as a performance tuning knob for large collections - it is not, it is a build trigger. The performance knob is ef and m. The second mistake is confusing indexing_threshold with full_scan_threshold and being surprised that a small collection still uses brute force even though a graph was built. The third mistake is setting indexing_threshold to a very large value on a collection that serves queries and then wondering why latency is linear in collection size - every segment is unindexed. Version note: the defaults for both thresholds have changed across releases and the semantics have been reworked, so read the effective optimizer config at runtime rather than copying values from an older doc.
Version-dependent: the default values of indexing_threshold and full_scan_threshold have changed several times, and in some releases the thresholds are applied per-segment rather than per-collection, which changes how they interact with the optimizer. The relationship between the two has also been reworked. If you are tuning either threshold, inspect client.get_collection().config to see the effective values on your version, and re-check after upgrading.
You have a 2k-vector collection and you see that no HNSW graph exists. Explain whether this is a problem and what the queries are doing instead.
A teammate raises full_scan_threshold to 1M on a 500k-vector collection. Explain what happens to query latency and whether it is a good idea.
Your collection has 30M points and you notice some segments are unindexed even though indexing_threshold is 20k. Diagnose why and whether it matters for query latency.
You need exact results for a specific endpoint on a large collection. Explain how you would use full_scan_threshold and exact search to guarantee exactness and what the latency cost is.
Design a configuration where a collection of 10M points stays mostly unindexed because queries are always highly selective and index-based. Is this a valid design, and what would break it?
You must guarantee a stable p99 latency as a collection grows from 1M to 100M points over a year. How would you set the two thresholds and what would you monitor to know when to adjust them?
Derive the crossover point where building an HNSW graph becomes cheaper per query than a brute-force scan, as a function of dimension, distance metric, and query rate. How does that compare with Qdrant's default thresholds?
You are designing a system that must switch between exact and approximate search transparently based on query cost. Describe the cost model, the decision criteria, and how you would validate that users do not see a quality regression.