Vector databases from the ground up
What a vector index is
A vector index answers one question fast: which stored vectors are nearest to this one? Everything else a vector database does is storage, filtering and operations around that answer.
A search problem in disguise
An embedding turns a piece of text, an image or a user into a list of numbers. Once all of it is a list of numbers, “find something similar” becomes “find the points closest to this point”. The index is the data structure that answers that question without comparing the query to every stored item. The whole discipline is about doing that comparison cheaply without losing the answer.
That is the whole trick, and it is worth being precise about it. The index does not read your documents and does not know what a refund is. It holds coordinates and it is very good at geometry. Meaning entered the system earlier, when the embedding model turned text into a position. Every quality problem you later blame on the database probably started there.
The useful mental model is a filing cabinet organised by meaning rather than by name. You do not ask for the folder called “refunds”; you hand over a description and the cabinet returns the folders whose descriptions sit closest to it. The cabinet is fast because it never reads the folders, only their labels. That is also why it can be confidently wrong.
The index is one part of the database
A vector database is an index plus everything a database needs: durable storage, a write-ahead log, metadata, a query planner, replication, backups and access control. The index decides how fast a search is; the rest decides whether the data is still there tomorrow. A benchmark that measures only queries per second is measuring the easy half.
Most disappointment with vector databases comes from confusing the two. An index that returns neighbours in two milliseconds is not a database, and a database with a slow index is not a fast one. Before comparing products, ask which half your problem belongs to. If the complaint is latency, it is the index. If it is a lost write or a leaked row, no index will save you.
The index, without the database
import faiss
import numpy as np
index = faiss.IndexFlatIP(384)
index.add(np.array(vectors, dtype="float32"))
scores, ids = index.search(np.array([query], dtype="float32"), k=5)
for score, item_id in zip(scores[0], ids[0]):
print(round(float(score), 3), item_id)A flat index in twenty lines
Run the search, then add a sixth document about shipping and confirm it can enter the top three. Change k to 1 and to 10 and watch the edges.
Ids, not text
An index stores vectors and identifiers, never the payload (the text and other fields). Search returns ids, and your application joins them to the rows that hold the text, the tenant and the permissions. That join is where most of the interesting bugs live. A deleted row can still have a vector in the index. A vector can outlive the text it was built from, or a filter can run on the index but not on the join. Treat the join as a first-class step with its own tests, not as a line of glue.
Keeping the payload out of the index is not laziness. It lets the index be small and the payload transactional (saved as one reliable unit). It also makes it clear which of the two is the source of truth when they disagree. The index is a cache of nearness; the row is the record. Once you say that out loud, the recovery path for a corrupt index is to rebuild it from the rows.
Normalise the vectors before you build an inner-product index, or use a metric that normalises for you. Mixing normalised and unnormalised vectors in one index produces scores that look fine and rankings that are wrong.
The interface is four calls
Add a vector with an id. Search for the k nearest to a query. Delete an id. Persist and reload. Every vector database, from a ten-line Python class to a distributed service, exposes those four calls under different names. The names are the least interesting thing about them. What differs is the cost of each call and the failure mode when it goes wrong.
Everything else — the graph, the partitions, the replicas — is an implementation detail (a choice made inside the software) behind them. Learn the four calls and the operating questions that follow, and a new product becomes a mapping exercise rather than a new field. The questions are always the same. How exact is the search, what does an update cost, what happens when a node dies, and how do you know the answers are still good?
That is also the right order for learning the rest of this course. Each chapter takes one of the four calls and asks what it costs at scale. How is the index built, how does a filter change the search, and what does a delete leave behind? How is the whole thing measured when it is too large for one machine? By the end, the four calls will look like four questions about failure.
The index knows ids, not meaning
Search, delete id 3, and search again. Notice that the index never returned text: the payload dictionary did, and the two can drift.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does a vector index store?
