AI Agent Memory Layer

Vector Databases

A comprehensive guide to vector databases powering similarity search, RAG pipelines, and long-term agent memory. Covers Qdrant, Milvus, and pgvector with hands-on examples.

1 Core Concepts

Embeddings

Dense numerical vectors (e.g., 768 or 1536 dimensions) that represent semantic meaning of text, images, or audio. Generated by models like OpenAI text-embedding-3-small, Cohere embed-v4, or open-source all-MiniLM-L6-v2.

Similarity Search (ANN)

Approximate Nearest Neighbor search finds the most semantically similar vectors without scanning every record. Distance metrics include Cosine Similarity, Euclidean (L2), and Dot Product.

HNSW Index

Hierarchical Navigable Small World — the dominant indexing algorithm. Builds a multi-layer graph of vectors for O(log n) search. Tunable via ef_construction (build quality) and M (max connections per node).

IVFFlat Index

Inverted File with Flat compression. Clusters vectors into nlist partitions using k-means, then searches only the closest nprobe clusters. Faster build time than HNSW, lower memory usage, but slightly lower recall.

Metadata Filtering

Attach structured key-value metadata (JSON payloads) to vectors and apply filters before or after ANN search. Critical for multi-tenant RAG, date-range filtering, and access-controlled agent memory.

Collections & Sharding

A collection is a named group of vectors with the same dimensionality and distance metric. Large collections are sharded across nodes for horizontal scaling. Each shard holds a partition of the index.

Hybrid Search

Combines dense vector similarity with sparse keyword matching (BM25). Fuses scores via Reciprocal Rank Fusion (RRF) or linear weighting. Essential for production RAG where keyword precision matters alongside semantic recall.

RAG Pipeline

Retrieval-Augmented Generation: Query embeddings retrieve relevant context chunks from a vector store, which are injected into the LLM prompt. The database serves as the agent's long-term memory layer.

2 Database Comparison

Feature Qdrant Milvus pgvector
TypePurpose-built vector DBPurpose-built vector DBPostgreSQL extension
LanguageRustGo + C++C (PG extension)
Index TypesHNSWHNSW, IVF, DiskANN, GPUIVFFlat, HNSW
FilteringPre-filter (payload index)Pre-filter (scalar index)SQL WHERE clauses
Hybrid SearchSparse + Dense fusionFull-text + vectortsvector + pgvector
ScalabilityDistributed shardingCloud-native, K8s operatorSingle-node (PG replicas)
Max VectorsBillions (sharded)Billions (partitioned)~10M practical limit
Best ForAgent memory, RAGLarge-scale ML pipelinesExisting PG stacks

3 Setup Guide

Qdrant (Docker)

# Pull and run Qdrant
docker pull qdrant/qdrant
docker run -p 6333:6333 -p 6334:6334 \
-v $(pwd)/qdrant_data:/qdrant/storage \
qdrant/qdrant
# Dashboard available at
# http://localhost:6333/dashboard
# Install Python client
pip install qdrant-client

Milvus (Docker Compose)

# Download compose file
wget https://github.com/milvus-io/
milvus/releases/download/
v2.5.6/milvus-standalone-
docker-compose.yml \
-O docker-compose.yml
# Start Milvus
docker compose up -d
# Install Python client
pip install pymilvus

pgvector (PostgreSQL)

# Docker with pgvector
docker run -p 5432:5432 \
-e POSTGRES_PASSWORD=secret \
pgvector/pgvector:pg17
# Enable the extension
psql -U postgres -c \
"CREATE EXTENSION vector;"
# Install Python driver
pip install psycopg2-binary pgvector

4 Usage Examples

Qdrant — Python RAG Pipeline

# qdrant_rag.py
from qdrant_client import QdrantClient, models
from openai import OpenAI
client = QdrantClient("localhost", port=6333)
openai = OpenAI()
# Create collection
client.create_collection(
collection_name="agent_memory",
vectors_config=models.VectorParams(
size=1536,
distance=models.Distance.COSINE
)
)
# Upsert with metadata
embedding = openai.embeddings.create(
model="text-embedding-3-small",
input="Deploy agents on K8s"
).data[0].embedding
client.upsert("agent_memory", [
models.PointStruct(
id=1,
vector=embedding,
payload={"topic": "k8s",
"source": "docs"}
)
])
# Search with filter
results = client.query_points(
"agent_memory",
query=embedding,
query_filter=models.Filter(
must=[models.FieldCondition(
key="topic",
match=models.MatchValue(
value="k8s")
)]
),
limit=5
)

pgvector — SQL-Native Similarity Search

