← All lessons
Lesson 28 RAG and documents 7:08

Vector databases: searching by meaning

Where your documents' vectors end up and how they get found fast. Indexes, distance metrics and the choice between the options that matter, without switching tools three times.

The video loads only if you ask: no request to YouTube before the click.

A regular database answers exact questions: give me the rows where the name is “Marco”. A vector database answers a different one: give me the texts that talk about something similar to this. It is the part that holds retrieval up, and the only component of the pipeline that behaves like real infrastructure — with indexes, trade-offs and numbers to work out.

SQL
  SELECT * FROM documents WHERE title LIKE '%polymorphism%'
  → finds it only if that word appears in the title

Vector
  search(vector("how does inheritance work in Java"))
  → also finds override, abstract classes, polymorphism:
    close meanings, different words

What it actually holds

A row in a vector store is not just a vector. It is three things, and you need all of them:

id:       "java-handbook#chunk-23"
vector:   [0.23, 0.87, -0.12, 0.45, ...]      ← to search
text:     "Polymorphism lets objects of..."    ← to answer
metadata: { source: "handbook.pdf", page: 145, section: "5.3" }

The vector is there to find, the text is what the model answers from, the metadata is what lets you filter and cite. A store that keeps only vectors forces you to hold the texts somewhere else and keep the two aligned by hand: it looks like a detail until the first re-index.

How closeness is measured

Three metrics, but the choice is simpler than it looks.

Cosine similarity. Looks at the angle between two vectors and ignores their length. It runs from -1 to 1, where 1 means “same direction”, which means same meaning. It is the standard for text and what you want in almost every case.

Euclidean distance. The straight-line distance between two points. It also accounts for vector length, which for text usually means nothing.

Dot product. Combines direction and length. On normalised vectors it coincides with cosine, which is exactly why several stores use it under the hood.

Rule of thumb: cosine, then move on. The one thing to watch is consistency: index with one metric and query with another and the scores mean nothing.

Indexes: why you don’t compare every vector

With a thousand chunks you can compare the question against all of them and take the closest. It is exact and costs nothing. With a million chunks the same approach falls over, so you use an index that agrees to be almost exact in exchange for speed.

IndexHow it worksThe trade-off
Exact (flat)Compares everything100% precise, slow at scale
HNSWNavigable layered graphVery fast, eats RAM
IVFGroups vectors into clustersGood balance, needs tuning
QuantisationCompresses the vectorsLittle RAM, some precision lost

What matters: below a hundred thousand chunks, the exact index is perfectly fine. Before that threshold, tuning an HNSW is time spent optimising something that is not the problem. At that point the problem is still chunking.

There is a consequence that surprises people: the moment you use an approximate index, retrieval can miss a relevant chunk that the exact one would have found. That is not a bug, it is the price you chose. But when comparing the quality of two setups, remember you are comparing this too.

The options, in the order you need them

ChromaFAISSLanceDBQdrantPinecone
How it runsIn your processLibraryIn your processServerCloud service
LocalYesYesYesYesNo
Metadata filtersYesNoYesAdvancedYes
Persists on its ownYesNo, by handYesYesManaged
To start withRecommendedNoYesLaterNo
In productionSmall scaleWith scaffoldingMedium scaleYesYes

Chroma to start: install and go, saves to disk, keeps text and metadata together. FAISS is the fastest library around but it is not a database: no persistence, no filters — the surroundings are yours to build. Qdrant is the next step when you need real filters or several users. Pinecone removes the maintenance and in exchange moves your documents onto somebody else’s service — which for personal notes or company material is exactly the question to ask up front, not later.

Metadata filters matter more than raw speed

Purely semantic search has a practical flaw: it cannot say no. Ask about chapter 5 and, if chapter 5 says nothing about it, you still get the least distant chunks, pulled from anywhere in the store.

Filters fix that, and they are why metadata has to be saved from the start:

search(vector("expense reimbursement"), filter={ source: "policy-2026.pdf" })
search(vector("polymorphism"),          filter={ chapter: 5 })
search(vector("holidays"),              filter={ lang: "en", year: { ">=": 2025 } })

A store without filters forces you to narrow down after retrieving, which is to say after the retrieval was already wasted. It is the main reason people switch tools mid-project.

Where it goes wrong

Changing the embedding model without rebuilding the store. Old vectors live in one space, new ones in another: the distances become meaningless numbers. There is no error, just retrieval getting worse for no visible reason. Change the model and you rebuild everything.

Re-indexing without emptying. Running indexing again over the same documents without clearing produces duplicates: the same text takes two or three of the top slots, and the model receives three copies of one thing instead of three different chunks.

Storing vectors but not texts. You retrieve identifiers and then have to fetch the texts from somewhere else, hoping nobody renumbered the chunks in the meantime.

Optimising the index before everything else. On small stores the index choice is not noticeable: chunking and the embedding model are. That is the order worth working in, not the reverse.

Reading the score as a probability. A cosine of 0.82 does not mean “82% correct”. Scores are comparable within one search, not in the absolute: the threshold below which you discard a result has to be measured on your own documents, not copied from a tutorial.

In short

ConceptIn one line
Vector databaseStores vectors and finds the ones closest to a question
What to storeVector to search, text to answer, metadata to filter
MetricCosine, save for special cases
Exact indexFine below a few hundred thousand chunks
Approximate indexesSpeed in exchange for the odd relevant chunk missed
Where to startChroma locally; Qdrant when filters or scale arrive
Metadata filtersOften more useful than raw speed
Recurring mistakeChanging the embedding model without rebuilding the store

Related lessons

  • Chunking: how documents get split

    The most underrated choice in the whole RAG pipeline. What the system can find depends on how you cut the text: bad chunking wastes the best embedding model and the best LLM.

  • Building a local RAG, from PDF to answer

    The full pipeline on a single machine: extract the text from PDFs, index it, query it. With the places where it actually trips up and how to tell whether it is working.

  • What RAG is: letting a model read your documents

    A model does not know your files. RAG lets it consult them at question time: search first, then answer. It is the difference between a closed-book and an open-book exam.

Watch on YouTube