medium.com 31 min read Original Source

Managing Millions of High-Dimensional Vectors in Modern Vector Database

Introduction

Vector databases are specialized systems for storing and retrieving vector embeddings numerical representations of data (text, images, audio, video, etc.) in a high-dimensional space. These databases enable semantic queries instead of exact keyword matching, they find items similar to a given query vector by using distance metrics like cosine or Euclidean distance. This capability is crucial for AI applications such as semantic search, recommendation systems, and multimodal retrieval, where conceptually related items (e.g. _images with similar conten_t or _texts with similar meanin_g) need to be found quickly among millions or billions of candidates.

Challenges of Scale: Managing millions of high-dimensional vectors is computationally challenging. A single vector may have hundreds or thousands of dimensions, and an exhaustive comparison of a query against millions of vectors is intractable (linear or worse in complexity). Furthermore, high-dimensional spaces suffer from the “curse of dimensionality” distances become less informative as dimensionality grows, making indexing and search harder. To address this, vector databases rely on Approximate Nearest Neighbor (ANN) algorithms, which trade a small amount of accuracy for dramatic gains in speed. Modern vector databases achieve 2–10× better performance than traditional systems by combining hardware optimizations (SIMD instructions, GPUs, NVMe SSDs) with advanced ANN indexing structures like HNSW, IVF, and others.

Storage and Modality: Under the hood, all data modalities are first converted into vectors via machine learning models (e.g. transformer encoders for text). Once in vector form, different modalities can be managed uniformly by the database. Vectors are typically stored in a columnar fashion and often compressed or quantized to reduce memory footprint. For example, storing 100 million 1024-dimensional float vectors naïvely would require ~400 GB of RAM, so production systems use compression (like Product Quantization) or on-disk storage to handle billion-scale corpora cost-effectively. Some systems adopt a hybrid architecture that keeps lightweight indexes in memory but offloads bulk vector data to disk (SSD), striking a balance between query latency and storage cost. In distributed deployments, the dataset is partitioned across nodes to scale out horizontally, with careful design for fault tolerance and load balancing.

In the following sections, we explore key ANN indexing techniques used to store and search millions of vectors efficiently. We discuss how each method works, and compare their accuracy, speed, memory usage, scalability, and update performance. We then provide implementation insights for building and tuning vector search pipelines, including how popular systems like FAISS, ScaNN, and Milvus leverage these techniques.

Hierarchical Navigable Small World (HNSW) Graphs

HNSW is a graph-based index and one of the most widely used ANN algorithms for vector databases. It was introduced by Malkov & Yashunin (2016) as an improvement over earlier “navigable small world” (NSW) graphs. The core idea is to arrange vectors as nodes in a multi-layer small-world graph with edges linking near neighbors, and to use a hierarchical, skip-list-like structure to enable very efficient searching.

Index Structure: HNSW builds multiple layers of graphs (typically logarithmic in number of vectors). The top layer contains a very coarse graph (few nodes with long links spanning the space), and each subsequent lower layer adds more nodes and shorter links. Every vector is placed in the graph at a randomly assigned highest layer (height determined by an exponentially decaying probability) and in all layers below that. Each node in a layer connects to its M nearest neighbors in that layer (with some heuristics to diversify links). The result is a navigable small-world network: from any entry point, there are enough long-range connections to eventually reach the vicinity of the target, and enough local connections to fine-tune the search.

Search Algorithm: Querying HNSW is done by greedy graph traversal. The search begins at the top layer with a random (or designated) entry node and evaluates its neighbors to find the node closest to the query. It then moves to that neighbor and repeats, descending layers one by one. At each layer, the search keeps track of the closest found candidates (using a beam or “ef” parameter controlling breadth). By the time the bottom layer (which contains all vectors) is reached, the algorithm has homed in on a local region of the space and can collect the k nearest neighbors. This process is extremely fast empirically sub-logarithmic or roughly O(log N) per search and yields high recall (often 90–100% of true nearest neighbors) with proper tuning. HNSW’s greedy search can sometimes get stuck in a local minimum (missing the true nearest neighbor), but increasing the ef search parameter mitigates this at the cost of slightly more computations.

Performance and Trade-offs: HNSW provides excellent accuracy and speed, often outperforming other methods at high recall levels. It can achieve nearly exact results with suitable parameters while maintaining query latency in the few-millisecond range even for millions of points. The main drawback is memory usage HNSW stores multiple layers of links and a copy of all vectors in RAM. Memory overhead is determined by the graph degree parameter M: higher M (more neighbors per node) increases accuracy but also memory (each edge stores an neighbor ID and possibly distance). In practice, HNSW indices often use 1.2× to 2× the memory of the raw dataset, or more, depending on M. For example, one benchmark found an HNSW index consumed significantly more RAM than a compressed index (IVF/PQ) for the same data, in exchange for higher recall.

Another advantage of HNSW is dynamic updates. The graph can be built incrementally: new vectors are inserted by searching their position in the existing graph then connecting to neighbors at each layer. HNSWlib (the C++/Python library) supports inserts and deletes, making it suitable for constantly evolving datasets. Updates are somewhat slower than queries (due to maintaining neighbor lists), but still efficient enough for real-time use. Overall, HNSW is a top choice when high recall and real-time updates are required and sufficient memory is available. Many vector databases (e.g. HNSWlib, FAISS, Milvus, Weaviate, Pinecone) implement HNSW as a default indexing option because of its strong accuracy-speed trade-off.

