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

Updates and deletes

Most vector indexes are built once and updated badly. An update is a delete and an insert, a delete is often a tombstone, and the gap between what is stored and what is live is where stale answers come from.

Part 5 of 10

Intermediate15 min readBack to Courses

Why in-place update is hard

A graph index stores edges between neighbours. Change one vector and every edge that pointed to it may now be wrong. Insert one and the graph needs new edges to stay navigable. Rebuilding the affected part is possible; rebuilding the whole graph is what many engines actually do. The cost is not the update itself, it is the maintenance window the update schedules.

A partition index has the same problem with centroids (the centre points of its lists). A vector that moves should perhaps move lists, and the centroid it was assigned to was computed from the old data. The index is a summary of the corpus, and changing the corpus invalidates the summary. The summary is still useful, but it is no longer the exact thing you measured when you built it.

This is also why the update path is the part of a vector database that benchmarks rarely cover. Insert-only workloads are easy and fast. The moment a corpus changes, the index has to match its summary to the new truth, and every engine does that differently. A product that edits documents daily is running a different workload from the one the benchmark measured.

Tombstones and compaction

The usual compromise is a tombstone (a deleted marker): mark the id deleted, filter it out of results, and reclaim the space later in a compaction pass (a background clean-up). Deletes become cheap and immediate, and the index slowly fills with vectors that are stored but never returned. The search stays correct as long as the filter is applied everywhere, which is a large “as long as”.

That is a reasonable trade as long as you watch it. Compaction is a background job with a cost, and an index that is ninety percent tombstones is slow. If the filter is ever wrong, it is also a leak of deleted content. The metric to watch is simple: live vectors divided by stored vectors, and the trend over a week.

A delete with an explicit selector

from qdrant_client import models

client.delete(
    collection_name="docs",
    points_selector=models.PointIdsList(
        points=["doc-1041", "doc-1042"],
    ),
    wait=True,
)

Delete now, reclaim later

Delete two ids and compare the live results before and after compaction. Then delete every other id and watch the gap between stored vectors and live ones grow.

Output
Click "Run" to execute your code

The visibility problem

A delete that is acknowledged but not yet applied is a delete that will come back. In a replicated system the replica you read from may not have seen the tombstone; in a single node the search may be using a cached graph. “Deleted” is a consistency question, not a true-or-false value. The product promise should be written as a time, not as a state.

The same applies to updates. If the payload is updated but the vector is not, search returns the old meaning with the new text. Re-embedding is part of the write, not a follow-up job, or the two stores drift. A queue that re-embeds later is fine only if search excludes the row until the new vector lands.

Deleting by metadata is a scan in disguise. If a user asks to be forgotten, delete by a stable id you recorded at write time; a filter-based delete that misses one collection is a compliance incident, not a bug.

Changing the embedding model

Vectors from two models are not comparable, even when the dimensions match. Upgrading the model means re-embedding the corpus and switching the query side at the same time. That is a migration with two indexes rather than an in-place edit. The old index keeps serving until the new one has been measured on the same queries, and then it is deleted on purpose.

Version the embeddings. Write the model name and version next to every vector, query only the current version, and keep the old index until the new one has been measured. A version field costs one column and saves an outage. It also makes the rollback obvious: point the query back at the old version, which is still there because you did not delete it in the same deploy.

The migration plan fits on one page. Build the new index beside the old one, and fill in old documents in batches. Write new documents to both indexes, compare a sample of queries, switch reads, and keep the old index for a rollback window. The hard part is not the plan. It is resisting the temptation to skip the comparison because the new model scored better on a benchmark that was never your traffic.

Two embedding versions, one index

Search across all versions, then restrict to v1 and v2. Confirm that the mixed search compares vectors that were never meant to share a space.

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

Why is an in-place vector update hard for a graph index?