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.
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.
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.
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.
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!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.)
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.
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.
results = collection.query(
query_texts=["fix a bicycle"],
n_results=3, # top 3 nearest
)
print(results["documents"]) # the 3 most similar docsA 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.
results = collection.query(
query_texts=["how do I fix this"],
n_results=3,
where={"topic": "bikes"}, # metadata filter!
)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.
Find close neighbors quickly without guaranteeing a perfect exhaustive result. Many different index algorithms can perform ANN.
Hierarchical Navigable Small World: a multi-layer graph where upper sparse layers make long jumps and the dense bottom layer refines the answer.
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.
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.
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.
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.
chromadb.Client()Fast local experiments. Data disappears when the process ends. Not a separate database “type.”
PersistentClient(path=...)Automatically stores and reloads local database files. Useful for local apps and development.
HttpClient(host=...)Your app connects to a separately running Chroma server, allowing multiple processes or machines to share it.
CloudClient(...)Connect to Chroma Cloud using tenant, database, and API-key credentials.
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="..."
)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.
Simple developer experience and integrated embedding workflows.
Serverless managed service; namespaces, metadata filters, dense/sparse search.
Vectors beside relational data, SQL joins, transactions, exact or ANN indexes.
HNSW-focused engine with strong payload filtering and dense/sparse vectors.
Vector plus inverted indexes, modules, hybrid search, multiple index modes.
Broad index selection and distributed architecture for very large deployments.
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.
Chroma is great for learning & small apps, but there's a whole ecosystem. Match each situation to a sensible choice.
collection.add(documents=...)…collection.query(query_texts=..., n_results=3) return?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.