Inverted File Index (IVF)

The Inverted File (IVF) index is a partitioning approach that accelerates search by clustering the vector space and restricting searches to a few relevant clusters. It’s sometimes called a “coarse quantizer” or “Voronoi partition” index. The idea is to use a clustering algorithm (typically k-means) to pre-partition the dataset into Nlist clusters, each represented by a centroid. Every vector is assigned to its nearest centroid and stored in an inverted list for that cluster. The collection of these lists is analogous to an inverted index in text search (hence the name).

Index Construction: Building an IVF index involves choosing the number of clusters (Nlist) and training the centroids. For example, FAISS’s IndexIVF trains Nlist centroids via k-means on a sample of the data. Each data vector is then quantized to the nearest centroid and appended to that centroid’s list. The index stores the centroids (in RAM for quick access) and the vectors in each list (or references to them).

Search Algorithm: At query time, a query vector is first matched to its closest centroids. Instead of searching all clusters, we only explore the top Nprobe clusters (a search parameter). The query is compared to each centroid (this is fast if Nlist is much smaller than total vectors), and the nearest centroids’ lists are retrieved. We then perform an exact search within those lists to find nearest neighbors. Because only a fraction of the data is scanned (those in the top Nprobe clusters), search is much faster than brute force. The trade-off is that if the true nearest neighbor was in a cluster outside those probed, it will be missed so Nprobe controls the recall/accuracy. In practice, IVF works well when data is naturally clustered or when moderate recall (e.g. 90–95%) is acceptable.

Performance and Trade-offs: The speed of IVF depends on Nlist and Nprobe. A larger number of clusters (Nlist) means each list is shorter (fewer vectors per cluster), so scanning one cluster is faster but it also means more centroids to compare for every query. Typically Nlist is set such that each inverted list has a few thousand vectors. Nprobe (how many lists to search) is then tuned to balance recall vs. latency. For example, Nprobe = 4 or 8 may be enough for high recall if the clustering is good. IVF alone (with raw vectors in lists) offers speed gains but not memory savings. However, it combines very well with compression (see PQ below) many systems use IVF to narrow the search, then Product Quantization to speed up distance computations. Memory-wise, an IVF index needs to store the cluster centroids (negligible memory) and the assignment of each vector (either storing the full vector in its list, or storing a compressed code). If storing full vectors, the memory is about the same as the original data size. If using compressed codes in lists, memory can be greatly reduced (again, see PQ).

IVF scales to very large datasets because the index can be stored on disk if needed: one can keep just centroids in RAM and keep inverted lists on SSD, reading the few relevant lists per query. This forms the basis of some billion-scale systems (e.g. Facebook’s DiskANN and IVFADCG, which use a disk-friendly IVF graph). Accuracy of IVF depends on vector distribution if nearest neighbors tend to fall in the same cluster as the query, recall will be high. If the clustering is poor (neighbors spread across clusters), IVF may miss results unless Nprobe is large (approaching a linear scan). In summary, IVF is “best for speed on clustered data but may lack precision” if used alone. It is often used in combination with other techniques, and is implemented in libraries like FAISS (IndexIVF* classes) and open databases like Milvus.

Product Quantization (PQ) and OPQ

Product Quantization is a vector compression technique that drastically reduces memory usage and accelerates distance computations by representing vectors approximately. PQ was introduced by Jégou et al. (2010) and has become a cornerstone of billion-scale vector search (it’s heavily used in FAISS, Spotify’s Annoy, and others). The principle is to split each high-dimensional vector into smaller subvectors and quantize each subvector independently using a pre-trained codebook. By concatenating the subvector codes, the full vector is compactly represented as a sequence of bytes or even bits.

How PQ Works: Suppose we have 128-dimensional vectors and we choose to split into M=8 subvectors of 16 dimensions each. We then train a codebook (via k-means) for each subvector space e.g. K=256 centroids per subspace (which can be stored in 16 KB per codebook). Each 16-D subvector is replaced by the index of its nearest centroid (which is an 8-bit code if 256 centroids). Thus the 128-D vector, originally 512 bytes (128 floats ×4 bytes) can be stored as just 8 bytes (8 subvector codes) a compression of 98%+. Indeed, PQ often yields 90–97% memory reduction in practice. The quantization introduces error, but if done carefully, distances computed on the compressed codes remain good approximations of true distances.

Distance Computation: A major benefit of PQ is fast distance calculations via lookup tables (LUTs). For a query vector, we can similarly split it into 8 subvectors. To compute distance between the query and any database vector’s PQ code, we don’t need to reconstruct the whole vector instead, for each subvector, we precompute the distance from the query subvector to each centroid in that subspace codebook (this yields M tables of size K). Then, given a database vector’s code (a sequence of M codebook IDs), we can sum up the M precomputed distances (one per subvector code) to get an approximate distance. This Asymmetric Distance Computation avoids heavy dot-products and uses only table lookups and additions. It’s extremely efficient and CPU-cache friendly, allowing scanning of thousands of vectors per millisecond per core. PQ codes can also be compared via SIMD instructions for even faster bulk processing.

