paulserban.eu

Writing Edition

Paul Serban

Writing, snippets & book notes

June 22, 2026

Vector Databases Explained: Foundations and Fundamentals for Engineers

How approximate nearest neighbor search actually works, and how to choose and operate a vector database for production RAG and semantic search

Introduction

Once an application starts embedding text into vectors, a new engineering problem appears immediately: how do you actually find the nearest vectors to a query, quickly, at scale, without comparing against every single one? A brute-force scan works fine for a few thousand vectors, but a knowledge base with millions of chunks turns that same brute-force comparison into a query that takes seconds instead of milliseconds - an unacceptable latency for almost any interactive application. Vector databases exist specifically to solve this problem: they are storage and indexing systems purpose-built around the specific access pattern that embeddings require, similarity search over high-dimensional numerical data, rather than the exact-match and range-query patterns traditional databases were designed around.

This article works through vector databases from first principles: what problem they solve that a plain array of vectors and a for-loop cannot, how approximate nearest neighbor (ANN) indexing actually works underneath the abstraction, the practical landscape of purpose-built databases versus vector extensions to existing databases, and the concrete engineering decisions - index type, distance metric, filtering strategy - that determine whether a vector search system performs well or quietly degrades as it scales. Understanding these fundamentals matters regardless of which specific product a team ends up using, since the underlying trade-offs are the same across nearly every implementation.

Context: Why Similarity Search Needed Its Own Database

A relational database is built around exact-match and range queries against structured columns - find the row where id = 42, or where created_at falls between two dates - and its indexes (B-trees, hash indexes) are optimized for exactly that access pattern. Vector similarity search asks a fundamentally different question: given a query vector, find the k vectors in the entire dataset that are closest to it by some distance metric, across potentially thousands of dimensions simultaneously. There's no natural way to sort or index high-dimensional vectors along a single ordering the way a B-tree can index a single numeric column, because "closeness" in a high-dimensional space doesn't reduce to a single sortable value - this is exactly the structural mismatch that motivated the development of purpose-built vector search systems and vector-specific index structures.

The brute-force approach - computing the distance between a query vector and every single vector in the dataset, then sorting to find the closest matches - is correct but computationally expensive at scale, since it's O(n) in the number of vectors, and that cost multiplies with every dimension in the vector. For a small dataset, this is entirely tractable and often the right choice; a few thousand vectors can be brute-force compared quickly enough for interactive use. It's the scaling curve that breaks down: a system searching millions or billions of vectors cannot afford an O(n) scan per query without unacceptable latency, which is precisely the gap that Approximate Nearest Neighbor (ANN) algorithms were built to close, trading a small, usually configurable amount of recall (occasionally missing the exact best match) for orders-of-magnitude faster search.

The third reason vector databases exist as their own category, rather than being reducible to "an ANN index plus a key-value store", is that real applications need vector similarity combined with structured filtering, versioning, and update semantics that a bare ANN library doesn't provide out of the box. A production RAG system rarely wants "the closest vectors, full stop" - it wants "the closest vectors, restricted to documents the current user has access to, published within the last year, in a specific category." A vector database has to support this kind of combined vector-and-metadata query efficiently, along with the operational concerns - persistence, replication, incremental updates as new documents are indexed - that a standalone ANN library was never designed to handle on its own. This is the functional gap between a research-grade nearest-neighbor library and a production-grade vector database.

Deep Technical Explanation: How Approximate Nearest Neighbor Search Works

HNSW (Hierarchical Navigable Small World) is the graph-based indexing algorithm most vector databases use for approximate nearest neighbor search, navigating through multiple layers from coarse to fine approximations, which handles large datasets well because its query complexity grows logarithmically rather than linearly with the number of vectors. Structurally, HNSW builds a multi-layer graph where the top layer contains a sparse set of long-range connections between distant points, and each successive lower layer adds progressively denser, shorter-range connections, down to a bottom layer containing every vector in the dataset. A query starts at the top, sparse layer, greedily navigates toward the region nearest the query vector, then descends to progressively denser layers to refine that search - much like using a highway system to get to the right region of a city quickly before switching to local streets for the last few blocks. This structure is what gives HNSW its strong query-latency characteristics, at the cost of a more expensive and memory-intensive index-build process compared to simpler alternatives.

