Vocabulary for this course

The terms the chapters assume, in plain words. You can open this from any chapter.

approximate nearest neighbour (ANN)
A search that checks only part of the index, so it is fast but may miss some of the true closest matches.
collection
A named group of vectors that share one configuration and are searched together.
cosine similarity
The dot product of two unit-length arrows, from 1 when they point the same way to -1 when they oppose.
dot product
Multiply matching numbers in two lists and add the results; it grows with both length and agreement.
embedding
A list of numbers that places a piece of text in a space where similar meanings sit close together.
embedding model
The trained program that turns text into vectors, with one fixed output width.
hybrid search
Running a word-matching search and a meaning-based search together, then combining their ranked lists into one.
index
A structure built ahead of time so the database can find nearby items without comparing against every one.
magnitude
The length of a vector, found by squaring its numbers, adding them and taking the square root.
metadata filter
A rule that keeps only the items whose stored facts, such as date or owner, match before or during the search.
model card
A short document describing a trained system: its data, its scores and its known limits.
normalised vector
A vector divided by its own length, so it has length one and only direction remains.
quantisation
Storing each number with less precision, which shrinks memory and usually costs a little accuracy.
recall
The share of the truly closest items that a search actually returned.
reranking
Scoring a short list of results again with a slower, more careful method and putting the best first.
similarity function
A rule that scores two vectors and returns a number saying how alike they are.
top-k
The shortlist a search returns: its best few results, cut off at a number chosen in advance.
training loss
The average error an embedding model makes on the text pairs it is trained to match.
unit length
A vector whose magnitude is exactly 1, which makes a dot product read as pure agreement.
vector
An ordered list of numbers, where each position is one measured property.

Vector databases from the ground up

Sharding and scale

One machine holds a finite number of vectors. Sharding splits the corpus, replication copies it, and both change what top-k means and what a delete costs.

Part 7 of 10

Advanced17 min readBack to Courses

The memory bill arrives first

A float32 vector (four bytes per number) of 768 dimensions is about three kilobytes. Ten million of them are thirty gigabytes before any index overhead, and a graph index adds edges on top. The first scaling problem is usually RAM, not query throughput. So a machine that answers ten thousand queries a second can still be unable to hold the corpus.

That number is also the argument for quantisation (storing each number with fewer bits): int8 cuts it by four, product quantisation by more, at a cost in recall. Decide which cost you are paying before you add machines, because sharding is the most expensive way to buy memory. Quantisation is a setting change; a shard is a new failure domain, a new backup and a new thing to monitor.

The arithmetic is worth doing before the design diagram. A single node with 64 GB of RAM and a graph overhead of 1.5 holds roughly fourteen million 768-dimensional float32 vectors, with nothing left for the operating system. That is the real capacity, not the number on the instance page. It is the number that decides whether the next conversation is about quantisation or about shards.

Sharding by hash or by tenant

Hash sharding spreads vectors evenly and makes the query a scatter-gather (ask every shard, then merge). Every shard returns its top k, and a merger takes the global top k. It scales writes and reads evenly and makes cross-tenant queries impossible to avoid. The price is that every query pays for the slowest shard and every incident involves more than one machine.

Sharding by tenant puts each customer on one shard, so a query touches one machine and a delete is local. It also creates hot shards when one tenant is much larger than the rest, which is a capacity planning problem rather than a search one. The routing table becomes the most important piece of state in the system, and it needs a backup.

The index, inside Postgres

import psycopg

with psycopg.connect(dsn) as conn:
    conn.execute(
        "CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops) "
        "WITH (m = 16, ef_construction = 64)"
    )
    rows = conn.execute(
        "SELECT id, embedding <=> %s AS distance "
        "FROM items ORDER BY distance LIMIT 5",
        (query_vector,),
    ).fetchall()

Merge before you answer

Compare the global top three with the naive concatenation and the merged version. Then move every good id into shard zero and confirm the naive version returns nine rows for k=3.

Output
Click "Run" to execute your code

Replication changes the answer

Replicas are how you survive a machine and how you serve more queries, and they introduce lag. A write acknowledged by the primary may not be visible on the replica that answers the next search. So a user can add a document and fail to find it. That is not a cache problem, and clearing the cache will not fix it.

The fix is a consistency promise, not a bigger cache. Route reads that must see their own writes to the primary, or carry a version and wait until the replica has caught up. Most search products can tolerate a second of lag; a delete usually cannot. Write the promise down as a number and test it with a clock, not with a feeling.

Top-k across shards is not the union of each shard’s top k. Merge and re-rank before you return, or the client receives k times too many results in an order that is not global.

The cost model to write down

Every shard multiplies the fixed cost: memory, replicas, snapshots, and the operational attention a failure needs. The number that decides the shard count is the largest corpus one machine can hold with headroom, not the total divided by a round number. Headroom is not optional. Compaction, a rebuild and a traffic spike all want the same memory as the steady state.

Write the model down before the incident: vectors × dimensions × bytes, plus index overhead, plus a replica factor, plus headroom. A rough number that everyone agrees on is worth more than a dashboard nobody trusts. Recompute it when the embedding model changes, because a dimension change moves every term at once.

The model also gives you a test: double the corpus and the shard count should roughly double, unless quantisation or a shorter vector moved the per-vector cost. If the plan says otherwise, one of the terms is stale. The stale term is usually the dimension of the current embedding model. Recompute the plan when the model changes, not when the incident does.

Size the shards on paper

Run the table, then change the dimension to 1536 and the budget to 128 GB. Recompute the shard count and the replica cost.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What usually limits a single vector node first?