Optimized PQ (OPQ): One limitation of basic PQ is that the subvector boundaries are fixed we split the vector into chunks of dimensions. If the data’s variance is not evenly distributed, some chunks might quantize poorly. OPQ (Optimized Product Quantization) addresses this by first applying a learned rotation or linear transformation to the vectors before splitting into subvectors. The idea is to rotate the axes such that each subvector captures an equal share of variance (decorrelating the dimensions). This minimizes quantization error in each subspace, yielding more accurate codes without changing code length. OPQ is an offline training step (solving for an optimal rotation via SVD or gradient methods) and is widely used to boost PQ accuracy for example, Facebook’s FAISS uses OPQ to improve recall by several percentage points at the same code size. In essence, OPQ provides a significant accuracy gain “for free,” i.e. without adding runtime cost or memory usage, making it a highly recommended enhancement to PQ.

Performance and Trade-offs: The strength of PQ/OPQ lies in memory efficiency and scalability. Storing vectors as compact codes enables billion-scale datasets to fit in memory or on disk caches. For example, compressing 1 billion 128-D vectors to 8 bytes each yields an index of only 8 GB trivial to handle on an SSD, whereas the raw data would be ~0.5 TB. PQ also improves search speed because of the reduced data size and use of table lookups. However, these benefits come at the cost of some accuracy: PQ is a lossy compression, so the approximate distances can lead to missing some true neighbors. With sufficiently fine quantization (more subvectors or more centroids), accuracy can approach that of exact search, but that increases code size and training time. There is a tunable trade-off between index size and recall: e.g. using 4 bytes per vector gives more error than using 16 bytes per vector. In practice, one might choose a code size that yields ~90% recall to save an order of magnitude in memory and accept rechecking a slightly larger candidate set to recover any missed results.

Building a PQ index has a training cost: running k-means on large data for each subvector (often using a sample of a few million points). Also, PQ by itself does not reduce the number of vectors you must compare it speeds up each comparison, but if you still compare to all vectors, that’s exhaustive. Therefore, PQ is almost always combined with a coarse index like IVF or HNSW. A popular configuration is IVF + PQ: first use IVF to select, say, 1% of the database as candidates, then use PQ codes to rank those candidates efficiently. This hybrid yields both a large reduction in comparisons and a fast method to evaluate each comparison. The result is a very fast ANN search with moderate accuracy. If higher accuracy is needed, one can increase Nprobe or codebook sizes, or do a re-ranking step with the original vectors on the top results.

In summary, PQ/OPQ offer optimal memory usage and good speed at the cost of some precision. They are ideal for edge devices or memory-limited environments and are crucial for billion-scale search. Libraries like FAISS provide flexible implementations (IndexPQ, IndexIVFPQ, IndexIVFOPQ) where you can configure M, K, and OPQ rotations. Many production systems (e.g. Meta’s Facebook AI, Spotify, NVIDIA for GPU acceleration) rely on PQ-based indexing for economically scaling to very large vector collections.

ScaNN (Scalable Nearest Neighbors)

ScaNN is a state-of-the-art ANN algorithm open-sourced by Google in 2020, designed to optimize end-to-end vector search for both speed and accuracy. It builds upon the ideas of clustering and quantization (like IVF and PQ) but introduces an improved quantization technique tailored for maximum inner product search (MIPS) and a multi-stage retrieval process. ScaNN’s architecture can be seen as a three-stage pipeline: partitioning, quantization, and re-ranking.

Partitioning: ScaNN first partitions the dataset into clusters (using k-means or other clustering), much like IVF. This stage narrows the search to a subset of vectors. At query time, ScaNN finds the nearest clusters to the query and only considers those clusters’ vectors, reducing the candidate pool dramatically.

Quantization (Anisotropic Quantization): Instead of using standard PQ which minimizes reconstruction error, ScaNN uses a learned quantization optimized for inner product similarity. In their ICML 2020 paper, the authors showed that minimizing Euclidean error is not necessarily optimal for preserving inner product rankings. They propose an anisotropic vector quantization that penalizes quantization error in the direction of the query more than orthogonal directions. Intuitively, for cosine or inner-product search, getting the length or direction wrong has different impacts on similarity ranking. By training the codebooks with a loss function that focuses on maintaining inner product order, ScaNN achieves higher recall for MIPS tasks at the same code budget. In practice, each vector in a cluster is represented by one or multiple quantization codes. This compression allows extremely fast scanning of candidates.

Re-ranking: After the partitioning and quantization stages, ScaNN obtains a set of top-K candidate vectors along with estimated distances. It then re-ranks the top candidates using the exact distance (on uncompressed vectors) to refine the results. This final stage ensures that the small loss from quantization doesn’t affect the ultimate result ordering. Re-ranking only a handful of vectors is inexpensive and significantly boosts accuracy to near-exact levels.

ScaNN’s workflow can be seen as IVF + a special PQ + a refine step, all optimized for modern hardware. It also includes tricks like vector residuals re-ranking and optional graph-based reordering, but the core is as above.

Performance: ScaNN is notable for its excellent speed-accuracy trade-off. On standard benchmarks (e.g. Glove-100 angular), ScaNN has been shown to achieve roughly 2× the query throughput at a given accuracy compared to other libraries. Google’s own tests demonstrated ScaNN reaching ~2x higher Recall@1 for a fixed latency budget versus HNSW or IVF-PQ implementations. It is particularly strong for inner product (maximum cosine similarity) searches due to its anisotropic quantization focus. In terms of memory, ScaNN’s use of compression means it can be much more memory-efficient than graph-based methods. The index stores cluster centers and quantization codebooks, plus compressed vectors; this is similar in footprint to an IVF+PQ index, which is typically far smaller than HNSW for large datasets. One comparison noted that HNSWlib needed more memory than ScaNN for the same data, since ScaNN relies on partitioning and quantization rather than keeping full neighbor lists in RAM.

