The Semantic Layer That Powers Modern AI Search, RAG, and Retrieval -- From Word2Vec to HNSW Indexes in Production
The Foundation of Semantic Search, Retrieval-Augmented Generation, and Modern AI Memory
Embeddings are the bridge between human language and machine mathematics. They transform text, images, and audio into dense vectors of floating-point numbers where geometric proximity encodes semantic similarity. Vector databases are the infrastructure layer built to store, index, and search these vectors at scale. Together, they form the retrieval backbone of nearly every production RAG system, semantic search engine, recommendation platform, and AI agent memory store deployed today. This guide covers the full stack: how embeddings are generated, what dimensionality means in practice, how similarity metrics work, the major vector database engines and their tradeoffs, index algorithms like HNSW and IVF, the step-by-step RAG retrieval pipeline, document chunking strategies, and production-tested code examples you can run today.
An embedding is a numerical representation of content -- typically a list of a few hundred to a few thousand floating-point numbers -- where the position of each number is learned so that semantically similar items end up close together in the resulting vector space. The word "embedding" comes from the idea that we are embedding high-dimensional, discrete things (words, sentences, images) into a continuous mathematical space where distance is meaningful.
Humans understand that "dog" and "puppy" are related, that "king" relates to "queen" as "man" relates to "woman," and that "I need to cancel my subscription" means the same thing as "How do I stop my recurring billing?" Traditional databases cannot do this -- they match exact strings or indexed tokens. Embeddings solve this by mapping each item to a point in a high-dimensional space where distance encodes meaning.
A 384-dimensional embedding is a list of 384 floats. Each dimension does not have a human-readable label -- the model learns what each axis "means" during training. But collectively, the vector captures semantic properties. Two sentences with similar meaning produce vectors that are close together (small distance, high cosine similarity) even if they share no words in common.
Embeddings enable a class of operations that are impossible with keyword search:
Embeddings are dense representations: every dimension has a non-zero value, and meaning is distributed across all dimensions. This contrasts with sparse representations like TF-IDF or BM25, where a document is a vector with mostly zeros and a few non-zero entries corresponding to the words it contains. Sparse representations excel at exact keyword matching and rare-term retrieval. Dense embeddings excel at semantic matching. Modern production search systems often combine both in a hybrid retrieval approach: BM25 for keyword precision plus embeddings for semantic recall, with results merged and re-ranked.
Embedding models have evolved through several major generations. Each generation improved semantic quality, context handling, and the range of inputs they can embed. Understanding this lineage matters because older models are still deployed in production, and the right model depends on your task, budget, and latency constraints.
Developed at Google by Tomas Mikolov and colleagues, Word2Vec introduced the idea of learning word embeddings from large text corpora using a shallow neural network. Two architectures were proposed: CBOW (Continuous Bag of Words), which predicts a target word from its context, and Skip-gram, which predicts context words from a target word. Training produces a lookup table: each word in the vocabulary maps to a dense vector (typically 100-300 dimensions). Word2Vec is static: "bank" gets the same vector whether it means a river bank or a financial bank. This ambiguity is its fundamental limitation.
Word2Vec's famous demonstration of vector arithmetic -- king - man + woman ≈ queen -- showed that embeddings capture analogical relationships. This was a watershed moment: it proved that neural networks learn structured, interpretable semantic representations without explicit programming.
GloVe (Global Vectors for Word Representation), developed at Stanford by Pennington, Socher, and Manning, takes a different training objective. Instead of predicting context words like Word2Vec, GloVe factorizes the global word-word co-occurrence matrix: it learns vectors such that the dot product of two word vectors approximates the logarithm of how often those words co-occur in a corpus. GloVe embeddings are also static (one vector per word) and typically 100-300 dimensions. Pre-trained GloVe vectors trained on Wikipedia + Gigaword (6 billion tokens) are still widely used as baseline embeddings in NLP courses and lightweight applications.
Facebook's fastText extended Word2Vec by learning embeddings for character n-grams in addition to whole words. A word's vector is the sum of its subword n-gram vectors. This means fastText can produce embeddings for words it has never seen (out-of-vocabulary words) by composing them from character sequences. It also handles morphology-rich languages better than Word2Vec or GloVe. The tradeoff is larger model size and slower lookup.
BERT (Bidirectional Encoder Representations from Transformers) was the breakthrough that made embeddings contextual. Instead of one fixed vector per word, BERT produces a different vector for the same word depending on its surrounding sentence. "Bank" in "I sat by the river bank" and "I deposited cash at the bank" get different embeddings. BERT is a Transformer encoder trained with masked language modeling (predicting randomly masked tokens) and next-sentence prediction.
For retrieval, the standard approach with BERT was to use the [CLS] token output (a 768-dimensional vector) as the sentence embedding. However, raw BERT [CLS] embeddings are surprisingly poor for semantic similarity -- the model was trained for token prediction, not sentence-level similarity. This gap motivated the next generation.
Sentence Transformers (also called SBERT) fine-tune BERT and similar encoder models on sentence-pair similarity tasks using contrastive learning. The result is a model whose sentence-level embeddings are directly optimized for semantic similarity -- cosine similarity between two SBERT embeddings meaningfully reflects how similar the sentences are. Popular models include:
OpenAI offers embedding models via API: text-embedding-ada-002 (1536 dimensions, now legacy), text-embedding-3-small (1536 dimensions, with support for shortening), and text-embedding-3-large (3072 dimensions, also shortable). These models are not open-weight -- you send text to the API and receive vectors back. They perform well on benchmarks and require no local infrastructure, but introduce a per-token cost, network latency, and a data-privacy dependency. For many enterprise deployments, self-hosted sentence-transformer or BGE models offer comparable quality at lower cost with full data control.
| Model Family | Year | Dimensions | Contextual? | Typical Use |
|---|---|---|---|---|
| Word2Vec | 2013 | 100-300 | No (static) | Legacy, teaching, simple NLP pipelines |
| GloVe | 2014 | 100-300 | No (static) | Legacy, pre-trained baselines |
| fastText | 2016 | 100-300 | No (subword) | OOV words, morphologically rich languages |
| BERT [CLS] | 2018 | 768 | Yes | Token-level tasks, not ideal for similarity |
| Sentence Transformers | 2019+ | 384-1024 | Yes | Self-hosted semantic search, RAG |
| BGE / E5 / GTE | 2023+ | 768-1024 | Yes | Top MTEB benchmarks, self-hosted |
| OpenAI text-embedding-3-small | 2024 | 1536 | Yes | API-based, no infra, per-token cost |
| OpenAI text-embedding-3-large | 2024 | 3072 | Yes | API-based, highest OpenAI quality |
The dimensionality of an embedding is the length of the vector -- how many floating-point numbers represent each item. More dimensions can capture more nuanced semantic distinctions, but at a cost: larger storage, slower similarity computation, more memory, and potentially diminishing returns. The "right" dimensionality is a tradeoff between retrieval quality, latency, and cost.
| Dimensions | Example Models | Storage per Vector | Strengths | Weaknesses |
|---|---|---|---|---|
| 384 | all-MiniLM-L6-v2, MiniLM-L12 | 1.5 KB (float32) | Fast, low memory, good baseline | Lower ceiling on nuanced tasks |
| 768 | all-mpnet-base-v2, BGE-base, E5-base | 3 KB | Strong quality/speed balance | 2x storage of 384-dim |
| 1024 | BGE-large, E5-large, GTE-large | 4 KB | High quality on benchmarks | Slower, more GPU memory |
| 1536 | OpenAI ada-002, text-embedding-3-small | 6 KB | Strong API-based quality | Per-token API cost, larger index |
| 3072 | OpenAI text-embedding-3-large | 12 KB | Highest OpenAI embedding quality | 2x cost of 3-small, large index |
Dimensionality directly drives storage cost. At float32 precision (4 bytes per float):
You can reduce dimensions after generation through techniques like PCA (Principal Component Analysis) or Matryoshka Representation Learning (MRL). OpenAI's text-embedding-3 models are trained with MRL, which means you can safely truncate the vector to a shorter length (e.g., take the first 256 or 512 dimensions of a 1536-dim vector) and retain most of the semantic quality. This is a genuine capability of the model, not a hack -- the training objective explicitly optimized for this. Truncation reduces storage and speeds up similarity computation at a modest quality cost.
Beyond dimensionality, the precision of each float matters. Most embedding models output float32. Storing as float16 halves storage with negligible quality loss for most similarity tasks. int8 scalar quantization (mapping each dimension to an integer in [-128, 127]) reduces storage 4x and speeds up distance computation significantly -- Qdrant, ChromaDB, and Weaviate all support this natively. Product quantization (PQ) compresses further (16x-32x) at a more noticeable quality cost, and is useful for billion-scale datasets where memory is the binding constraint.
Once you have vectors, you need a way to measure how "close" two vectors are. The choice of distance metric affects retrieval results and must align with how the embedding model was trained. Using the wrong metric is a common and silent retrieval-quality bug.
Cosine similarity measures the cosine of the angle between two vectors, ignoring their magnitude. It ranges from -1 (opposite) to 1 (identical direction), with 0 meaning orthogonal. For text embeddings, values typically fall in [0.5, 1.0] for related content and approach 1.0 for near-duplicates.
Cosine similarity is the default and most common metric for text embeddings. Most sentence-transformer and OpenAI models are trained with cosine similarity as the objective. It is magnitude-invariant, which means a long document and a short query with the same "direction" (topic) will still be considered similar.
The dot product is the sum of element-wise multiplications: dot(A, B) = sum(a_i * b_i). It does not normalize by vector length. If both vectors are L2-normalized (scaled to unit length), then dot product equals cosine similarity. Many vector databases support a "dot product" metric that, when combined with normalized vectors, is mathematically equivalent to cosine similarity but faster to compute (no division by norms).
Some models (notably certain OpenAI and Cohere models) are trained with dot product as the objective and their vectors are not normalized. For these, use dot product directly. Check the model documentation.
Euclidean distance is the straight-line distance between two points in space: L2(A, B) = sqrt(sum((a_i - b_i)^2)). It is sensitive to both direction and magnitude. For L2-normalized vectors, Euclidean distance and cosine similarity are monotonically related: L2_dist = sqrt(2 - 2*cosine_sim). This means ranking by one is equivalent to ranking by the other when vectors are normalized.
Euclidean distance is common in computer vision embeddings (e.g., CLIP image embeddings, face recognition embeddings) and some older models. For text, cosine similarity or dot product is almost always preferred.
| Metric | Formula | Range | Magnitude Sensitive? | Typical Use |
|---|---|---|---|---|
| Cosine Similarity | (A . B) / (||A|| ||B||) | [-1, 1] | No | Text embeddings (default) |
| Dot Product | sum(a_i * b_i) | (−∞, ∞) | Yes | Normalized vectors, certain API models |
| Euclidean (L2) | sqrt(sum((a_i − b_i)^2)) | [0, ∞) | Yes | Image embeddings, vision, clustering |
all-* family are trained with cosine similarity. OpenAI text-embedding-3 models work well with cosine or dot product (they are approximately normalized). Cohere embed-v3 specifies dot product. If unsure, L2-normalize all vectors on insertion and use cosine/dot product -- this is the safe default.
A vector database is a specialized data store optimized for inserting, indexing, and querying high-dimensional vectors along with their associated metadata. Traditional databases (PostgreSQL, MySQL, MongoDB) are designed for exact-match lookups, range queries, and relational joins. They use B-tree or hash indexes that are fundamentally unsuited for finding the "nearest" vectors in a 768-dimensional space. Vector databases exist because nearest-neighbor search in high dimensions is a different computational problem that requires different algorithms and data structures.
Given a query vector q and a dataset of N vectors, find the k vectors most similar to q. The brute-force solution is to compute the distance from q to every vector in the dataset, sort, and return the top k. This is O(N * d) per query, where d is dimensionality. For 10 million 768-dim vectors, a single query requires ~7.6 billion floating-point operations. At query time, this is too slow for interactive applications.
Vector databases solve this with Approximate Nearest Neighbor (ANN) algorithms. They pre-build index structures (HNSW, IVF, etc.) that trade a small amount of retrieval accuracy for massive speedups -- often 100x-1000x faster than brute force with 95%+ recall. They also handle the operational concerns: persistence, sharding, replication, metadata filtering, and horizontal scaling.
| Capability | Traditional DB (PostgreSQL/MySQL) | Vector DB (Qdrant/Pinecone/Milvus) |
|---|---|---|
| Primary query type | Exact match, range, join | Nearest-neighbor (similarity) search |
| Index structure | B-tree, hash, GIN | HNSW, IVF, PQ, ScaNN |
| Query complexity | O(log N) for indexed lookups | O(log N) approximate with HNSW |
| Handles high-dim vectors? | Poorly (no native ANN index) | Purpose-built |
| Metadata filtering | Excellent (SQL) | Good (varies by engine) |
| Transactional consistency | ACID, strong | Eventual to strong (varies) |
| Best for | User accounts, orders, relational data | Semantic search, RAG, recommendations |
The vector database landscape matured rapidly between 2022 and 2026. The six options below cover the mainstream choices for production RAG systems. Each has distinct strengths, deployment models, and operational profiles.
| Database | Deployment | License | Index Types | Strengths | Best For |
|---|---|---|---|---|---|
| Qdrant | Self-host / Cloud | Apache 2.0 | HNSW, scalar/int8 quantization, product quantization, binary quantization | Fast, Rust-based, excellent filtered search, payload indexinging, quantization built-in | Production RAG needing filtered search + self-hosting |
| ChromaDB | Self-host (embedded or server) | Apache 2.0 | HNSW (via hnswlib) | Simplest setup, Python-native, great for prototyping and local dev | Prototyping, local development, small-to-medium datasets |
| Pinecone | Managed Cloud only | Proprietary (SaaS) | Proprietary (HNSW-based, serverless architecture) | Zero ops, serverless scaling, good for teams without infra capacity | Teams wanting fully managed, no infrastructure management |
| Weaviate | Self-host / Cloud | BSD-3 | HNSW, scalar quantization, PQ, binary | Built-in modules for embedding generation, GraphQL API, hybrid search | All-in-one systems wanting embedded model inference + hybrid search |
| Milvus | Self-host / Cloud (Zilliz) | Apache 2.0 | HNSW, IVF, IVF_PQ, DiskANN, SCANN, GPU indexes | Billion-scale, distributed, GPU-accelerated, most index options | Very large datasets (100M+ vectors), GPU acceleration |
| pgvector | Self-host (PostgreSQL extension) | PostgreSQL License | HNSW, IVFFlat | Vector + SQL in one DB, ACID, existing Postgres ecosystem | Smaller datasets, SQL workloads, not wanting a separate DB |
Written in Rust, Qdrant is a performance-oriented vector search engine with first-class support for filtered search. Its standout feature is payload filtering with its own indexing structures, which lets you pre-filter by metadata (tags, dates, categories) before or during vector search without the performance cliff that some engines hit. Qdrant supports scalar quantization (int8), product quantization, and binary quantization natively. It runs as a single binary, a Docker container, or a managed cloud service. The API is REST-based (JSON over HTTP) with gRPC also available. For RAG systems where filtered search is important (e.g., "search only documents from department X dated after Y"), Qdrant is a top recommendation.
ChromaDB is the easiest vector database to start with. It runs embedded in a Python process (no server needed) or as a client-server setup. The Python API is clean and integrates tightly with LangChain and LlamaIndex. Chroma uses HNSW under the hood (via the hnswlib library) and stores metadata alongside vectors. Its weakness is scale and operational maturity: it is not designed for billion-vector datasets or high-throughput multi-tenant production deployments. For a local RAG prototype or a small internal tool, Chroma is excellent. For a production system serving thousands of concurrent users, evaluate Qdrant, Weaviate, or Milvus instead.
Pinecone is a fully managed, proprietary SaaS vector database. You never touch a server, configure an index, or manage replication. Its serverless architecture (introduced 2024) separates storage and compute, so you pay for what you use rather than provisioning fixed-size pods. Pinecone supports metadata filtering, namespaces for multi-tenancy, and sparse-dense hybrid vectors. The tradeoffs are cost at high query volumes, vendor lock-in, no self-hosting option, and data leaving your infrastructure. Pinecone is ideal for teams that want to ship semantic search fast and have no capacity or desire to operate infrastructure.
Weaviate is an open-source vector search engine (BSD-3 license) with a distinctive feature: vectorizer modules that can generate embeddings server-side. You can configure a Weaviate class to use, say, the text2vec-transformers module, and Weaviate will call a local or external model to embed your text on insertion -- you just send text, not vectors. Weaviate also has built-in hybrid search (BM25 + vector) and a GraphQL API. It supports HNSW, scalar quantization, product quantization, and binary quantization. Weaviate is a strong choice when you want the database to handle embedding generation and hybrid search in one system.
Milvus (created by Zilliz) is the choice for very large scale. It is a distributed, cloud-native vector database designed for datasets from hundreds of millions to billions of vectors. Milvus supports the most index types of any engine listed here: HNSW, IVF (flat, PQ, SQ8), DiskANN, SCANN, and GPU-accelerated indexes (GPU_CAGRA, GPU_IVF). Its architecture separates storage and compute, supports sharding and replication, and has a mature ecosystem (Attu for management UI, Zilliz Cloud for managed service). The operational complexity is higher than Qdrant or Chroma -- Milvus typically runs as a multi-component distributed system (etcd, MinIO, Pulsar/Kafka, and Milvus nodes). For datasets under 10M vectors, this complexity is usually not justified. For 100M+ vectors with high throughput, Milvus is the leading open-source option.
pgvector is a PostgreSQL extension that adds a vector type and two index types: HNSW (recommended, added in pgvector 0.5.0) and IVFFlat. You store vectors in a regular Postgres table alongside any other columns, and you query them with SQL: SELECT * FROM docs ORDER BY embedding <=> '[0.1, 0.2, ...]' LIMIT 10;. The <=> operator is Euclidean distance, <#> is negative inner product, and <~> is cosine distance. pgvector gives you vector search plus the full power of SQL (joins, aggregations, transactions, row-level security) in one database. For datasets up to roughly 1-5 million vectors, pgvector with HNSW is performant and operationally simple. Beyond that, dedicated vector databases offer better memory management, quantization, and distributed scaling.
The index is the data structure that makes similarity search fast. Without an index, every query scans the entire dataset (brute force / flat search). With an index, queries touch a small fraction of vectors and return approximate results with high recall. Understanding index types is essential because they control the speed-recall-memory tradeoff in your vector database.
The flat index is no index at all: the query vector is compared against every vector in the dataset, distances are computed for all, and the top-k are returned. This is exact -- 100% recall by definition. The cost is O(N * d) per query. For small datasets (up to ~10,000-100,000 vectors depending on dimensionality and latency budget), brute force is fast enough and guarantees perfect results. Many vector databases offer a "flat" or "exact" search mode for this reason. Beyond that size, you need an approximate index.
Brute force is also the ground truth for benchmarking: you compare ANN index recall against flat search results to measure how many true top-k neighbors the index finds.
HNSW is the dominant ANN index in production vector databases as of 2026. It is a graph-based index inspired by the "small world" network concept. Vectors are nodes in a multi-layer graph. The top layers contain few nodes with long-range connections; the bottom layer contains all nodes with short-range connections. A query starts at a top-layer entry point, greedily moves toward the query vector, and descends layer by layer, refining the search at each level. This hierarchical structure gives logarithmic-time search with excellent recall.
Key HNSW parameters:
HNSW strengths: excellent recall-speed tradeoff, fast queries (sub-millisecond at moderate scale), no training step required (unlike IVF), incremental insertion is efficient. HNSW weaknesses: high memory usage (the graph adds 1.5x-2x on top of raw vector storage), index does not fit in memory well for billion-scale datasets, and filtered search can be tricky (the graph may not respect filters, leading to post-filtering recall drops).
IVF works by clustering the dataset into nlist cells using k-means. At query time, the query vector is compared to the cluster centroids, and only vectors in the nearest nprobe clusters are searched (flat search within those clusters). This reduces the search space from N to roughly N * nprobe / nlist.
Key IVF parameters:
IVF requires a training step (k-means clustering on a representative sample) before insertion. This is a key difference from HNSW, which builds incrementally. IVF variants include IVF-Flat (exact search within probed clusters) and IVF-PQ (vectors within clusters are product-quantized for further compression). IVF-PQ is the most memory-efficient option for very large datasets, at the cost of lower recall.
IVF strengths: lower memory than HNSW (no graph overhead), good for very large datasets, supports quantization variants. IVF weaknesses: requires training, slower queries than HNSW at equal recall on most workloads, insertion after training can degrade cluster quality over time (may require re-training).
| Index | Recall | Speed | Memory | Build Time | Training Required? | Best Scale |
|---|---|---|---|---|---|---|
| Flat (brute force) | 100% | Slow (O(N)) | Low (vectors only) | None | No | < 100K vectors |
| HNSW | High (95-99%) | Very fast | High (1.5-2x) | Moderate | No | Up to ~100M (RAM-limited) |
| IVF-Flat | Good (90-98%) | Fast | Low | Moderate + training | Yes (k-means) | 1M - 100M |
| IVF-PQ | Moderate (85-95%) | Fast | Very low | Moderate + training | Yes | 100M - 1B+ |
| DiskANN | High (95-98%) | Medium (SSD-bound) | Low (disk) | Slow | Yes | 1B+ (disk-resident) |
Retrieval-Augmented Generation (RAG) is the dominant architecture for grounding LLMs in external knowledge. The retrieval layer -- powered by embeddings and a vector database -- is what distinguishes RAG from pure prompt engineering. Here is the complete pipeline, broken into its ingestion and query phases.
Step 1 -- Document Loading: Read source documents from their original locations: PDFs, HTML pages, Word docs, Confluence pages, databases, Notion pages, GitHub repos. Each document becomes a Document object with text content and metadata (source URL, title, author, date, department, access level).
Step 2 -- Chunking: Split documents into smaller passages (chunks) that fit within the embedding model's context window and the LLM's context budget. Chunking strategy (covered in detail in the next section) heavily affects retrieval quality. Typical chunk sizes: 200-1000 tokens with 10-20% overlap.
Step 3 -- Embedding Generation: Pass each chunk through the embedding model to produce a vector. This is a batch operation -- chunks are sent in batches of 32-256 to the embedding model (local or API) to maximize throughput. Each chunk now has: an ID, the vector, the chunk text, and metadata inherited from the parent document.
Step 4 -- Vector Database Insertion: Upsert each (ID, vector, payload) tuple into the vector database. The payload includes the chunk text, source document metadata, and any precomputed filter fields. The vector database builds or updates its HNSW index as vectors arrive.
Step 5 -- Index Optimization: After a bulk load, optionally tune index parameters (HNSW M, ef_construction, quantization settings) based on the dataset size and target latency. For incremental updates, this is done periodically rather than per-insert.
Step 1 -- Query Embedding: The user's question is passed through the same embedding model used during ingestion. Using a different model for queries than for ingestion is a fatal retrieval bug -- the vectors live in different spaces and similarity is meaningless. The output is a single query vector.
Step 2 -- Metadata Filter Construction (Optional): If the user's query implies a scope (e.g., "What did our finance team say about Q4 earnings?"), construct a metadata filter: {"department": "finance", "date": {"$gte": "2025-10-01"}}. This narrows the search space before vector similarity is computed.
Step 3 -- Vector Search: Send the query vector to the vector database with parameters: top-k (e.g., 10-20), the similarity metric, optional filter, and search-time index parameters (ef_search for HNSW, nprobe for IVF). The database returns the k most similar chunks with their scores and payloads.
Step 4 -- Re-ranking (Optional but Recommended): Retrieve a larger top-k (e.g., 50-100) from the vector database, then re-rank them with a cross-encoder model (e.g., bge-reranker-large, Cohere Rerank). Cross-encoders score query-document pairs jointly (not via embeddings) and are more accurate than pure vector similarity. Re-ranking the top 50-100 down to the final 5-10 significantly improves end-to-end RAG quality.
Step 5 -- Context Assembly: Take the final top-k chunks (after re-ranking), concatenate their text with source citations, and insert them into the LLM prompt under a "Context" or "Retrieved Documents" section. The prompt also includes the user's question and instructions to answer based on the context.
Step 6 -- LLM Generation: The LLM reads the context and question, generates an answer grounded in the retrieved chunks, and (if instructed) cites the sources. The answer is returned to the user along with the source citations for verification.
Chunking is how you split documents into the passages that get embedded and stored. It is one of the most impactful and most neglected levers in RAG system quality. Bad chunking destroys retrieval even with a perfect embedding model: a chunk that spans two unrelated topics will not match either query well, and a chunk that cuts a sentence in half loses meaning. Good chunking preserves semantic coherence within each chunk.
The simplest strategy: split text into chunks of N tokens (or N characters) with an optional overlap. Common settings: 512 tokens with 64-token overlap. Fixed-size chunking is easy to implement and predictable. Its weakness is that it ignores semantic boundaries -- a chunk may start or end mid-sentence, fragmenting the meaning. Overlap (10-20% of chunk size) mitigates but does not solve this. Fixed-size chunking is a reasonable default for homogeneous text (articles, manuals) and is the baseline most frameworks use.
Split text at sentence boundaries, then group sentences into chunks up to a target size. This preserves sentence-level semantics -- no chunk cuts a sentence in half. Use a sentence tokenizer (NLTK, spaCy, or the simpler regex-based splitter in LangChain) to identify boundaries first. Sentence chunking is better than fixed-size for prose but can still group unrelated sentences if a paragraph shifts topics mid-way.
Split at paragraph boundaries. Paragraphs are natural semantic units -- an author typically groups related sentences into a paragraph. For well-structured documents (reports, articles, documentation), paragraph chunking often produces the most coherent chunks. The challenge is variable chunk size: some paragraphs are one sentence, others are 500 words. You can merge small adjacent paragraphs or split large ones with sentence-level fallback to stay within a target size range.
LangChain's RecursiveCharacterTextSplitter is the most widely used approach in practice. It splits on a hierarchy of separators (e.g., ["\n\n", "\n", ". ", " ", ""]) -- first by paragraphs, then by lines, then by sentences, then by characters -- until chunks fit the target size. This respects document structure as much as possible while guaranteeing a size bound. It is the recommended default for most RAG ingestion pipelines.
A more advanced strategy: embed each sentence, then split at points where the cosine similarity between adjacent sentences drops below a threshold (often the 95th percentile of similarity drops). This produces chunks that are semantically coherent -- each chunk covers one "topic" as detected by embedding similarity drift. Semantic chunking requires an extra embedding pass during ingestion (embed every sentence first, then chunk), which adds cost. Research and practitioner reports suggest it can outperform fixed-size chunking on complex documents, but the gain is modest on simple text and the cost is non-trivial.
| Strategy | Coherence | Complexity | Cost | Best For |
|---|---|---|---|---|
| Fixed-Size | Low (may cut sentences) | Very simple | Low | Homogeneous text, baseline |
| Sentence-Based | Medium | Simple | Low | Prose, articles |
| Paragraph-Based | High | Simple | Low | Well-structured documents |
| Recursive | High | Simple | Low | General default (most docs) |
| Semantic | Highest (adaptive) | Complex | High (extra embedding pass) | Complex multi-topic docs |
| Document-Specific | Highest (structural) | Medium-High | Medium | Code, Markdown, PDFs, tables |
The OpenAI embeddings API is the fastest way to get a working embedding pipeline. Send text, receive a vector.
Always batch your embedding requests. Embedding one text at a time is 10x-50x slower than batching, because the model's forward pass amortizes overhead across the batch. For self-hosted sentence-transformers on GPU, batch sizes of 64-256 are typical. For CPU-only inference, 32-64 is more appropriate (larger batches do not help much on CPU and consume more memory). For OpenAI's API, batch up to 2048 texts per request (the API limit) -- this also reduces per-request overhead and network round-trips.
If storage or memory is a constraint, reduce dimensions in this order of preference:
dimensions parameter to 512 or 256. Safe, model-supported, minimal quality loss.Metadata filtering is one of the most impactful yet underused techniques in production vector search. Instead of searching across all vectors, you pre-filter by metadata (date range, category, user ID, access level) and search only the filtered subset. This improves both relevance (the user only sees results from their scope) and performance (fewer vectors to search).
The challenge is that HNSW graphs are built without knowledge of filters. A naive approach (retrieve top-k by similarity, then drop results that fail the filter) can return zero results if none of the top-k match the filter. This is called the post-filtering recall problem. Vector databases handle this differently:
| Dataset Size | Recommended Index | Expected Query Latency | RAM Needed (768-dim, HNSW) | Engine Suggestion |
|---|---|---|---|---|
| 1K - 100K | Flat (exact) or HNSW | < 1ms | < 1 GB | pgvector, Chroma, Qdrant |
| 100K - 1M | HNSW | 1 - 5ms | 1 - 10 GB | Qdrant, Weaviate, pgvector |
| 1M - 10M | HNSW + int8 quantization | 2 - 10ms | 5 - 50 GB | Qdrant, Weaviate |
| 10M - 100M | HNSW + PQ, or IVF-PQ | 5 - 20ms | 20 - 200 GB | Qdrant, Milvus |
| 100M - 1B+ | DiskANN or IVF-PQ (distributed) | 10 - 50ms | Disk-based + sharded | Milvus (distributed) |
In production, track these metrics continuously:
Source: Avondale.AI Technical Documentation Library
Category: Core AI Concepts
Type: Technical Reference Guide
License: CC BY 4.0
Suggested Citation:
Embeddings and Vector Databases Explained: The Semantic Layer That Powers Modern AI Search and RAG. Avondale.AI Technical Documentation Library. Accessed June 2026. https://avondale.ai/technical/embeddings-vector-databases.html