Blog

Snapshots, Backup and Zero-Downtime Reindexing

How to back up a collection, how to restore one, and the alias swap that lets you rebuild an index with a new model or new parameters while traffic keeps flowing. The operational part you need before your first incident, not after.

Part 2 said the vector dimension and the distance metric are fixed at creation and can never be changed. Part 3 said changing m rebuilds the graph. Part 8 said migrating to a new embedding model means re-embedding everything.

All three are the same operational problem: you need to replace a collection while people are using it. This part is how.

It is also, less excitingly, how you take a backup — which matters more and gets thought about less.

Qdrant 1.19.1, with the alias and snapshot calls executed against a live server.


Try this first

You’re changing embedding model. The new one has a different dimension, so the existing collection cannot be altered — it has to be replaced.

Sketch the cutover. Assume search traffic is continuous and you cannot return errors or empty results at any point.

The naive plan has a window where the system is broken. Find it before reading on.


Snapshots

A snapshot is a consistent copy of a collection at a point in time, written to the server’s disk.

snapshot = client.create_snapshot(collection_name="docs", wait=True)
print(snapshot.name, snapshot.size)
# docs-1672520078969318-2026-09-18-07-31-53.snapshot 407040

The name carries the collection, an internal id and a timestamp. You can list them, download them and delete them:

client.list_snapshots("docs")
client.delete_snapshot("docs", snapshot_name=snapshot.name)

Three things worth knowing before you rely on them.

A snapshot lands on the node’s disk. It is not a backup until it is somewhere else. A snapshot sitting on the same disk as the data it protects is a copy, not a backup — it survives a bad deployment and not a failed disk. Download it, or point Qdrant at object storage.

Snapshots are per collection, with a full-storage variant. There is a whole-instance snapshot too, which is the right unit when you care about restoring a node rather than a collection.

Restoring creates a collection. You restore into a name, and the usual shape is to restore alongside the live one and then swap, rather than restoring over the top of something serving traffic.

There’s also a lighter-weight cousin worth knowing: you can scroll the entire collection out through the API and write it wherever you like. That’s slower and far more portable — it’s how you move data between major versions or into a different system entirely.


Aliases

An alias is a name that points at a collection. Queries can use the alias, and you can move it to a different collection atomically.

client.update_collection_aliases(change_aliases_operations=[
    models.CreateAliasOperation(
        create_alias=models.CreateAlias(collection_name="docs_v1", alias_name="docs")
    ),
])

print([(a.alias_name, a.collection_name) for a in client.get_aliases().aliases])
# [('docs', 'docs_v1')]

Your application only ever names docs. Which collection that means is an operational decision you can change without deploying anything.

This is the single most useful operational habit in this series, and it costs nothing: always query through an alias, from day one. Retrofitting it later means a deploy; having it from the start means the cutover below is available to you whenever you need it.


The zero-downtime rebuild

Now the actual procedure. Rebuild with a new model, a new dimension, new HNSW parameters — anything — with traffic flowing throughout.

Replacing a collection while it is being queried queries ask for docs docs_v1 docs_v2

Schematic. The point is the interval, not any duration: the naive rebuild has a window where the name resolves to nothing and then to something incomplete, and an incomplete collection answers rather than failing. The alias swap removes the window because both alias operations are applied as one unit.

1. Build the new collection alongside the old one.

client.create_collection(
    "docs_v2",
    vectors_config=models.VectorParams(size=1024, distance=models.Distance.COSINE),
    hnsw_config=models.HnswConfigDiff(m=32, ef_construct=256),
    optimizers_config=models.OptimizersConfigDiff(indexing_threshold=0),  # bulk load
)

Traffic is still going to docs_v1 through the alias. Nothing has changed for anyone.

2. Backfill it, re-embedding with the new model. Then restore indexing_threshold and wait for the index to build (Part 10).

3. Verify before you switch. This is the step people skip, and it’s the one that makes the whole procedure safe. Query docs_v2 directly — not through the alias — and check:

  • indexed_vectors_count == points_count, so it is actually indexed;
  • the point count matches what you expect;
  • recall and latency against your own measurements from Part 4;
  • a handful of real queries return sensible results.

A new collection that is green and empty is still green.

4. Move the alias atomically.

client.update_collection_aliases(change_aliases_operations=[
    models.DeleteAliasOperation(delete_alias=models.DeleteAlias(alias_name="docs")),
    models.CreateAliasOperation(
        create_alias=models.CreateAlias(collection_name="docs_v2", alias_name="docs")
    ),
])

Both operations are in one call, so there is no instant where docs points at nothing. That atomicity is the whole reason this works.