Scalability and Updates: ScaNN is designed for large-scale static datasets, where one can afford an upfront training (clustering + codebook) and then perform many queries. It’s integrated into Google’s production services (e.g. serving ANN in Spanner/AlloyDB) to handle high query volumes. Like IVF/PQ, updating a ScaNN index (adding new vectors) without retraining can degrade performance if the new data isn’t well represented by existing clusters or codebooks. In practice, moderate additions can be assigned to the nearest cluster and quantized with existing codebooks, but significant data changes might require rebuilding the index. ScaNN is not typically used for rapidly changing data where inserts happen continuously in those cases, a graph index like HNSW might be preferable.

Overall, ScaNN excels in high-throughput, large-scale search where memory is a concern and slight approximation is acceptable. It is available as a library (with TensorFlow integration) and is supported in some databases (Google’s AlloyDB, and Milvus has an experimental integration). When configured properly, ScaNN often outperforms older methods in both efficiency and recall for massive datasets, making it a leading choice for production ANN search at Google-scale.

Annoy (Approximate Nearest Neighbors Oh Yeah)

Annoy is an ANN library developed by Spotify, known for its simplicity and minimal memory overhead. It uses a forest of random projection trees to index vectors, rather than graphs or hashing. Annoy was designed to be lightweight and disk-friendly for powering music recommendations at Spotify, where the index could be memory-mapped and queries are read-heavy on a mostly static dataset.

Index Structure: An Annoy index consists of multiple binary trees built by recursively splitting the data. Each tree is constructed by randomly picking two points from the dataset and using the hyperplane equidistant between them as a partition. This divides the space (and the set of points) into two halves: those closer to point A vs closer to B. The process continues recursively in each subset: pick two random points in the subset, split by their separating hyperplane, until the subset size falls below a threshold (K points per leaf). The leaves (tree endpoints) thus contain small clusters of nearby points. Each tree is built with different random choices, so an item will end up in somewhat different neighborhoods across trees, increasing the chance that at least one tree’s partitioning is favorable for any given query.

Search Algorithm: To find nearest neighbors, Annoy traverses each tree from the root, deciding at each node to go left or right depending on which side of the hyperplane the query falls. This gives a leaf node in that tree, whose points are candidates. Importantly, the search doesn’t stop there: Annoy can optionally backtrack and explore the opposite side of some splits to avoid missing neighbors near a boundary. A parameter search_k controls how many nodes/leaves are inspected in total a larger search_k means exploring more of the tree (or multiple trees) for higher recall. After accumulating candidates from all trees, the algorithm selects the nearest ones among those.

Performance and Use Cases: Annoy’s key advantage is simplicity and memory/disk efficiency. The index is essentially just the set of split hyperplanes (defined implicitly by storing two pivot points per node) plus the vectors. Annoy stores vectors on disk in a memory-mapped file and loads them on demand, which means you can search datasets larger than RAM as long as the access patterns are somewhat local. It doesn’t require heavy preprocessing (no global training like k-means). Building the trees is relatively fast (each tree build is O(n log n) for n points). Annoy is very lightweight at query time too just distance computations and binary decisions.

However, Annoy comes with trade-offs. Its accuracy/recall is generally lower than graph-based methods for high-dimensional data. To achieve higher recall, you need to increase the number of trees and search_k, which in turn increases query time and index size (because more trees = more storage, and each query must traverse all trees). In benchmarks, HNSW tends to outperform Annoy in both recall and speed when fine-tuned, especially as dimensionality grows or dataset size increases. For example, one analysis noted that for 1M 128-D vectors at ~90% recall, HNSW was faster and more memory-efficient than Annoy (Annoy’s index was ~3× larger due to multiple trees). Annoy also lacks dynamic update capability if you want to add or delete vectors, the recommended approach is to rebuild the index offline. This makes it suited only for static or infrequently updated datasets.

In summary, Annoy is a great choice for static, large datasets where memory-mapping to disk is needed and extremely fast construction is not required. Its implementation is simple and multi-threaded (you can build trees in parallel and queries can probe trees in parallel). Many projects have used Annoy for million-scale ANN search with decent results, but for mission-critical high-accuracy tasks or frequently changing data, other methods (HNSW, IVF/PQ) are often preferred. Spotify’s own engineering has evolved past Annoy; they introduced a new library “Voyager” that improves on Annoy’s memory usage and accuracy (using quantization) highlighting that even Annoy’s original creators saw the need for improvement. Nonetheless, Annoy remains a popular baseline and is very easy to deploy due to its minimal dependencies and disk-based design.

Locality-Sensitive Hashing (LSH)

Locality-Sensitive Hashing is one of the oldest approaches for ANN search, with theoretical guarantees for sub-linear query time. LSH uses hash functions designed so that similar vectors are likely to produce the same hash code (i.e. collide in the same bucket). By hashing all vectors into buckets, you can limit a nearest neighbor search to only those candidates that share a bucket with the query in at least one of several hash tables. Classic LSH was widely studied in the 2000s (Indyk & Motwani, 1998) and works for various metrics (e.g. cosine similarity, Euclidean distance, Jaccard similarity for sets, etc.) by using appropriate hash family designs.

