Design an idempotent embedding-indexing pipeline that survives partial failures
You're building the indexing pipeline that ingests documents, chunks them, embeds the chunks, and upserts to a vector DB. The pipeline must be idempotent: re-running the same input must not produce duplicates, must handle partial-failure retries cleanly, and must support model upgrades without leaking stale vectors. Outline the design.
Content-keyed IDs at every stage (sha256-derived chunk_id, model_version-namespaced upserts) make retries no-ops and model upgrades non-destructive.
Picture a librarian copying books onto shelves. If she labels each copy with a random sticker, she cannot tell which copies are duplicates. If a book is dropped halfway and she restarts, she might end up with two copies of the same chapter on different shelves. The fix is to label every chapter with a tag derived from the book title and the chapter number. Now if she restarts, she just writes the same chapter into the same labeled slot. Nothing duplicates because the slot already exists. When the librarian gets a new translation of the same books, she does not throw out the old translation. She builds a parallel shelf labeled with the new translation, lets readers pick which version they want, and only removes the old shelf once everyone has switched. Indexing pipelines work the same way.
Detailed answer & concept explanation~7 min readEverything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example.
Everything important, quickly.
60s: content-keyed chunk_id at chunking stage; embedding cache keyed by (chunk_hash, model_id, model_version); vector primary key (chunk_id, model_version) for non-destructive upgrades; per-doc state table; dead-letter queue for permanent failures; GC for orphaned chunks and old model_versions.
import hashlib
CHUNKER_VERSION = "semantic-v3"
MODEL_ID = "voyage-3-large"
MODEL_VERSION = "2026-04"
def chunk_id(doc_id: str, chunk_position: int) -> str:
raw = f"{doc_id}|{chunk_position}|{CHUNKER_VERSION}"
return hashlib.sha256(raw.encode()).hexdigest()
def cache_key(chunk_text: str) -> str:
text_hash = hashlib.sha256(chunk_text.encode()).hexdigest()
return f"{text_hash}|{MODEL_ID}|{MODEL_VERSION}"
def upsert_vector(vector_db, doc_id, chunk_position, vector, chunk_text):
cid = chunk_id(doc_id, chunk_position)
# Composite key is the idempotency primitive
vector_db.upsert(
id=f"{cid}|{MODEL_VERSION}",
vector=vector,
metadata={
"doc_id": doc_id,
"chunk_id": cid,
"chunk_position": chunk_position,
"chunker_version": CHUNKER_VERSION,
"model_id": MODEL_ID,
"model_version": MODEL_VERSION,
},
)Real products, models, and research that use this idea.
- Pinecone's reference architecture for production RAG includes a `model_version` namespace in metadata and recommends content-keyed chunk IDs for safe rollback.
- Weaviate's multi-tenant pattern uses one tenant per (corpus, model_version), which gives the same isolation as composite-key upserts and makes migration a tenant-level operation.
- Anthropic's contextual retrieval guide explicitly describes content-keyed chunk IDs and per-version cache namespaces as the recommended pattern for safe re-indexing.
- Qdrant's collection per model version pattern is the documented 2026 best practice for embedding pipelines that need rollback semantics across model upgrades.
What an interviewer would ask next. Try answering before peeking at the approach.
QHow would you handle a chunker change that produces a different chunk count for the same document?
QWhat's the failure mode if you forget model_version in the upsert key?
Don't say thisRed flags and common mistakes that signal junior thinking. Click to expand.
Red flags and common mistakes that signal junior thinking. Click to expand.
Using random UUIDs as chunk_ids. Retry now writes a duplicate vector under a new id, and the DB has no way to tell the two are the same chunk.
The night-before-the-interview bullets. Scan these on the way to the call.
Primary sources. Skim if you want the original framing.
Same topic, related formats. Practice these next.