{"slug": "what-to-check-before-tuning-a-qdrant-collection", "title": "What to Check Before Tuning a Qdrant Collection", "summary": "Qdrant, the vector database company, published a guide on what to check before tuning a Qdrant collection, emphasizing that retrieval goals must be defined first and that certain settings verify correctness rather than performance. The article details how to inspect vector indexing, payload indexes, and labeled query sets, and maps symptoms to specific tuning articles, including hybrid search, rerankers, candidate depth, and memory optimization.", "body_md": "Before you change a setting, decide what better retrieval means for your workload. The right document at rank one, more candidates for a reranker, lower latency, and a smaller memory footprint each favor different settings, so pick your goal first. If your labeled queries can’t detect the improvement you’re chasing, you won’t be able to tell whether a change helped.\n\nSome settings are there to verify correctness, not to tune performance. If a vector is unindexed, a sparse vector is missing the IDF modifier, or the BM25 average length is wrong, the results are invalid. Any benchmark or comparison you run after that will reflect a broken setup. This article shows you how to check each setting and what the correct state looks like.\n\n## The Retrieval Pipeline You Are Tuning\n\nEvery query first retrieves candidates, then ranks them. In dense-only search, one vector search does both. Hybrid search adds a sparse prefetch for exact terms, then fusion combines the dense and sparse candidate lists. A reranker, if present, scores the top candidates again.\n\n*The hybrid pipeline and the settings each stage owns. Dense-only search uses the dense prefetch path on its own, so limit and hnsw_ef are its only settings here.*\n\nIf you run dense-only search and exact keywords are missing from results, hybrid search is the first change to test. [Tuning hybrid search](https://qdrant.tech/articles/how-to-tune-hybrid-search/) covers the request shape, what the second prefetch costs, and how to check that fusion beats either prefetch on your labels.\n\nBefore you tune:\n\n- Check that vectors are indexed and that every field used in a filter has a payload index.\n[Collection details](https://qdrant.tech/documentation/manage-data/collections/#collection-info)and[payload indexing](https://qdrant.tech/documentation/manage-data/indexing/#payload-index)show what to inspect. - Build a labeled query set and choose a metric that matches the product experience. A labeled query pairs a real user query with the documents that should be returned.\n[Measuring retrieval relevance](https://qdrant.tech/documentation/improve-search/retrieval-relevance/)walks through the setup.\n\n## The Symptom Tells You Where to Start\n\nStart with the failure mode, not the config reference. The table maps each symptom to the first useful check and the article that covers it.\n\n| What You See | First Check | Read Next |\n|---|---|---|\n| You cannot separate a gain from noise | Build labeled queries, choose a metric, and calculate an interval | This article |\n| Relevant documents do not appear | Measure whether candidate depth is limiting recall |\n|\n\n[How to Tune Hybrid Search in Qdrant](https://qdrant.tech/articles/how-to-tune-hybrid-search/)[How to Tune Hybrid Search in Qdrant](https://qdrant.tech/articles/how-to-tune-hybrid-search/),[When Is a Reranker Worth It?](https://qdrant.tech/articles/when-a-reranker-is-worth-it/)[When Is a Reranker Worth It?](https://qdrant.tech/articles/when-a-reranker-is-worth-it/)[Candidate Depth: How Much Retrieval Is Enough?](https://qdrant.tech/articles/candidate-depth/)[When Your Collection Outgrows RAM](https://qdrant.tech/articles/when-your-collection-outgrows-ram/)## How to Read These Measurements\n\nThe procedure transfers: choose a metric that matches the product experience, compare settings on labeled queries, and validate the winner on fresh queries.\n\nQdrant’s API and algorithm mechanics carry across collections. The result of a parameter sweep depends on the embedding model, dataset, query mix, filters, index state, shard layout, and deployment. Use each result to choose a test on your own collection, then keep only the settings your labels support.\n\n## Silent Settings Can Break Quality\n\nCheck the stages you run before tuning anything else. Each prerequisite has a correct state for a given collection and can fail without an error. Fix them before you benchmark or compare settings, otherwise you are measuring a configuration error, not a trade-off.\n\n### Dense Search and Indexing\n\n** Vectors are indexed** Call\n\n`GET /collections/{collection_name}`\n\nand compare `indexed_vectors_count`\n\nwith `points_count`\n\n. In a dense-only collection, the counts should match once indexing is complete. In a hybrid collection, where each point has one dense and one sparse vector, `indexed_vectors_count`\n\nshould be twice `points_count`\n\n, because Qdrant counts each vector separately.If the indexed count is lower, indexing may still be running, may have stopped, or some segments may be smaller than the default `indexing_threshold`\n\nof 10,000 KB. See the [indexing optimizer documentation](https://qdrant.tech/documentation/ops-optimization/optimizer/#indexing-optimizer). Qdrant builds an HNSW graph only after a segment reaches `indexing_threshold`\n\n. Before then, it searches the segment without HNSW, so changing `hnsw_ef`\n\nhas no effect.\n\n** full_scan_threshold** Dense and sparse vectors have separate thresholds in different units, so a value copied between them lands nowhere near the intended size. The dense threshold counts kilobytes of vectors in a segment, 10,000 by default. It sends a search to an exact scan instead of the graph when the segment holds fewer vectors than that, or when a filter matches fewer points than that.\n\nThe sparse threshold counts vectors, 5,000 by default, and applies only when a filter is present.\n\n### Sparse Retrieval\n\nThese settings apply whether the collection has thousands of documents or billions.\n\n** Modifier.IDF** Use this modifier for sparse vectors from BM25 or miniCOIL. Both leave inverse document frequency (IDF) to Qdrant, which computes it per shard for each query term and weights the term by it. SPLADE already includes corpus-level term weighting, so applying the modifier would count rarity twice.\n\n** BM25 avg_len** Set\n\n`avg_len`\n\nto the average number of tokens in the field after BM25 [stems words and removes stopwords](https://qdrant.tech/documentation/search/text-search/full-text-search/#bm25-text-processing). BM25 uses this value to adjust for document length. Do not estimate it from raw word counts. In the five datasets tested here, the stemmed count was 15% to 43% lower. The correct values ranged from 35.3 to 151.4, compared with the default of 256. Measure it using the same stemmer and stopword settings as the collection.\n\n### Hybrid Search\n\nFusion placement matters on sharded collections. `score_threshold`\n\nis a risk at any scale when a request moves from single-vector retrieval to fusion.\n\n** Fusion placement** At the root of the query, fusion runs once, after every shard returns its candidates. Inside a\n\n`prefetch`\n\n, fusion runs on each shard. Each shard fuses only its own candidates, and the outer query ranks by those shard-local fused scores. The result changes with shard count and with how points are distributed, and no error tells you it happened. Nested fusion is deliberate when an outer stage rescores its output. On a single-shard collection, both placements produce the same ranking.** score_threshold** Use\n\n`score_threshold`\n\nonly when you have a measured minimum acceptance score for the stage that returns results. A threshold copied from dense-only search is unsafe in a root-level RRF or DBSF query. Qdrant compares it with the fused score, not the dense or sparse score. It can silently truncate the result list or return no results. Validate it on labeled queries, or leave it unset.### Filtered Search\n\nIndex every field you filter on. The cost of skipping one grows with collection size and query concurrency.\n\n** Payload indexes** A healthy collection has a payload index for every field used in its filters. Create these indexes before ingestion. If you add one later, Qdrant does not add the filter-aware HNSW edges automatically. You must\n\n[rebuild the HNSW index](https://qdrant.tech/documentation/manage-data/indexing/#rebuild-the-hnsw-index). Qdrant Cloud strict mode rejects queries that filter on unindexed fields. Even with the right indexes, strict filters can reduce recall.\n\n[What ACORN fixes, and what fixes ACORN](https://qdrant.tech/articles/filtered-vector-search-acorn/)measures this effect on one million points.\n\n## Change Things in Cost Order\n\nStart with a change that does not rebuild the collection or add a retrieval stage. Move to a higher-cost tier only when the lower-cost options do not address the symptom.\n\n| Tier | What | Applies To | Cost |\n|---|---|---|---|\n| No New Retrieval Work | Fusion method, RRF `k` , weights | Hybrid search | Reorders lists you already retrieved. No rebuild or extra retrieval stage |\n| Expanded Retrieval | `hnsw_ef` | Dense search | Increases search breadth and query time |\n| Expanded Retrieval | Prefetch `limit` | Any pipeline with a downstream stage | Retrieves more candidates, increasing query time |\n| Expanded Retrieval | `full_scan_threshold` | Dense search, especially filtered search | Uses exact scans for larger candidate pools, which can increase query time |\n| A New Stage | Sparse prefetch | Dense-only search | A second index, a second vector per point, and 0.6 to 1.5 ms of query time on one shard |\n| A New Stage | Reranker | Any pipeline | A model call per candidate |\n| Rebuild | Embedding model, `m` | Every collection | Re-indexing the collection. Changing the embedding model also means generating a new vector for every point |\n| Rebuild | Quantization | Collections limited by memory | Re-indexing, plus a compressed copy of every vector. Holding ranking quality then depends on rescoring |\n\nConsider a model-level rebuild only when it addresses a measured constraint, since a new embedding model means re-embedding every point. [How to choose an embedding model](https://qdrant.tech/articles/how-to-choose-an-embedding-model/) covers that decision. When memory is the constraint, a Matryoshka model’s [ mrl parameter](https://qdrant.tech/documentation/inference/matryoshka-models/) shortens the vector itself, which is a different trade from compressing it with quantization.\n\n## Choose a Metric Before You Tune\n\nChoose the metric before you compare settings, because the metric decides the winner. In our testing, `nDCG@10`\n\n, `MRR@10`\n\n, and `Recall@100`\n\neach name a different best setting, and `Recall@100`\n\ndisagrees with `nDCG@10`\n\non four of five datasets.\n\n** nDCG@k** rewards relevant results near the top, gives additional credit when labels are graded, and normalizes each query against a perfect ranking. Use it when rank order among several results matters.\n\n** MRR@k** is the mean of one over the rank of the first relevant result. It asks how fast you got to something good. Use it when a query has one right answer.\n\n** Recall@k** is the share of all relevant documents that made it into the top k. Use it when you measure a first stage that feeds something else. It is capped per query by the number of relevant documents: a query with 359 relevant documents cannot exceed 0.28 at\n\n`Recall@100`\n\n, because only 100 can fit. The average across queries can land higher, because queries with fewer relevant documents are not held to that cap. In our testing, one dataset averages 358.9 relevant documents per query, and its best `Recall@100`\n\nwas 0.3877. Count relevant documents per query before choosing k.## Make Sure Your Labels Can Detect a Gain\n\n[Retrieval relevance](https://qdrant.tech/documentation/improve-search/retrieval-relevance/) covers building a labeled set. Its size decides whether any retrieval tuning is visible to you at all.\n\nA labeled set is large enough when it can distinguish the improvement you care about from normal query-to-query variation. Size alone will not save an unrepresentative set. Pull queries across the mix your product sees, including its important query types and filters, and spot-check a sample of the labels yourself.\n\nEvery check below takes one score per query for each setting you are comparing. Use the Qdrant request your service already sends. The scoring is the same whether your pipeline runs dense-only search, hybrid fusion, or a reranker.\n\nScoring starts with the metric itself. `dcg`\n\nsums graded relevance with a discount that grows with rank. `ndcg_at_k`\n\nruns that sum on what came back, then divides it by the same sum over the best ordering the query’s labels allow.\n\n``` python\nimport math\n\ndef dcg(gains):\n    \"\"\"Relevance summed with a discount that grows with rank.\"\"\"\n    return sum(gain / math.log2(rank + 2) for rank, gain in enumerate(gains))\n\ndef ndcg_at_k(doc_ids, relevance, k=10):\n    \"\"\"One query's ranking against the best ranking its labels allow.\"\"\"\n    returned = [relevance.get(doc_id, 0) for doc_id in doc_ids[:k]]\n    ideal = sorted(relevance.values(), reverse=True)[:k]\n    return dcg(returned) / dcg(ideal) if any(ideal) else 0.0\n```\n\nThen run your labeled queries through both settings. You write `search`\n\n, which applies one setting to the request your service already sends and returns the points as the server ranked them. Add `with_payload=[\"doc_id\"]`\n\nto that request so every point carries the ID your labels use, or read `point.id`\n\nif your point IDs are already your document IDs. `score`\n\nturns each list into one number, and subtracting the two scores for each query gives the per-query gain.\n\n```\n# Relevance keyed by the document IDs your labels already use.\nqrels = {\"q1\": {\"doc-41\": 1, \"doc-77\": 2}}\n# Your labeled queries. Each value is what search sends to Qdrant: text or a vector.\nqueries = {\"q1\": [...]}\n# The one parameter under test, in whatever form your search applies it.\ncurrent_setting = {\"hnsw_ef\": 64}\ncandidate_setting = {\"hnsw_ef\": 256}\n\ndef search(query_id, query, setting):\n    \"\"\"You write this: your own Qdrant request, with setting applied.\n\n    Return the points in the order the server ranked them, each carrying doc_id.\n    \"\"\"\n    raise NotImplementedError\n\ndef score(queries, qrels, search, setting):\n    \"\"\"One nDCG@10 per query, for one setting.\"\"\"\n    return {\n        query_id: ndcg_at_k(\n            [point.payload[\"doc_id\"] for point in search(query_id, query, setting)],\n            qrels.get(query_id, {}),\n        )\n        for query_id, query in queries.items()\n    }\n\ncandidate = score(queries, qrels, search, candidate_setting)\ncurrent = score(queries, qrels, search, current_setting)\nper_query_gain = [candidate[q] - current[q] for q in sorted(queries)]\n```\n\nThe two calls must differ in exactly one setting. Filters, query shape, and candidate limits stay identical. For `MRR@10`\n\nand `Recall@100`\n\n, [pytrec_eval](https://github.com/cvangysel/pytrec_eval) computes both from the same `qrels`\n\n.\n\nResample the per-query gains with replacement to estimate how much the average gain would move if you had drawn a different set of queries. The resulting 95% interval shows the range consistent with that sampling variation. If the interval includes zero, your labels cannot establish a quality gain.\n\n``` python\nimport numpy as np\n\ndef interval(per_query_gain, resamples=1000, seed=42):\n    \"\"\"95% interval for the mean per-query gain of one setting over another.\"\"\"\n    gains = np.asarray(per_query_gain, dtype=float)\n    rng = np.random.default_rng(seed)\n    draws = rng.integers(0, len(gains), size=(resamples, len(gains)))\n    return np.percentile(gains[draws].mean(axis=1), [2.5, 97.5])\n```\n\nThe more labeled queries you evaluate, the more precise the measured gain. Across our datasets, the 95% interval typically extended this far above and below the `nDCG@10`\n\ngain:\n\n| Labeled Queries | Interval, Either Side of the Gain |\n|---|---|\n| 25 | 0.047 |\n| 50 | 0.035 |\n| 100 | 0.025 |\n| 200 | 0.018 |\n| 300 | 0.015 |\n\nThe label count you need depends primarily on effect size and query-to-query variation, not collection size alone.\n\nIn our measurements, [fusion settings](https://qdrant.tech/articles/how-to-tune-hybrid-search/) moved `nDCG@10`\n\nby 0.012 to 0.038, gains from tuning an already-working collection rather than rebuilding the retrieval pipeline.\n\nFifty labeled queries were enough for the larger gains: the 0.038 gain had an interval excluding zero in 93% of draws, while gains under 0.02 cleared that bar in 7% to 38%. Treat small movement as unresolved until you have the labels to measure it.\n\n## Check the Winner on Fresh Queries\n\nA setting selected and evaluated on the same queries will look better than it performs on fresh queries. Split the labeled queries in half: select the winner on one half, then measure its gain on the other. We repeated that split 200 times per dataset.\n\nThe selected setting usually transfers. Ranking all 30 settings again on the fresh half, our pick typically landed in the top four, and it fell behind the default in 0% to 6% of splits. The gain does shrink: it retained 67% to 95% of what selection reported, so report the number from the fresh queries.\n\nIf you compare separately rebuilt indexes, check top-10 agreement across two builds before you treat a small `nDCG@10`\n\ndifference as a tuning gain. In our clean rebuild test, query sampling moved `nDCG@10`\n\nmore than graph variation did.\n\n## Start with One Change\n\nRecord the current relevance metric and p95 latency for a representative query set. Choose one low-cost change from the symptom table, validate it on fresh queries, and keep it only if the gain survives. Once you have that baseline, [Candidate Depth: How Much Retrieval Is Enough?](https://qdrant.tech/articles/candidate-depth/) shows how to test whether retrieval depth is the constraint.", "url": "https://wpnews.pro/news/what-to-check-before-tuning-a-qdrant-collection", "canonical_source": "https://qdrant.tech/articles/before-tuning-a-qdrant-collection/", "published_at": "2026-08-19 21:00:00+00:00", "updated_at": "2026-08-24 04:13:50.659169+00:00", "lang": "en", "topics": ["ai-infrastructure", "ai-tools", "machine-learning"], "entities": ["Qdrant"], "alternates": {"html": "https://wpnews.pro/news/what-to-check-before-tuning-a-qdrant-collection", "markdown": "https://wpnews.pro/news/what-to-check-before-tuning-a-qdrant-collection.md", "text": "https://wpnews.pro/news/what-to-check-before-tuning-a-qdrant-collection.txt", "jsonld": "https://wpnews.pro/news/what-to-check-before-tuning-a-qdrant-collection.jsonld"}}