IVF (Inverted File index), and its common variant IVFFlat, take a different approach: the vector space is partitioned into a fixed number of clusters (using a clustering algorithm like k-means) during index construction, and a query first identifies which cluster(s) its vector falls nearest to, then searches only within those clusters rather than across the entire dataset. HNSW generally outperforms IVFFlat on query latency for online, interactive systems, but its index builds are slower and considerably more memory-intensive - in some measured cases using several times the memory of an equivalent IVFFlat index, which is why IVF-based indexes remain a reasonable choice on memory-constrained deployments or for workloads that tolerate somewhat higher query latency in exchange for cheaper indexing.

Beyond the choice of graph-based versus cluster-based indexing, quantization techniques address the storage and memory cost of vectors directly, by compressing each vector's representation - reducing the precision of its floating-point values, or replacing the full vector with a compact learned approximation - at some cost to search accuracy. This matters in practice because a large embedding collection at full 32-bit floating-point precision can consume a substantial amount of memory, and many production vector databases offer quantization specifically to keep large indexes resident in memory (where ANN search performs best) rather than forcing costly disk access during query time. The general engineering trade-off across all of these techniques is the same: index build time, memory footprint, query latency, and recall accuracy are in tension, and no single configuration is optimal for every workload - which is exactly why production-grade vector databases expose these as tunable parameters rather than hardcoding one choice.

The Landscape and Practical Implementation

The vector database landscape splits along one primary architectural line: purpose-built vector databases versus vector extensions added to an existing general-purpose database. pgvector adds vector search directly to PostgreSQL, storing embeddings alongside application data in the same tables and transactions, supporting both HNSW and IVFFlat indexes - the appeal here is operational simplicity: no separate system to deploy, and vector search participates in the same transactional guarantees and query language as the rest of an application's data. A dedicated vector database generally becomes the stronger choice once a workload reaches a very large number of vectors, needs horizontal scaling beyond what a single Postgres instance comfortably provides, or requires vector-specific features a general-purpose extension doesn't prioritize. Purpose-built options - Pinecone (fully managed), and self-hosted or open-source systems like Qdrant, Weaviate, Milvus, and Chroma - differ from each other mainly in deployment model, hybrid search support, and operational maturity rather than in fundamentally different indexing approaches, since most of them converge on HNSW as their primary index structure.

Hybrid search - combining vector similarity with traditional keyword search (commonly BM25 ranking) in a single query - improves recall for a large share of real RAG workloads, and is natively supported by several of the major purpose-built vector databases, which matters because pure semantic search occasionally misses queries that hinge on exact terms - product codes, proper nouns, specific error messages - where lexical matching outperforms semantic similarity. Metadata filtering is the other capability that separates a usable production system from a bare similarity-search demo: restricting a vector query to documents matching specific structured criteria (a tenant ID, a date range, an access-control tag) needs to be efficient, ideally executed as part of the same index traversal rather than as a separate post-filtering pass that discards most of the candidates a naive top-k search already returned.

The Python example below shows a realistic ingestion and hybrid-query pattern against Qdrant, combining vector similarity with a metadata filter - the kind of combined query most production RAG systems actually need rather than pure unfiltered similarity search.

# vector_pipeline.py
from qdrant_client import QdrantClient
from qdrant_client.models import (
    VectorParams, Distance, PointStruct, Filter, FieldCondition, MatchValue,
    HnswConfigDiff,
)
from openai import OpenAI

openai_client = OpenAI()
qdrant = QdrantClient(url="http://localhost:6333")

COLLECTION = "product_docs"

def create_collection():
    qdrant.create_collection(
        collection_name=COLLECTION,
        vectors_config=VectorParams(
            size=1536,
            distance=Distance.COSINE,
        ),
        hnsw_config=HnswConfigDiff(
            m=16,              # graph connectivity per node; higher = better recall, more memory
            ef_construct=200,  # candidate list size during index build; higher = slower build, better recall
        ),
    )

def embed(text: str) -> list[float]:
    response = openai_client.embeddings.create(
        input=text, model="text-embedding-3-small"
    )
    return response.data[0].embedding

def index_document(doc_id: str, text: str, tenant_id: str, category: str):
    vector = embed(text)
    qdrant.upsert(
        collection_name=COLLECTION,
        points=[PointStruct(
            id=doc_id,
            vector=vector,
            payload={"text": text, "tenant_id": tenant_id, "category": category},
        )],
    )