5. Keep the old collection. For as long as it takes to be confident. Rollback is the same call with the names reversed, and it is instant. Delete docs_v1 only when you no longer want that option.

The window in the naive plan

Back to the opening question. The naive cutover is: delete the old collection, create the new one with the same name, load it. Between the delete and the end of the load, docs either does not exist or is partially populated — and a partially populated collection is worse than a missing one, because it returns plausible, wrong, incomplete results rather than an error.

The alias swap removes the window entirely. At every instant, docs names a complete collection.


What about writes during the cutover?

The procedure above is clean for a read-mostly collection rebuilt from a source of truth. If writes arrive continuously, you need one more decision, and there is no universal answer:

  • Dual-write to both collections during the backfill, so v2 stays current. Your ingest writes twice and you deal with the new collection not existing yet at the start.
  • Backfill then catch up from a change log or a timestamp watermark, replaying anything that landed during the build.
  • Freeze writes for the cutover, if your product can tolerate it. Simplest by a wide margin, and often perfectly acceptable for a batch-updated corpus.

The thing to decide explicitly is what happens to a write that arrives between your catch-up finishing and the alias moving. Dual-writing covers it; a watermark needs the catch-up to run until the moment of the swap.


Explain it like I’m ten

Imagine a shop with a sign outside saying which door to use.

You want to redecorate. The bad plan is to close the shop, redecorate, reopen — nobody can buy anything meanwhile. The worse plan is to redecorate while people are shopping, so half the shelves are empty and customers leave with the wrong things, not knowing anything was wrong.

The good plan is to build a second shop next door while the first one keeps trading. When the new one is finished and stocked, you check it properly — walk the aisles, make sure the shelves are full — and then you move the sign. One second, and everyone starts using the new door.

You keep the old shop for a while, because if something’s wrong you move the sign back and you’re where you started.

Where the analogy breaks: a real shop costs rent twice while both exist. So does this — two collections means two sets of vectors in memory during the cutover, which has to fit. That’s the actual constraint on how often you can do it, and it’s why the memory arithmetic from Part 5 shows up here too.

The precise version

An alias is a mapping from a name to a collection, resolved per request. update_collection_aliases applies a list of operations as a single atomic unit, so a delete-then-create pair on the same alias name has no observable intermediate state: a request either resolves to the old collection or to the new one, never to neither.

This makes the swap a linearisable pointer move rather than a data migration. All the expensive and failure-prone work — embedding, upserting, indexing — happens on a collection nothing is reading, where a failure costs only that collection.

The correctness condition is the one the write path imposes. Let Tswap be the moment the alias moves. Every write acknowledged before Tswap must be present in v2 at Tswap, or it is lost from the reader’s point of view. Dual-writing establishes that by construction; a watermark-based catch-up establishes it only if the catch-up is still running at the instant of the swap.

Snapshots are a different guarantee: a consistent point-in-time copy, which is a recovery mechanism rather than an availability one. Durability of that copy depends entirely on where it is stored, and a snapshot on the node’s own disk shares the node’s failure modes.


Trade-offs

Alias swap against in-place update. The swap needs room for two collections at once and gives you instant rollback and a verification window. In-place has no rollback and cannot change dimension or metric at all.

Snapshot against scroll-and-reload. A snapshot is fast and version-coupled. Scrolling out through the API is far slower and completely portable — it is what you use across major versions or to another system.

Keeping the old collection against reclaiming memory. Rollback stays available while the old collection exists, and it occupies resources. The memory ceiling is what decides how long you keep it.

Dual-write against freeze. Dual-writing keeps the cutover invisible and complicates the ingest path. Freezing writes is trivial and needs a product that tolerates it.

Snapshot frequency. More snapshots mean less data lost in a recovery and more storage and I/O. The right frequency comes from how much re-embedding you’re prepared to redo, not from a default.


Common mistakes

Not using an alias from the start. Every procedure here assumes one. Adding it later needs a deploy, usually during the incident where you first wanted it.

Leaving snapshots on the node. That’s a copy, not a backup. It survives a mistake, not a disk.

Swapping before verifying. A new collection that is green may still be empty, partially indexed, or embedded with the wrong model. Check the counts and run real queries first.

Deleting the old collection immediately. That’s the rollback, and it costs nothing to keep for a day.

Two separate alias calls instead of one atomic operation. Delete then create as separate requests leaves a window where the alias resolves to nothing.

Forgetting writes during the backfill. Anything written to the old collection after the backfill starts is missing from the new one unless you planned for it.

Assuming a snapshot restores across any version. Snapshots are coupled to the storage format. Across a major upgrade, scroll the data out instead.


Interview questions

