XP: 0
⚙️ Part 2 · Module 10

Vector Databases (ChromaDB)

In Module 9 you computed cosine similarity against every document — fine for 8 docs, hopeless for 8 million. A vector database stores embeddings and finds the nearest ones almost instantly. You'll build a real searchable store with ChromaDB in a handful of lines.
Why you need one

Comparing to every doc doesn't scale

Module 9's approach was a brute-force scan: to find the closest vector, compare the query to all of them. With 8 docs that's instant. With 8 million docs — and thousands of queries a second — it collapses. A vector database solves this: it indexes your vectors so it can find the nearest ones without checking them all.

Analogy — a library with a catalog

Finding a book by walking past every shelf = brute force. A vector DB is the catalog: it already organized everything by "location in meaning-space," so it jumps you to the right shelf instantly. You skip 99.9% of the books.

🎮 Game 1

Brute-force vs vector DB — the speed race

Slide the number of documents and race the two approaches. Brute force scales with the collection size; the vector DB stays nearly flat thanks to its index.

Search time as data grows
documents: 1,000
10010K1M100M
🐢 Brute force
⚡ Vector DB
The anatomy

What's inside: vectors + IDs + metadata + documents

A vector DB stores four things per item: the embedding vector (for similarity), an ID, the original document text, and optional metadata (tags like author, date, category — for filtering). You add items once; then you query by meaning.

python · chromadb — create & add
import chromadb
client = chromadb.Client()
collection = client.create_collection("my_docs")

collection.add(
    documents=["Repairing a bike tire", "Best winter coats"],
    metadatas=[{"topic":"bikes"}, {"topic":"clothing"}],
    ids=["doc1", "doc2"],
)   # Chroma embeds the text for you automatically!
Chroma embeds for you

You pass text, and ChromaDB runs an embedding model internally to make the vectors — so you don't even call the embedder yourself. (You can also pass your own vectors if you want a specific model.)

🎮 Game 2

Add documents to a collection

Click "Add" to insert docs one by one. Watch each get an auto-generated embedding vector, an ID, and its metadata — exactly what collection.add() does.

collection.add(...) — building the store
Each row = one stored item.
🎮 Game 3

Query it — nearest neighbors, instantly

Now the payoff. Type a query; the DB returns the top-k most similar documents by meaning — in one call. This is what you built manually in Module 9, but instant and scalable.

collection.query(...) → top matches
Try: "fix a bicycle", "staying warm", "sad feelings"
python · chromadb — query
results = collection.query(
    query_texts=["fix a bicycle"],
    n_results=3,               # top 3 nearest
)
print(results["documents"])   # the 3 most similar docs
🎮 Game 4

Filter by metadata (search + constraints)

A key power: combine semantic search with exact filters. "Find docs similar to X, but only where topic = bikes and year ≥ 2023." Toggle filters and watch the results narrow.

Semantic search + metadata where-clause
Query: "how do I fix this" · toggle filters:
python · filtered query
results = collection.query(
    query_texts=["how do I fix this"],
    n_results=3,
    where={"topic": "bikes"},   # metadata filter!
)
The secret sauce

How is it so fast? Approximate Nearest Neighbors

The trick behind vector DBs: instead of comparing the query with every stored vector, an index identifies a promising region or route and scores only a candidate set. It usually finds a very-nearly nearest result while doing far less work. This is ANN — Approximate Nearest Neighbor search.

ANN = SEARCH GOAL

Find close neighbors quickly without guaranteeing a perfect exhaustive result. Many different index algorithms can perform ANN.

HNSW = ONE ANN INDEX

Hierarchical Navigable Small World: a multi-layer graph where upper sparse layers make long jumps and the dense bottom layer refines the answer.

Approximate does not mean random

ANN deliberately explores the most promising candidates. Quality is measured with recall@k: how many of the true exact top-k neighbors appeared in the ANN top-k. More exploration improves recall but costs latency.

Five ways an ANN index avoids a full scan
Click each family. A vector database may support one or several of these.
HNSW: take the express levels, then refine
The query seeks node H. Run the search and watch it descend from sparse shortcuts to dense neighbors.
Ready: start at an entry point on the sparse top layer.
The speed-quality knobs

For HNSW, M controls graph connections, ef_construction controls effort while building, and query-time ef controls how many candidates are explored. Higher values generally improve recall but consume more memory, build time, or query time. IVF uses lists and probes for a similar trade-off.

🎮 Game 5

ANN: hop through a graph, don't scan everything

Watch the difference. Exact search checks every point (red, slow). ANN hops through a small graph toward the query (teal, fast) — visiting a handful of points instead of all of them.

Exact scan vs ANN graph-hop
The ⭐ is your query; find the nearest dot.
Chroma beyond the notebook

Is ChromaDB only in memory? No — these are deployment modes