How LSH Works: In a typical LSH for cosine similarity (angular distance), one can use random hyperplane projections as the hash family. For example, pick a random vector r of the same dimension; define the hash of data vector v as 1 if v·r >= 0 and 0 if v·r < 0. This produces a 1-bit hash. If two vectors are very similar (small angle between them), they are likely to have the same sign for r (collision). To get a more selective hash, you concatenate multiple independent random projections into a longer bit-string. Each such bit-string corresponds to a bucket in a hash table. The locality sensitive property means similar vectors have a high probability of falling into the same bucket on a given table. Of course, a single hash table might miss some neighbors (false negatives), so LSH uses multiple independent hash tables to boost recall. If two vectors are similar, chances are they collide in at least one of the many tables.

Other types of LSH exist for Euclidean distance (e.g. random shift grids) and for Jaccard (minhashing, as used for document similarity). But the high-level principle is the same: hash to buckets, then only search within those buckets.

Search Algorithm: With LSH built, a query vector is hashed by all the LSH functions to identify its buckets across the tables. All points in those buckets (across all tables) are gathered as candidates ideally this set is much smaller than the whole database. A final exact distance computation is done on these candidates to find the true nearest neighbors. The accuracy (recall) of LSH depends on how many tables and how the hash parameters are set: more tables means higher chance to find neighbors (fewer false misses) at the cost of more candidates to check (and more memory for the index). Fewer tables or shorter hash codes mean faster hashing but lower recall. There is usually a tunable threshold or target probability for collision that guides how many bits and tables to use.

Performance and Trade-offs: LSH offers theoretical guarantees on query time being sub-linear in N for certain conditions a rare property among ANN methods. It does not require training (just random projections, etc.) and supports easy updates: adding a new vector means computing its hashes and inserting into the hash tables, which is quick. Deletions are similarly straightforward. Memory usage of LSH, however, can be high: you may have, say, 20 hash tables, each with an entry for every vector (that’s 20× the number of data points stored as references). Also, if your data doesn’t actually have good locality structure, LSH might put a lot of points in the same buckets, leading to many candidates (worst case, all points in one bucket gives no speedup). For very high dimensions (e.g. 1000+), the efficiency of LSH tends to degrade because the chance of collision for truly nearest neighbors might not be very high without extremely long hashes or many tables.

In practice, pure LSH has been somewhat eclipsed by graph and quantization methods which often give better empirical performance. Still, LSH is used in some niche areas: for example, near-duplicate detection in documents and images (where minhash or cosine LSH can quickly find very similar items), or as a component in hybrid search systems. Some database systems have offered LSH-based indexes (Microsoft SQL Server had an LSH indexing feature for example, and Elasticsearch’s dense vector can use an LSH-like approach for certain similarity functions). The Pinecone and Zilliz communities also provide tutorials on LSH as a learning tool.

The key takeaway is that LSH provides a conceptually simple mechanism: it hashes similar items to the same bucket with high probability, drastically cutting down the search space. However, to reach high recall it often needs many hash tables, which increases memory and runtime (multiple lookups) to the point that other methods become more efficient. Thus, while historically important, LSH is now less commonly the first choice for large-scale vector search unless specific conditions favor it (e.g. needing theoretical guarantees or extremely high-speed lookup for low-dimensional data).

Comparison of ANN Indexing Methods

