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.
0-2 years experience
2-5 years experience
5-8 years experience
8+ years experience