Chroma is one database with several ways to run or connect to it. In-memory is convenient for a disposable notebook, while persistent, server, and cloud modes keep data beyond one Python process.

Ephemeral memory

chromadb.Client()

Fast local experiments. Data disappears when the process ends. Not a separate database “type.”

Local persistent

PersistentClient(path=...)

Automatically stores and reloads local database files. Useful for local apps and development.

Client/server

HttpClient(host=...)

Your app connects to a separately running Chroma server, allowing multiple processes or machines to share it.

Managed cloud

CloudClient(...)

Connect to Chroma Cloud using tenant, database, and API-key credentials.

python · four Chroma connection patterns
import chromadb

memory = chromadb.Client()
local = chromadb.PersistentClient(path="./chroma_data")
server = chromadb.HttpClient(host="localhost", port=8000)
cloud = chromadb.CloudClient(
    tenant="tenant-id", database="database-name", api_key="..."
)
The ecosystem

Six common choices — and what actually differs

A product choice is not merely “which one is fastest?” Consider where it runs, who operates it, existing infrastructure, filtering and hybrid-search needs, index choices, update rate, multi-tenancy, scale, and cost.

Chroma

LOCAL · SERVER · CLOUD

Simple developer experience and integrated embedding workflows.

Pinecone

MANAGED CLOUD

Serverless managed service; namespaces, metadata filters, dense/sparse search.

pgvector

POSTGRES EXTENSION

Vectors beside relational data, SQL joins, transactions, exact or ANN indexes.

Qdrant

OPEN SOURCE · CLOUD

HNSW-focused engine with strong payload filtering and dense/sparse vectors.

Weaviate

OPEN SOURCE · CLOUD

Vector plus inverted indexes, modules, hybrid search, multiple index modes.

Milvus

OPEN SOURCE · DISTRIBUTED

Broad index selection and distributed architecture for very large deployments.

Do not skip these

A vector database is more than “store and search”

Embedding contractStored and query vectors must use the same embedding model, dimension, normalization, and semantic space.
Distance metricChoose cosine, dot product, or Euclidean distance to match how the embedding model was designed and normalized.
Recall versus latencyBenchmark ANN results against an exact-search sample. Tune the index instead of trusting defaults blindly.
Metadata filteringIndex fields used by filters. Filtering can change the best query plan and can break naive graph traversal.
Hybrid retrievalDense semantic search misses exact IDs and jargon; combine it with keyword/BM25 or sparse-vector signals.
RerankingRetrieve a wider candidate set quickly, then use exact vectors or a cross-encoder to reorder the shortlist.
CRUD and freshnessPlan inserts, upserts, deletes, index-build lag, compaction, and whether queries require immediate consistency.
Tenancy and securityUse namespaces, tenant fields, row-level rules, or separate collections. Never rely on similarity search for access control.
OperationsUnderstand persistence, backups, snapshots, replicas, shards, failure recovery, monitoring, memory, disk, and cloud cost.
Three different decisions

Database: Chroma, Pinecone, pgvector, Qdrant, Weaviate, or Milvus. Index: flat, HNSW, IVF, PQ, DiskANN, and so on. Embedding model: the model that creates vectors. Choosing one does not automatically choose the other two.

🎮 Game 6

Which vector DB, and when?

Chroma is great for learning & small apps, but there's a whole ecosystem. Match each situation to a sensible choice.

Pick the right store
Scenario 1 of 6
🎯 Check your understanding

Quick challenge

1. Why use a vector database instead of Module 9's brute-force scan?
2. In ChromaDB, when you call collection.add(documents=...)
3. What is metadata used for?
4. What does ANN (Approximate Nearest Neighbor) trade for speed?
5. What does collection.query(query_texts=..., n_results=3) return?
6. What does HNSW stand for?
7. Which statement correctly separates the three choices?
Takeaway

What you learned

A vector database stores vectors with IDs, source data, and metadata; supports updates, filters, and retrieval; and uses indexes to scale similarity search. ANN is the approximate-search goal. HNSW means Hierarchical Navigable Small World and is one graph-based ANN index; IVF, quantization, trees, and DiskANN are other approaches. Chroma can run in memory, persist locally, use a server, or connect to cloud. Pinecone is managed; pgvector adds vectors to Postgres; Qdrant emphasizes vector search plus filtering; Weaviate combines vector and inverted indexes; Milvus offers distributed scale and broad index choices. In production, measure recall and latency, keep the embedding contract consistent, secure tenants, plan CRUD and backups, and use hybrid retrieval when exact terms matter.

Next up → Module 11: RAG (Retrieval-Augmented Generation) — the big payoff of Half 2. Combine your vector DB with an LLM: retrieve relevant docs, stuff them into the prompt, and let the model answer using your data. This is how you give an LLM knowledge it was never trained on.