Each ANN method has different strengths and ideal use cases. We compare HNSW, IVF, PQ/OPQ, ScaNN, Annoy, and LSH on several key dimensions:

  • Accuracy/Recall: Graph-based approaches like HNSW generally achieve the highest recall for a given search time HNSW can get almost exact results if configured with a high search parameter, making it suitable for precision-critical applications. ScaNN also offers high recall, especially for inner product search, by using re-ranking to recover any lost neighbors. IVF on its own may miss neighbors if they fall in different clusters; its recall depends on data distribution and Nprobe (more probes improve recall at cost of speed). PQ/OPQ affect accuracy in a different way: they approximate distances, so even if the right neighbor is considered, its distance is estimated high compression can reduce recall unless combined with re-ranking. Annoy tends to have lower recall in high dimensions unless a very large number of trees are used. LSH usually gives probabilistic guarantees; you can increase tables to improve recall, but practical recall might plateau below 100% unless using enormous resources. In summary, for top accuracy: HNSW is often best, followed by ScaNN (with re-ranking) and IVF+PQ (with high probes). Annoy and LSH are typically used when some reduction in recall is acceptable for speed.
  • Speed/Throughput: All these methods can answer queries in milliseconds or less, but there are nuances. HNSW is known for excellent query speed, often able to traverse millions of points in a few microseconds per distance check due to the small-world structure a well-tuned HNSW might return results in <10 ms even for 1M+ vectors at high recall. ScaNN and IVF+PQ can be even faster for lower recall targets, because they eliminate large portions of the search space quickly (e.g. ScaNN might use only 5% of vectors as candidates and very fast code distance computations). In one benchmark, ScaNN handled roughly twice as many QPS as HNSW at similar accuracy. Annoy’s speed depends on the number of trees and search_k; with few trees, it’s very fast (just O(log n) per tree), but high recall settings slow it down. LSH speed depends on table lookups computing hash codes is usually fast (just dot products), but gathering candidates from many tables can add overhead. LSH can be very fast at moderate recall but tends to retrieve a larger candidate set than graph or IVF methods, which slows down the final stage. In practice, HNSW and ScaNN are often among the fastest for high recalls, while IVF/PQ excels for very large n when heavy compression keeps computations minimal. Annoy and LSH are fast in simpler or low-dimensional scenarios but may not scale as well in speed for difficult high-d datasets.
  • Memory Footprint: PQ (especially with OPQ) is the clear winner on memory efficiency it can shrink vectors by 10× or more, drastically cutting memory needs. IVF itself doesn’t save memory unless combined with PQ or other compression (IVF actually adds a small overhead for storing cluster info). ScaNN’s memory use is moderate: like IVF+PQ, it stores compressed vectors and centroids, so memory per vector is low (on the order of a few bytes + overhead). HNSW is more memory-intensive: it stores full vectors (unless you separately compress them) plus graph links. Typical HNSW configurations might use ~16–32 bytes of links per vector (M neighbors * ~4–8 bytes each) in addition to the vector itself. For example, an HNSW index for 1M 128-D vectors might consume a few hundred MB of memory, whereas a PQ code index for the same could be 1/10th of that or less (depending on code size). Annoy’s memory profile is interesting: it stores multiple copies of vectors (one in each tree) if using memory map, but as references if purely on disk. An Annoy index with 50 trees effectively stores each vector 50 times in the tree structure (as coordinates on different splits). Thus, for high tree counts, Annoy’s index file can become larger than HNSW’s memory usage at equivalent recall. However, because Annoy accesses disk pages on demand, its RAM usage can remain low if queries only touch a portion of the dataset. LSH’s memory usage can blow up with the number of tables; e.g., 20 tables means 20× entries. If each bucket entry is just an ID, that might be manageable, but if vectors are high-dimensional and one stores projections, it’s more. Overall, memory-sensitive scenarios favor PQ/OPQ (or hybrid disk solutions), whereas HNSW trades memory for speed, and Annoy/LSH lie somewhere in between (Annoy being good if disk-based, LSH if few tables).
  • Scalability: All methods can scale to millions of vectors; the question is how they handle billion-scale. HNSW in-memory becomes challenging at many billions due to RAM limits it’s possible (with enough servers or a huge RAM machine), but expensive. IVF+PQ and ScaNN are more scalable to huge datasets because of their compact storage: they have been used to index up to a billion vectors on a single machine with SSD support. Distributed scaling (sharding) works with any of the methods: you can partition data across nodes and perform searches in parallel then merge results. For disk-based scaling, DiskANN (a variant of HNSW/Vamana graph on SSD) and IVF on disk have shown that even 1B+ vectors can be searched with only a small latency penalty (~2–3 ms) compared to RAM. Annoy can also handle many vectors since it can work off disk; Spotify used it for tens of millions of songs. But as data grows, the query times might increase or the number of trees needed might become impractical for high recall. LSH can scale by increasing tables, but memory and diminishing returns become issues beyond a certain dataset size. In summary, IVF/PQ and ScaNN are often the choice for extreme-scale (billion+) due to their compression and disk-friendly nature. HNSW can scale to mid-nine-figures on a single node (with lots of RAM), or further with sharding, but at significant cost. Annoy and LSH might require many machines or compromise on recall to scale that high.
  • Dynamic Updates: If the dataset changes frequently (adds/deletes), this can influence the choice of index. HNSW supports dynamic inserts and deletions (deletions are lazy in some implementations) and remains one of the most update-friendly ANN structures. Annoy is at the opposite end: it’s effectively static; updating requires rebuilding the trees (though small append-only additions could be hacked by adding new leaves, it’s not supported out-of-the-box). IVF allows inserts by assigning new points to the nearest centroid this is straightforward and fast, though if the distribution of data shifts too much, the original centroids might become suboptimal (periodic re-clustering might be needed). Deletion in IVF is just removal from a list. PQ codebooks are usually fixed after training; adding new vectors and encoding them with the existing codebooks is fine, but if the data distribution changes significantly, the compression might degrade so major updates might warrant re-training PQ on a fresh sample. ScaNN similarly can add points to clusters and quantize them, but the quality could drop if many points fall outside the learned model; there’s no built-in incremental training. Google’s ANN services likely recompute ScaNN indexes offline for updated corpora. LSH is very update-friendly: new vectors simply get hashed into the tables, no global rebuild needed, and removal is easy too. This flexibility is a reason LSH was used historically in streaming scenarios. However, the flip side is that LSH doesn’t optimize the search structure with data it’s data-oblivious hashing so it might be less efficient query-wise than structures that do adjust to data.

To summarize the comparison in a nutshell, IVF is best for speed and clustered data but may lack precision. HNSW provides high accuracy and recall but consumes more memory and compute. PQ is optimal for memory efficiency and fast responses but requires preprocessing and may reduce accuracy. Each technique can be the best choice depending on the application’s priorities (latency, recall, hardware constraints, data size, update frequency).

Implementation Insights and Best Practices

Building an effective vector search pipeline often involves combining techniques and tuning parameters to the specific use case. Here are some insights and guidelines for practitioners:

