Qdrant: HNSW Indexing, Payload Filtering, Quantization, and Vector Search Performance
- 4 hours ago
- 8 min read
Qdrant is an open-source vector database written in Rust, built around a single-stage architecture where approximate nearest neighbor search and payload filtering are executed inside the same index traversal rather than as separate pre- or post-filtering passes. It ships as a self-hosted binary (Apache 2.0), a Docker image, and a managed offering (Qdrant Cloud) with free, standard, premium, and hybrid-cloud tiers. The current stable line is the 1.19.x release series, which added TurboQuant memory tiers on top of the quantization framework introduced across the 1.15–1.18 releases. This article covers how Qdrant's HNSW implementation is configured and tuned, how payload filtering interacts with the vector index, the four quantization methods available and their compression/accuracy trade-offs, and the practical considerations — cost, benchmark claims, deployment model — that determine when Qdrant is the right choice versus alternatives such as pgvector, Milvus, or Weaviate.
········
HNSW INDEXING AND SEARCH PARAMETERS IN QDRANT.
How the graph is built and traversed, and which parameters control the accuracy/speed/memory trade-off.
Qdrant's default vector index is Hierarchical Navigable Small World (HNSW), a multi-layer proximity graph. Upper layers contain sparse long-range connections between distant nodes; lower layers are denser and connect nearby nodes. A query starts at the top layer, greedily moves toward the closest node, and descends layer by layer, refining the candidate set until it reaches the base layer, where the actual nearest neighbors are collected. This gives logarithmic-ish search complexity relative to a brute-force scan, at the cost of recall being approximate rather than exact.
Four parameters govern the trade-off. m sets the number of bidirectional edges created per node when the graph is built; higher values increase recall and memory footprint roughly linearly. ef_construct controls how many candidate neighbors are evaluated during graph construction — raising it improves the quality of the graph (and downstream recall) but increases indexing time and, indirectly, resource usage during bulk ingestion. ef is the equivalent parameter at query time (it defaults to ef_construct if unset); increasing it trades query latency for higher recall without touching the stored index. full_scan_threshold is a size cutoff, expressed in kilobytes, below which Qdrant abandons the HNSW graph and performs a brute-force scan, since for small segments a linear scan can outperform graph traversal overhead.
Since version 1.16, Qdrant also extends HNSW itself for filtered search: when payload indexes exist on the fields used in a filter, Qdrant adds extra graph edges based on those indexed values, and applies the ACORN algorithm to explore neighbors-of-neighbors when the immediate neighbors of a node are excluded by the filter. This is what lets filtered queries stay on the HNSW graph instead of falling back to a full scan when a filter is highly selective — but it only activates if the payload index was created before or is rebuilt after the relevant vectors were ingested; indexing payload fields after the fact does not retroactively add those edges until the segment is re-optimized.
........
Parameter | Effect | Trade-off |
m | Edges per node in the graph | Higher = better recall, more memory (roughly linear) |
ef_construct | Candidates evaluated while building the graph | Higher = better graph quality, slower indexing |
ef | Candidates evaluated at query time | Higher = better recall, higher query latency |
full_scan_threshold | KB size below which HNSW is skipped for a brute-force scan | Avoids graph overhead on small segments |
........
Qdrant also supports GPU-accelerated index building for large collections, and a separate sparse vector index (since 1.7) built on inverted-index principles for keyword-style or SPLADE-style sparse embeddings, which supports dot-product similarity only and can be kept in pinned, cached, or cold memory tiers independently of the dense HNSW index.
········
PAYLOAD FILTERING AND FILTER-AWARE SEARCH.
What filter clauses and payload index types are available, and how filtering avoids degrading recall on selective queries.
Every point stored in Qdrant carries a JSON payload alongside its vector(s), and filters are expressed as combinations of must (AND), should (OR), and must_not (NOT) clauses, which can be nested arbitrarily. Condition types cover exact match, match-any (IN) and match-except (NOT IN) for keyword and integer fields, numeric and datetime range conditions, prefix and full-text (tokenized, with optional stemming and stopword removal) matching, phrase matching, geo bounding-box, radius and polygon conditions, array cardinality checks (values_count), null/empty checks, and nested-object filtering that applies a condition independently to each element of an array field rather than to the array as a whole.
Indexable payload types include keyword, integer, float, bool, geo, datetime, text, and uuid, each with its own index structure; a uuid field, for instance, is stored as a keyword-like index optimized for the fixed-length UUID format rather than arbitrary strings. Filtering works on unindexed fields too — Qdrant will still evaluate the condition — but without an index it cannot benefit from the filter-aware HNSW edges described above and can fall back to scanning candidates rather than pruning the graph efficiently, which becomes the dominant cost as collections grow. In strict mode, administrators can set unindexed_filtering_retrieve to false so that queries filtering on non-indexed fields are rejected outright rather than silently running slow.
The practical implication is that payload filtering in Qdrant is not a post-processing step applied after vector search narrows the candidate set, nor a pre-filter that restricts the search space before it starts (both of which are common designs in other systems and each have known failure modes: pre-filtering can force a full scan when the filter is very selective, post-filtering can return too few or zero results when the filter removes most of the top-k candidates). Instead, filtering conditions are woven into the same graph traversal as the vector search, which is why creating the relevant payload indexes before or immediately after bulk ingestion — rather than adding them later against an already-built HNSW graph — materially affects both filtered-query latency and recall stability under selective filters.
········
QUANTIZATION: SCALAR, BINARY, PRODUCT, AND TURBOQUANT.
How each compression method trades memory and speed against recall, and which workloads each one fits.
Qdrant offers four quantization mechanisms that compress stored vectors to reduce memory footprint and, in most cases, accelerate distance computation through SIMD-friendly integer or bit operations. All of them are asymmetric by default in Qdrant's implementation: the stored vectors are compressed, but the query vector is kept at full precision, and results can optionally be rescored against the original uncompressed vectors after an initial pass over the compressed index (oversampling controls how many extra candidates are pre-selected for this rescoring step — an oversampling factor of 2.4 with a limit of 100, for example, pre-selects 240 candidates before the final scoring pass).
Scalar quantization (available since 1.1) converts 32-bit floats to 8-bit integers, giving a fixed 4x compression ratio. Qdrant's own documentation states the resulting error is typically under 1% of recall, and a quantile parameter (default 0.99) can exclude extreme outlier values from the calibration range to tighten the quantization for the bulk of the distribution. Product quantization (since 1.2) splits each vector into sub-vectors and quantizes each chunk independently via k-means clustering, reaching compression ratios up to 64x — the highest of the four methods — but it is not SIMD-friendly, so query speed is slower than scalar or binary quantization despite the larger memory savings; Qdrant positions it for cases where memory cost dominates and query latency is secondary. Binary quantization (since 1.5) reduces each vector component to a single bit, yielding up to 32x compression and, per Qdrant's published testing, up to a 40x query speedup versus unquantized vectors on suitable data; it performs best on high-dimensional, roughly centered embedding distributions, and Qdrant's own tests report 0.98 recall@100 with 4x oversampling on OpenAI's 1536-dimension text-embedding-ada-002 vectors, and 0.98 recall@50 with 2x oversampling on Cohere's 4096-dimension embed-english-v2.0 — figures that are vendor-published and specific to those embedding models, not general guarantees across arbitrary embeddings.
TurboQuant (introduced in 1.18, with additional memory-tier controls in 1.19) is the newest method: it applies a fast random rotation to vectors before compression to redistribute the data more evenly, then quantizes at a selectable bit depth — 4-bit (8x compression, the default), 2-bit (16x), 1.5-bit (24x), or 1-bit (32x). It fully supports SIMD acceleration for cosine, dot-product, and Euclidean distance; Manhattan distance requires full vector reconstruction and is markedly slower under TurboQuant as a result. Across all four methods, storage can additionally be split across memory tiers — original vectors cached in RAM with quantized vectors pinned for fastest access, original vectors moved to cold/disk storage with only the quantized copy pinned, or everything kept cold and dependent on SSD/NVMe throughput — which is a separate lever from the quantization method itself and lets teams tune the RAM/latency trade-off independently of compression ratio.
........
Method | Compression | Mechanism | Best fit |
Scalar | 4x (fixed) | float32 → int8 | General-purpose, low accuracy cost |
TurboQuant | 8x / 16x / 24x / 32x (selectable) | Random rotation + bit-depth quantization, SIMD-accelerated | Tunable speed/memory ratio, newest method |
Binary | Up to 32x | 1 bit per component | High-dimensional, centered embeddings; max query speed |
Product | Up to 64x | Sub-vector k-means clustering | Memory minimization over speed (not SIMD-friendly) |
........
Qdrant recommends rescoring (re-evaluating top candidates against original full-precision vectors) as the default corrective step for binary quantization and for TurboQuant at 1-, 1.5-, and 2-bit depths, since the accuracy loss at those compression levels is large enough that raw quantized scores alone are unreliable for final ranking.
········
DEPLOYMENT, COST, AND WHERE QDRANT FITS.
Qdrant Cloud's free tier provides a shared 0.5 vCPU, 1GB RAM, 4GB disk cluster at no cost, which independent pricing trackers estimate holds around 250,000 uncompressed vectors or roughly 7-8 million vectors once binary quantization is applied — these are third-party estimates, not figures published directly by Qdrant, and actual capacity depends heavily on vector dimensionality and payload size. Paid managed clusters bill hourly for allocated resources rather than per query or per write, with third-party cost trackers placing typical production clusters (2GB to 16GB RAM) in roughly the $30–$400/month range before enterprise SLA add-ons; Premium tier pricing (99.9% uptime SLA, SSO, private networking, SOC 2 Type II) is quote-based. Because the engine itself is Apache 2.0 licensed, self-hosting on commodity infrastructure remains a fixed-cost alternative to the managed service at any scale — commonly cited as the more economical path once a workload consistently needs more than roughly 100GB-class managed clusters, though the crossover point depends on operational overhead a team is willing to absorb versus paying for managed backups, upgrades, and support.
On raw performance, vendor and third-party benchmarks disagree by workload and by who is running them, so figures should be read with the source in mind. A benchmark published by Timescale (maker of the competing pgvectorscale extension) in 2025, testing 50 million 768-dimension Cohere embeddings, reported pgvectorscale reaching 11.4x higher throughput than Qdrant at 99% recall (471.57 vs 41.47 QPS) while Qdrant showed lower tail latency (39% better p95, 48% better p99) and a substantially faster index build (about 3.3 hours versus roughly 11.1 hours for pgvectorscale) — this is a competitor-published result and its tuning methodology has not been independently reproduced or confirmed by Qdrant, so it should be treated as one vendor's benchmark rather than a neutral comparison. Qdrant's own published benchmarks, run on its site, report favorable results against Milvus, Weaviate, Elasticsearch, and pgvector on its chosen datasets and hardware, which carries the same caveat in the opposite direction. In practice, relative performance between Qdrant, Milvus, Weaviate, and pgvector-based setups depends heavily on dataset size, vector dimensionality, filter selectivity, recall target, and how carefully each system's parameters (HNSW m/ef, quantization, index type) were tuned for the comparison — generic published numbers rarely transfer directly to a specific production workload.
Choose Qdrant when the workload needs vector search combined with selective, high-cardinality payload filters evaluated in the same query — its filter-aware HNSW extension is built specifically for that pattern — and when a team wants either a lightweight Apache-2.0 self-hosted deployment or a managed cloud without adopting a full database platform; choose an alternative (pgvector if the data already lives in Postgres and query patterns are simpler, Milvus or Elasticsearch if the deployment needs to scale past single-node RAM by design, or a fully managed platform if operational simplicity outweighs the value of open-source control) when those specific conditions dominate over Qdrant's filtering and quantization advantages.
········
FOLLOW US FOR MORE.
·····
DATA STUDIOS
·····




