AI Engineering Learning Hub

Vector Databases (ChromaDB)

What a vector database does, when a NumPy array is enough, and how ChromaDB, pgvector, Qdrant and FAISS compare for RAG.

What it is

A vector database stores embeddings alongside their text and metadata, and answers 'give me the k nearest vectors to this one' quickly, even with millions of rows. It adds the things a plain array lacks: persistence, updates and deletes by id, metadata filtering, and an approximate index so queries stay fast as data grows.

ChromaDB is the easiest one to start with. It runs in-process, persists to a folder, embeds documents for you if you let it, and its API is three calls: create a collection, add documents, query. pgvector adds the same capability to Postgres you already run. Qdrant, Weaviate and Pinecone are dedicated servers for scale. FAISS is a library, not a database: blazing fast search with no persistence or filtering of its own.

For this portfolio I deliberately did not use one. A few dozen paragraphs fit in a NumPy matrix and brute-force cosine search takes microseconds. At B1 Properties, with listing documents that changed and a chatbot that needed filtering, ChromaDB was the right tool.

Why it's needed

Brute-force search over an array is O(N) per query and has no update story. Once you have more than a hundred thousand chunks, or chunks that change daily, or several tenants whose data must stay separate, you need an index, metadata filters and upserts. That is the job a vector database does.

Choosing one is also an engineering-judgement signal. Reaching for Pinecone to search fifty paragraphs is over-engineering; sticking with a NumPy array for ten million product descriptions is under-engineering. Knowing the thresholds is the skill.

How it works

  • Create a collection, which is a named set of vectors that share one embedding model. Never mix models in a collection.
  • Add documents with stable ids, their text, their vectors and metadata such as source, section or tenant. Re-adding the same id upserts, which is how you update.
  • Query with a vector (or let Chroma embed the query text) and n_results. Optionally pass a where filter on metadata to narrow the candidates first.
  • Under the hood an HNSW graph index finds approximate nearest neighbours in logarithmic time instead of scanning everything.
  • Persist to disk with a persistent client so restarts do not re-embed the corpus.
  • At scale, move to a server deployment (Chroma server, Qdrant, pgvector) so several app instances share one index.

Code example

import chromadb
from chromadb.utils import embedding_functions

# Persistent local store; the folder holds the index between restarts
client = chromadb.PersistentClient(path="./chroma_store")
embed = embedding_functions.SentenceTransformerEmbeddingFunction(
    model_name="all-MiniLM-L6-v2")

collection = client.get_or_create_collection(
    name="portfolio", embedding_function=embed,
    metadata={"hnsw:space": "cosine"})

paragraphs = open("knowledge_base.txt", encoding="utf-8").read().split("\n\n")
collection.upsert(                                  # upsert = add or update
    ids=[f"kb-{i}" for i in range(len(paragraphs))],
    documents=paragraphs,
    metadatas=[{"source": "knowledge_base.txt", "pos": i}
               for i in range(len(paragraphs))],
)

res = collection.query(
    query_texts=["experience with AWS and Docker"],
    n_results=3,
    where={"source": "knowledge_base.txt"},         # metadata filter
)
for doc, dist in zip(res["documents"][0], res["distances"][0]):
    print(round(dist, 3), doc[:90])

Where I used this

At B1 Properties I built a LangChain + ChromaDB real-estate chatbot over listing data, where documents changed and metadata filtering mattered. For this portfolio the knowledge base is small enough that an in-memory NumPy matrix beats running a separate vector store, and that was a deliberate choice.

Use cases

  • RAG over large or frequently updated document sets
  • Multi-tenant assistants where each customer's data must be filtered separately
  • Product and listing search with semantic matching plus attribute filters
  • Long-term memory for agents, storing past interactions as vectors
  • Image and audio similarity search using multimodal embeddings

Common mistakes

  • Using a vector database for a tiny corpus. It adds a dependency, a persistence folder and a failure mode for no retrieval gain.
  • Random ids on every ingest run, so each deploy duplicates the whole corpus and retrieval returns the same chunk three times.
  • Mixing two embedding models in one collection after a model upgrade. Re-embed everything into a new collection and switch.
  • Forgetting the distance metric. Chroma defaults to L2; set the space to cosine if that is what you evaluated with.
  • Treating the vector store as the source of truth. Keep the original documents elsewhere; the index should be rebuildable.

Interview questions

When do you actually need a vector database?
When brute force gets slow (roughly past a hundred thousand vectors), when documents change and you need upserts and deletes by id, when you need metadata filtering such as per-tenant isolation, or when several app instances need to share one index. Below that, an in-memory array is simpler and faster.
Why didn't this portfolio use ChromaDB?
The knowledge base is a few dozen paragraphs that change only on deploy. A NumPy matrix encoded at startup answers a query in microseconds with zero extra dependencies. Adding a vector store would have been complexity without benefit, and I would rather show that judgement than show a tool.
How does an approximate index like HNSW work, roughly?
It builds a layered graph where each vector links to its near neighbours. A query starts at the top layer, greedily hops toward closer vectors, and descends layer by layer. It visits a tiny fraction of the data and returns the true nearest neighbours most of the time, which is a worthwhile trade for a big speedup.
pgvector or a dedicated vector database?
If you already run Postgres and your scale is moderate, pgvector keeps vectors next to the rest of your data with transactions, joins and one backup story. Dedicated stores win on very large collections, heavy filtering workloads and specialised features like quantisation. Start with pgvector unless you have a reason not to.
How do you update a document that is already indexed?
Use deterministic ids derived from the document and chunk position, then upsert. The new chunks replace the old ones. If a document shrinks, delete the ids that no longer exist. Random ids make updates impossible and lead to duplicate retrieval.
What metadata should you store with each vector?
Whatever you will filter or cite by: source document, section heading, position, timestamp, tenant or user id, and a content hash to detect unchanged chunks. Metadata is cheap to store and expensive to backfill.
Ask the AI assistant about this topic

Get new topics by email

One short, practical AI-engineering lesson whenever a new topic is published. No spam, unsubscribe any time.

Need this built for your product?

I build production RAG pipelines, LLM automations and voice agents. Open to freelance projects and full-time roles.