Hybrid Indexes: As noted, many systems use combinations like IVF + PQ (coarse clustering plus code compression) to get both speed and memory benefits. Another hybrid is HNSW + PQ: one can store PQ-compressed vectors in an HNSW graph to reduce memory, using the graph for candidate search and PQ for distance eval this isn’t native in all libraries, but some (like DiskANN’s Vamana graph) effectively do a graph on compressed residual vectors. Choosing a composite index can be done with Faiss’s IndexFactory which allows chaining components (e.g. an “IVF256,PQ8” means 256 clusters and 8-byte PQ codes). Empirically, IVF+PQ is very powerful for billion-scale retrieval on disk, while HNSW is great for smaller (hundreds of millions) in-memory sets that need dynamism.

Parameter Tuning: Each algorithm has critical parameters:

  • HNSW: M (max neighbors per node) and efConstruction/efSearch. Higher M and ef increase accuracy at cost of memory and query time. A common approach is to set M moderately (e.g. 16 or 32) and then adjust efSearch until recall is satisfactory (often efSearch 2× or 4× the target k). efConstruction can be set equal to or higher than efSearch for good graph quality.
  • IVF: Nlist (number of clusters) and Nprobe. Nlist affects index build time (k-means on perhaps 10% of data) and memory (centroids storage is trivial, though). If Nlist is too low, clusters are too large (slower search); if too high, you pay more cost computing distances to many centroids. A rule of thumb is set Nlist ~ √N for N vectors, then tune Nprobe to balance recall. In Faiss, one might try Nlist such that each list has a few thousand points, then adjust Nprobe (e.g. 4, 16, 64) to see recall vs latency trade-offs.
  • PQ/OPQ: Key parameters are code size (bytes per vector) = M (subvectors) and bits per subvector (which determines K, usually 8 bits -> K=256). Increasing M or bits improves accuracy but increases memory and search time linearly. Training the codebooks on a representative sample is crucial if your sample is too small or not i.i.d., PQ performance suffers. Always use OPQ if available; it’s essentially free accuracy. Also, consider multi-level quantization (residual quantization, etc.) if you need smaller codes with better accuracy, though those are more complex to implement.
  • ScaNN: It has parameters like number of partitions (clusters) and the anisotropic quantization dimensions. Google provides an autotuning script, but generally one might start with a similar approach to IVF (choose partitions so that each has e.g. 1000 vectors) and ensure the training data for quantization is large. ScaNN also has a trade-off parameter to decide how much to favor recall vs speed (it can produce a family of indices; one then picks operating point). Fine-tuning ScaNN may require testing different quantizer settings and verifying on a validation set. It’s relatively new, so fewer off-the-shelf guides exist.
  • LSH: Parameters include the number of hash tables and the size of each hash (number of bits). These are usually chosen based on theoretical bounds or experimentation. A common strategy is to fix an approximate recall target (e.g. 90%) and incrementally add tables until that recall is met, monitoring the average candidate count (which affects speed). Since LSH hashing is fast, the cost mainly comes from scanning candidates, so tune to minimize candidate count. Modern variants of LSH (like multi-probe LSH) can adjust search range without adding tables.

Distance Metric and Data Preprocessing: Ensure you choose an index suited for your distance metric. For example, inner product (MIPS) search can be turned into Euclidean search by normalizing vectors if all vectors are non-negative, or by using trick like appending an extra dimension (used in older approaches). HNSW and Annoy natively support Euclidean or cosine (cosine just means normalize vectors and use Euclidean). IVF/PQ and ScaNN can support both, but for MIPS specifically, ScaNN’s anisotropic quantization is beneficial. If your vectors are extremely high-dimensional and sparse, consider techniques to reduce dimensionality (like PCA) before indexing many vector DBs allow a preprocessing step. Also, normalize your data if needed (for cosine, always normalize vectors to unit length; for Euclidean, sometimes whitening or standardizing dimensions can help indexing efficiency).

Index Maintenance: For dynamic scenarios, plan out a strategy. HNSW can be used in streaming ingestion just keep an eye on the growing graph size and perhaps periodically rebuild if fragmentation occurs (some implementations don’t reuse freed memory from deletions). IVF can be maintained incrementally but after a lot of updates, the cluster boundaries might become suboptimal you might schedule a re-clustering every X inserts or on a schedule. If downtime for reindexing is acceptable, one approach is to have two indices and swap: build a fresh one while querying the old, then swap over (this is what some do with Annoy since it requires rebuild). Many vector databases like Milvus or Pinecone handle these details internally by abstracting the index they may build new index files in the background for large updates while continuing to serve queries on the old index, then swap (often called a “rolling update” of the index).

Metadata and Filtering: In real applications, you often need to filter vectors by some metadata (e.g. only search images of a certain category). Not all ANN methods integrate filtering easily. One simple approach is to do the ANN search and then post-filter results, but this can hurt performance if many results are filtered out. Some databases support filtered ANN by storing multiple indexes or a composite index (like a IVF index per filter value). HNSW doesn’t natively support attribute filtering, but systems (like Vespa or Weaviate) combine it with inverted indices or rerank post-filter. If filtering is heavy in your use case, consider an index that supports it (Milvus and Vespa offer hybrid search combining boolean filters with vector similarity) or segment your data by filter values into separate indexes to reduce work. This is a higher-level consideration but important for implementation.