1. How do you change a collection’s vector dimension?

Answer You don’t — it’s fixed at creation, along with the distance metric. You create a new collection with the new dimension, backfill it by re-embedding, verify it, and move an alias. That’s the same procedure you’d use for a new embedding model or substantially different HNSW parameters, and it’s why querying through an alias from day one matters. **Follow-up:** what *can* be changed in place? HNSW parameters like `m` and `ef_construct`, optimiser settings, and quantization config — all of which trigger background rebuilding.

2. Why is the alias swap atomic, and why does that matter?

Answer `update_collection_aliases` takes a list of operations applied as one unit, so deleting the old mapping and creating the new one happen together. A request resolves either to the old collection or to the new one — never to a missing alias. It matters because the alternative is a window where `docs` names nothing or names a half-populated collection, and the second is worse: it returns plausible incomplete results instead of an error. **Follow-up:** what does that make the swap, conceptually? A pointer move. All the risky work happened on a collection nobody was reading.

3. Is a snapshot a backup?

Answer Only once it’s somewhere else. `create_snapshot` writes to the node’s own disk, so it shares that node’s failure modes — it protects you from a bad deploy or an accidental delete, not from losing the disk. Download it or configure object storage, and test a restore. An untested backup is a hypothesis. **Follow-up:** when would you scroll the data out instead? Across a major version upgrade, or when moving to another system — snapshots are coupled to the storage format, and a scroll is portable.

4. Walk through a zero-downtime embedding model change.

Answer Create `v2` with the new dimension and `indexing_threshold=0`. Backfill by re-embedding everything. Restore the threshold and let the index build. Verify against `v2` directly: indexed count equals point count, totals match, recall and latency measured, real queries sensible. Then move the alias in a single atomic call. Keep `v1` until you’re confident. The part people skip is verification, and the part they get wrong is writes arriving during the backfill. **Follow-up:** how do you handle those writes? Dual-write to both collections, or catch up from a change log and keep catching up until the moment of the swap.

5. What’s the resource constraint on this procedure?

Answer Both collections exist simultaneously, so you need room for both — memory in particular, since the new collection’s vectors and graph have to be resident enough to verify and then serve. That’s the real limit on how casually you can do rebuilds, and it’s where the arithmetic from Part 5 shows up: if one copy nearly fills the machine, two won’t fit, and you need quantization or a bigger node before this procedure is available at all. **Follow-up:** any way around it? Rebuild in a separate cluster and swap at the load balancer rather than the alias, which trades database-level atomicity for infrastructure-level.

6. After a swap, results got worse. What do you do?

Answer Move the alias back. It’s one call, it’s atomic, and the old collection is still there — which is exactly why you keep it. Then investigate on the new collection while nothing is using it: was it fully indexed, was the right model used for every batch, did the backfill actually complete, do the counts match. All of these are much easier to answer when the thing isn’t serving traffic. **Follow-up:** how would you have caught it before the swap? Query the new collection directly with a fixed relevance query set and compare against the old one — the same comparison, run before rather than after.

7. What happens to a point written during the backfill?

Answer It goes to whichever collection your ingest names. If ingest writes to the old collection, it’s missing from the new one, and after the swap it has silently disappeared from search. The three options are dual-writing during the transition, catching up from a change log until the moment of the swap, or freezing writes for the cutover. The decision to make explicitly is what covers a write landing between catch-up finishing and the alias moving. **Follow-up:** which would you pick for a nightly-batch corpus? Freeze — the window is a few seconds and nothing is writing anyway. Complexity you don’t need is complexity that breaks.

8. How would you test that your backups work?

Answer Restore one, into a new collection name, and run your query set against it. Compare counts and results against the live collection. Do it on a schedule, not once. The failure modes are quiet: snapshots that stopped being taken, storage that filled, a retention policy deleting the only copy, a format change after an upgrade. None of those announce themselves, and all of them surface during the restore you actually needed. **Follow-up:** what would you monitor? That a snapshot was created recently, that it was copied off the node, and its size — a sudden drop in size is a collection that lost data.

Sources

What to remember

Query through an alias from the first day. It costs nothing and it is what makes every operational procedure in this part possible.

To replace a collection — new model, new dimension, new parameters — build the new one alongside, verify it properly, then move the alias in one atomic call, and keep the old one until you’re sure. The expensive work happens where nobody is looking, and the cutover is a pointer move.

And a snapshot on the node’s own disk is a copy, not a backup. It is a backup when it is somewhere else and you have restored from it at least once.

The alias swap turns a risky migration into a pointer move. Everything that can go wrong happens on a collection nobody is reading.

How useful was this post?

Click on a heart to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.