def search(query: str, tenant_id: str, category: str | None = None, limit: int = 5):
    query_vector = embed(query)

    must_conditions = [FieldCondition(key="tenant_id", match=MatchValue(value=tenant_id))]
    if category:
        must_conditions.append(
            FieldCondition(key="category", match=MatchValue(value=category))
        )

    return qdrant.search(
        collection_name=COLLECTION,
        query_vector=query_vector,
        query_filter=Filter(must=must_conditions),
        limit=limit,
        search_params={"hnsw_ef": 128},  # candidate list size at query time
    )

For teams already standardized on Postgres, adding vector search via pgvector avoids introducing a new system entirely. The TypeScript example below shows the equivalent pattern using pgvector, combining a vector similarity query with a standard SQL WHERE clause for metadata filtering - the same combined query pattern as above, expressed through ordinary SQL.

// pgvectorSearch.ts
import { Pool } from "pg";
import OpenAI from "openai";

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const openai = new OpenAI();

async function embedText(text: string): Promise<number[]> {
  const response = await openai.embeddings.create({
    input: text,
    model: "text-embedding-3-small",
  });
  return response.data[0].embedding;
}

async function searchDocuments(
  query: string,
  tenantId: string,
  limit = 5
): Promise<{ id: string; text: string; distance: number }[]> {
  const queryVector = await embedText(query);
  const vectorLiteral = `[${queryVector.join("",)}]`;

  const result = await pool.query(
    `SELECT id, text, embedding <=> $1 AS distance
     FROM documents
     WHERE tenant_id = $2
     ORDER BY embedding <=> $1
     LIMIT $3`,
    [vectorLiteral, tenantId, limit]
  );

  return result.rows;
}

// Schema, for reference:
// CREATE TABLE documents (
//   id TEXT PRIMARY KEY,
//   text TEXT NOT NULL,
//   tenant_id TEXT NOT NULL,
//   embedding VECTOR(1536) NOT NULL
// );
// CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);

Trade-offs and Pitfalls

The most common mistake is choosing index parameters (or an index type entirely) without validating against the actual recall and latency the application needs, rather than accepting whatever default a client library ships with. Every ANN index trades some accuracy for speed, and the size of that trade-off is controlled by tunable parameters - HNSW's graph connectivity and candidate-list size, an IVF index's cluster count and number of clusters searched - that have real, measurable effects on both query latency and the odds of missing a genuinely relevant result. Shipping with untuned defaults, then discovering months later that recall is quietly worse than assumed for the specific dimensionality and dataset size in use, is a common and avoidable production surprise; validating recall against a labeled test set during initial setup, and periodically thereafter, catches this early instead.

A second pitfall is treating metadata filtering as an afterthought layered on top of vector search rather than an integrated part of the query. Filtering after retrieving a fixed top-k set of vector matches - retrieve 10 nearest neighbors, then discard the ones that don't match a tenant ID - can silently return far fewer relevant results than expected if a large share of the nearest vectors happen to belong to other tenants, since the filtering step operates only on whatever the unfiltered vector search already returned. Most mature vector databases instead push filtering into the index traversal itself, evaluating metadata constraints as part of the search rather than after it, and understanding whether a given system does this - and configuring it to do so - is a meaningful, easy-to-miss performance and correctness concern.

Best Practices for Building on a Vector Database

Match the deployment model to the actual scale and operational context of the application rather than defaulting to whichever option is most talked about. A team already running Postgres, with a dataset in the low millions of vectors, often gets more value from adding pgvector to existing infrastructure than from standing up and operating a separate purpose-built vector database - fewer moving parts, one transactional system, one set of operational runbooks. A team anticipating hundreds of millions of vectors, multi-region distribution, or vector-specific features like native hybrid search should weigh a purpose-built system's added operational surface against the capability gap it closes, rather than assuming the simplest option will scale indefinitely.

Validate recall empirically rather than trusting an index's default configuration, using a held-out set of queries with known correct matches - the same kind of golden dataset used for evaluation more broadly - to measure whether the chosen index parameters actually deliver acceptable accuracy at the latency the application needs. This validation should be repeated whenever the underlying embedding model changes, since a different model's vector distribution can shift the recall characteristics of an index tuned against the previous model's outputs. Pair this with deliberate planning for re-indexing: since switching embedding models means re-embedding the entire corpus, and since many ANN index structures need to be rebuilt (not just incrementally updated) when significant portions of the underlying data change, treating index migration as an explicit, tested operational procedure - rather than an improvised one-off - avoids a painful surprise the first time it's actually needed.