Hardware Optimizations: Leverage hardware when possible. FAISS, for instance, has GPU versions of IVF and PQ that can dramatically speed up searches using brute-force blocks on GPU memory. HNSW is typically memory-bound and benefits from SIMD optimizations (the HNSWlib and Faiss implementations use AVX2/FMA instructions for distance calculations). If using GPUs, ensure the batch size of queries is large enough to utilize them (GPU shines when searching many queries in parallel or one query against a huge dataset). Also, consider vector quantization to fit indexes in GPU memory (FAISS has an IndexIVFPQFastScan that uses optimized lookup on GPU memory). For disk-based search, ensure using fast SSDs and try to access sequentially DiskANN, for example, does asymmetric read IO to prefetch likely nodes. Tuning OS page cache and I/O schedulers can make a difference.

Monitoring and Benchmarks: Deploying an ANN index is not a set-and-forget task. Use benchmarks like ANN-Benchmarks to evaluate different methods on your data distribution if possible. Monitor query latency and recall in production by occasionally doing an exact search on a sample of queries for comparison (if feasible) or at least keep track of user engagement metrics for search results. The performance can degrade as data grows or changes be ready to retune parameters or switch index types if needed. It’s not uncommon to start with one method and later adopt another as requirements change (e.g. starting with Annoy for simplicity, then moving to HNSW for better recall when data scales up, etc.).

Vector Database Integration: Modern vector DBs abstract a lot of these details. For example, Milvus allows you to select the index type (HNSW, IVF_FLAT, IVF_PQ, DISKANN, etc.) and set parameters in a config, then it will handle building and querying it. FAISS is a library, so you manually build indexes, but it’s highly optimized and used under the hood by many systems (like OpenSearch, TiDB, etc. use Faiss indexes for their vector search). ScaNN can be used via its Python API or integrated into TensorFlow for ANN in embeddings workflows. Pinecone and Weaviate are cloud services that by default use HNSW indexes and handle scaling and replication for you they choose sane defaults (e.g. Pinecone uses HNSW with certain M, ef settings appropriate for the pod size). When using such services, understanding the underlying index helps in configuring dimensions like distance metric and in interpreting performance, but you don’t have to implement the ANN search yourself. It’s always good to refer to the documentation of these platforms to see what indexing methods they use. For instance, Elastic (OpenSearch) uses an HNSW index for its kNN plugin, Azure Cognitive Search recently introduced HNSW, Google Vertex AI Matching Engine uses ScaNN under the hood, and AWS OpenSearch supports Faiss/HNSW. Knowing this can guide your expectations (e.g. HNSW means slower updates but high recall, ScaNN means needing to batch queries for throughput, etc.).

Conclusion

In conclusion, managing millions of high-dimensional vectors is made feasible by these specialized indexing and search techniques. There is no one-size-fits-all: the optimal choice depends on the specific mix of accuracy, latency, memory, and data dynamics requirements. Vector databases like FAISS, ScaNN, Milvus, and others implement these methods often offering a menu of index types so that practitioners can pick what fits their use case. By understanding the internals and trade-offs of HNSW, IVF, PQ/OPQ, ScaNN, Annoy, and LSH, one can make informed decisions and tune the system to achieve both efficient and effective similarity search across all types of vectorized data.

References and Further Reading

To dive deeper into each method, consider reading the core papers and official implementations. The HNSW paper by Malkov et al. provides the theoretical and empirical analysis of the graph approach. The original PQ paper by Jégou et al. 2011 and the OPQ paper by Ge et al. 2013 detail how quantization yields efficiency. Google’s ScaNN paper “Accelerating Large-Scale Inference with Anisotropic Vector Quantization” explains the improvements to quantization for search. Spotify’s Annoy code on GitHub is very accessible for learning (just C++ implementing tree splits). And textbooks or surveys on ANN (e.g. Aumüller et al., 2017) often include LSH as a foundational technique. Additionally, the ANN-Benchmarks website and repository compare many of these algorithms on common datasets, which can inform which one might work best for a new scenario.

References

What Exactly is a Vector Database and How Does It Work — Milvus Blog

Locality Sensitive Hashing (LSH): The Illustrated Guide | Pinecone

Building Billion-Scale Vector Search — part one | by Jo Kristian Bergum | vespa | Medium

Approximate Nearest Neighbor (ANN) Search Explained: IVF vs HNSW vs PQ | TiDB

A Visual Guide to the Hierarchical Navigable Small World Algorithm

Hierarchical Navigable Small Worlds (HNSW) | Pinecone

Efficient and robust approximate nearest neighbor search using Hierarchical Navigable Small World graphs

Announcing ScaNN: Efficient Vector Similarity Search

Annoy vs HNSWlib on Vector Search — Zilliz blog

Product Quantization: Compressing high-dimensional vectors by 97% | Pinecone

Product quantization PQ indexing method — Rohan’s Bytes

What is ScaNN (Scalable Nearest Neighbors)? — Zilliz Learn

Understanding the SCaNN index in AlloyDB | Google Cloud Blog

Approximate Nearest Neighbors Oh Yeah (Annoy) — Zilliz Learn

In what ways do tree-based indices (such as Annoy’s random …

Approximate Nearest Neighbor Search in Vespa — Part 1

Introducing Voyager: Spotify’s New Nearest-Neighbor Search Library

spotify/annoy: Approximate Nearest Neighbors in C++/Python …