- 1Detect the change by document id or content hash
- 2Re-chunk and re-embed the new version of the document
- 3Delete the old chunks and vectors for that document id
- 4Refresh any derived caches (semantic cache, summaries)
- 5Upsert the new vectors into the index
Detect the change, delete the old chunks for that doc id, re-chunk and re-embed, upsert the new vectors, then refresh derived caches — delete before write so stale and fresh copies never coexist.
Imagine updating one recipe card in a recipe box. First you notice the card changed. Then you pull out the old card so nobody grabs the outdated version by mistake. You write the new card, slot it back in, and finally tear up any sticky notes that summarized the old recipe. If you slipped the new card in before pulling the old one, the box would hold two versions of the same recipe and a hungry cook might grab the wrong one. That is why the order matters: remove the stale copy first, add the fresh copy, and clean up the notes last.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Most RAG demos load a corpus once and never touch it again. Production indexes are the opposite: documents get edited, deleted, and added continuously, and rebuilding the whole index for every change is wasteful and slow. Incremental indexing is the discipline of updating only what changed — and the order of operations is where correctness lives or dies.
The trap is that a vector index is a shared, concurrently-read data structure. While you are halfway through swapping a document, real queries are hitting the index. If your update order ever lets the old and new versions of a document coexist, retrieval can ground an answer in text the user already corrected. If it ever lets the document vanish, retrieval silently loses a source.
This deep dive walks the five steps in order, explains the constraint behind each transition, and then pushes into the consistency and deletion edges — the parts that separate someone who has shipped an ingestion pipeline from someone who has only read about one.
Why detection gates everything
The pipeline starts by detecting that a document actually changed, and it starts there for a cost reason. Embedding is the expensive step — every chunk is a model call, and a large document can be dozens of chunks. If your sync job re-embeds every document it sees, you pay that cost on every cycle even when nothing changed, which at scale is the difference between a cheap nightly job and a runaway bill.
The standard mechanism is a content hash stored per document id. On each sync you recompute the hash of the incoming document and compare it to the stored one. Equal hash, skip the document entirely. Different hash, proceed. This also makes the pipeline idempotent: replaying the same unchanged input is a guaranteed no-op, which matters when an upstream connector redelivers records.
A timestamp is a weaker gate. Files get re-saved, metadata gets touched, and clocks drift across sources, all of which can bump a modified time without changing a single byte that gets chunked. A hash keyed on the chunkable content fires only on real change. The practical rule: detect on what you embed, not on when the file was written.
Situations where this technique stops working.
2–4 min · Everything important, quickly.
| Step | Why it is here | What breaks if skipped or moved |
|---|---|---|
| Detect change (hash/id) | Gate the costly re-embed | Re-embed everything every sync; wasted cost |
| Delete old chunks | Clean replace before write | Stale and fresh copies coexist in search |
| Re-chunk and re-embed | Produce current vectors | Index never reflects the new content |
| Upsert new vectors | Make new version searchable | Document missing from results |
| Refresh derived caches | Rebuild against fresh vectors | Cache keeps serving stale answers |
Real products, models, and research that use this idea.
- LlamaIndex's IngestionPipeline with a docstore uses upserts keyed on a document hash, skipping unchanged docs and replacing changed ones — the detect then replace pattern in code.
- Pinecone and Weaviate expose delete by metadata filter (delete all vectors with a given document id) precisely so an edited source can be cleanly removed before re-upsert.
What an interviewer would ask next. Try answering before peeking at the approach.
QHow do you avoid a window where the edited document is missing from search under concurrent reads?
Either wrap the delete and upsert in a per-document transaction if the store supports it, or write the new chunks under a new version tag and atomically flip a pointer, deleting the old version afterward. The goal is that any reader sees exactly one version — never zero, never two. Discuss what your specific vector store guarantees.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Upserting new chunks before deleting the old ones. For a brief window the index holds both versions, so retrieval can surface stale and current text for the same document and the model may answer from the outdated copy.
60 second bullets to scan on the way to the call.
State the five steps of an incremental update in order from memory.
Explain why change detection comes before any re-embedding work.
Primary sources. Browse if you want the original framing.
Same topic, related formats. Practice these next.