Design metadata schemas and filtering strategy deliberately from the outset, treating access-control and tenancy boundaries as first-class query constraints rather than something bolted on after initial launch. Retrofitting proper multi-tenant filtering onto a vector index built without it in mind often requires re-indexing the entire dataset with the new metadata fields properly configured for efficient filtered search, which is considerably more expensive than designing for it from the start.

Analogies and Mental Models

HNSW's layered graph structure is best understood through the lens of a hierarchical transportation network: think of long-haul flights, regional trains, and local streets as three progressively finer-grained ways of covering distance. Getting from one distant city to a specific street address doesn't mean walking the entire way - it means taking a flight to get into the right region quickly, a regional train to get closer, and only walking the final short distance on local streets. HNSW's top, sparse layer plays the role of long-haul flights, connecting distant regions of the vector space directly; its bottom, dense layer plays the role of local streets, offering fine-grained precision only once the search has already narrowed down to roughly the right neighborhood.

The trade-off between purpose-built vector databases and extensions like pgvector is well captured by the difference between a specialized workshop tool and a versatile multi-tool already in your kit. A dedicated table saw does one class of cut exceptionally well and is worth its dedicated space in a shop doing a lot of that specific work. A quality multi-tool already in your bag handles the same cut adequately, without requiring you to carry, learn, and maintain an entirely separate piece of equipment - the right choice when that one task isn't yet the dominant thing your shop does. Neither is universally correct; the right tool depends on how central and how large-scale the specific job actually is.

The 80/20 of Working With Vector Databases

A small number of decisions determine most of a vector search system's real-world performance and reliability. Choosing and validating an appropriate ANN index configuration - rather than accepting untuned defaults - is the single highest-leverage decision, because index parameters directly control the recall-versus-latency trade-off that determines whether a "similar" result search actually surfaces the genuinely relevant material a user needs. Designing metadata filtering as an integrated part of the vector query from the start, not a post-hoc filter layered on top, is the second highest-leverage decision, since it's what prevents a filtered query from silently returning far fewer relevant results than an unfiltered recall number would suggest.

The third disproportionately valuable practice is matching the deployment model - extension versus purpose-built database - to the application's actual scale and operational capacity, rather than over- or under-provisioning relative to genuine need. Everything beyond these three - exotic quantization schemes, multi-vector retrieval strategies, fine-grained per-collection tuning - adds real value at scale, but is refinement layered on top of a foundation these three decisions already establish. Teams building their first vector-search-backed application get disproportionately more value from validating index configuration, integrating metadata filtering properly, and choosing a sensible deployment model than from exploring the full space of advanced vector database features available today.

Key Takeaways

Conclusion

Vector databases exist because similarity search over high-dimensional embeddings is a genuinely different problem from the exact-match and range queries traditional databases were built to solve, and approximate nearest neighbor algorithms like HNSW and IVF are the specific technique that makes that search fast enough to be useful at real scale. Understanding how these indexes actually work - the recall-versus-latency trade-off every configuration choice makes, and how metadata filtering needs to integrate with vector search rather than sit alongside it - turns a vector database from an opaque dependency into a system an engineer can actually tune and reason about.

The choice between a lightweight extension like pgvector and a purpose-built system like Qdrant, Weaviate, or Pinecone matters less than getting the fundamentals right regardless of which one a team picks: validated index configuration, properly integrated filtering, and a deliberate plan for the re-indexing that inevitably comes with changing an embedding model. Get those three right, and the specific product choice becomes a genuinely secondary decision - one about operational fit and scale, not about whether the underlying search will actually work.

References

  1. Efficient and Robust Approximate Nearest Neighbor Search Using Hierarchical Navigable Small World Graphs - Malkov & Yashunin, 2018: https://arxiv.org/abs/1603.09320
  2. pgvector - PostgreSQL vector extension documentation: https://github.com/pgvector/pgvector
  3. Qdrant - official documentation: https://qdrant.tech/documentation/
  4. Weaviate - official documentation: https://weaviate.io/developers/weaviate
  5. Milvus - official documentation: https://milvus.io/docs
  6. Pinecone - official documentation: https://docs.pinecone.io
  7. Chroma - official documentation: https://docs.trychroma.com
  8. Product Quantization for Nearest Neighbor Search - Jégou, Douze & Schmid, 2011 (IEEE TPAMI): https://ieeexplore.ieee.org/document/5432202