Use a zero‑downtime alias swap combined with incremental upserts to a shadow collection, then point the read alias to the new index.
1. Create a shadow collection with the same schema but a new name (e.g., my-index-v2).
2. Batch‑upsert new embeddings to the shadow using the same upsert API; keep the original index serving reads.
3. Validate the shadow by running a few similarity queries and checking recall against a held‑out set (target >0.95@10).
4. Swap the alias (or update the routing table) so that the logical name my-index now points to my-index-v2.
5. Retire the old collection after a grace period; optionally keep it for rollback.
| Strategy | Latency impact | Write cost | Operational complexity |
|---|---|---|---|
| Alias swap (shadow + alias) | none (instant) | low (single upsert pass) | medium |
| Rolling reindex (shard‑by‑shard) | slight (partial reads) | high (duplicate writes) | high |
| Hybrid shard‑merge | moderate (re‑balancing) | medium | high |
Pinecone example
import pinecone
pinecone.init(api_key="YOUR_KEY", environment="us-west1-gcp")
# create shadow index
pinecone.create_index("my-index-v2", dimension=768, metric="cosine")
shadow = pinecone.Index("my-index-v2")
for batch in get_new_embeddings():
shadow.upsert(vectors=batch)
# atomic alias swap
pinecone.update_index("my-index", new_index_name="my-index-v2")Qdrant example
from qdrant_client import QdrantClient
client = QdrantClient(url="http://localhost:6333")
client.recreate_collection("products_v2", vectors_config={"size":768,"distance":"Cosine"})
client.upload_collection("products_v2", vectors=embeds, payload=metadata)
client.update_alias("products", "products_v2")