-- Create table with vector column
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
content TEXT,
metadata JSONB,
embedding vector(1536)
);
-- Create HNSW index
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16,
ef_construction = 64);
-- Insert embeddings
INSERT INTO documents
(content, metadata, embedding)
VALUES (
'Deploy agents on K8s',
'{"topic":"k8s"}'::jsonb,
'[0.1, 0.2, ...]'::vector
);
-- Nearest neighbor search
SELECT content,
1 - (embedding <=> '[0.1,...]'::vector)
AS similarity
FROM documents
WHERE metadata->>'topic' = 'k8s'
ORDER BY embedding <=> '[0.1,...]'::vector
LIMIT 5;

5 Best Practices

Chunk Strategy

Split documents into 256–512 token chunks with 10–20% overlap. Use semantic chunking (by paragraph or section) over fixed-size splitting. Smaller chunks improve retrieval precision; larger chunks preserve context.

Embedding Model Selection

Benchmark on your domain data using MTEB. text-embedding-3-large (3072d) for max quality, all-MiniLM-L6-v2 (384d) for low-latency self-hosted. Match dimensionality to your performance budget.

Index Tuning

HNSW: set ef_construction=128 for high-quality builds. At query time, tune ef_search — higher values (200+) increase recall at the cost of latency. Profile recall@10 vs. p99 latency for your SLA.

Payload Indexing

Create indexed payload fields for any metadata you filter on. Without payload indexes, Qdrant/Milvus fall back to brute-force post-filtering, which destroys performance on large collections.

Multi-Tenancy

Use tenant_id as an indexed payload field rather than separate collections per tenant. Single-collection multi-tenancy with payload filtering is more memory-efficient and simpler to manage.

Quantization

Enable scalar (int8) or binary quantization to reduce memory by 4–32×. Binary quantization + rescoring achieves 95%+ recall while cutting RAM usage and increasing throughput significantly.

6 Scaling & Production

Collection Sharding

Distribute a collection across multiple shards on different nodes. Qdrant and Milvus both support automatic sharding. Set shard count at creation — more shards = more write throughput but higher query fan-out latency.

Replication for HA

Set replication_factor ≥ 2 so each shard has replicas on separate nodes. Qdrant uses Raft consensus for leader election. Milvus replicates via its message queue (Pulsar/Kafka). Replicas serve read queries for horizontal read scaling.

Billion-Vector Strategy

For 1B+ vectors: use binary quantization (32× RAM reduction) + disk-based HNSW (Qdrant) or DiskANN (Milvus). Only load frequently accessed segments into RAM. Use segment-level caching for hot data.

Write Throughput

Batch upserts in chunks of 100–1000 points for optimal throughput. Use async clients. Disable indexing during bulk import (indexing_threshold=0 in Qdrant), then rebuild. Milvus supports bulk insert via Parquet files.

pgvector Scaling Limits

pgvector is single-node — scale via PostgreSQL read replicas for read throughput and partitioning by tenant for multi-tenant workloads. Practical limit: ~10M vectors per table with HNSW. Beyond that, migrate to a purpose-built vector DB.

Monitoring at Scale

Track p99 search latency, recall@K, indexing lag, and memory/disk usage per shard. Set alerts when search latency exceeds SLA (e.g., 50ms p99). Monitor segment merge operations that temporarily increase latency.

7 FAQ

When should I use a purpose-built vector DB vs. pgvector?
Use pgvector if you already run PostgreSQL and have <5M vectors — it avoids adding infrastructure. Use a purpose-built DB like Qdrant or Milvus when you need distributed sharding, billions of vectors, advanced pre-filtering, or sub-10ms p99 latency at scale.
What embedding dimensions should I use?
Higher dimensions (1536–3072) capture more nuance but cost more RAM and compute. For most RAG applications, 1536 dimensions (OpenAI's text-embedding-3-small) is the sweet spot. For latency-critical or edge deployments, 384-dimensional models like all-MiniLM-L6-v2 are excellent.
How do I handle embedding model upgrades?
Embeddings from different models are not compatible — you cannot mix them in a single collection. Create a new collection, re-embed all documents, validate recall on a test set, then switch the alias. Qdrant supports collection aliases for zero-downtime migrations.
What is the difference between HNSW and IVFFlat?
HNSW builds a multi-layer graph and offers the best recall and query speed, but uses more memory and has slower build times. IVFFlat uses k-means clustering and is faster to build with lower memory usage, but has slightly lower recall. For pgvector, HNSW is generally preferred for production workloads.
How much RAM do I need?
Rule of thumb: 1M vectors × 1536 dims × 4 bytes ≈ 6 GB for raw vectors alone, plus ~2× for HNSW graph overhead = ~12–18 GB total. With scalar quantization (int8), this drops to ~3–5 GB. Binary quantization reduces it further to ~1–2 GB with rescoring.