What you’ll take away
- One source can produce several indexed children; writing its new output does not itself remove omitted old keys.
- An idempotent update can repeatedly preserve an incomplete replacement.
- Compare desired children with records owned by the same parent, and preserve unrelated parents.
- Absence from a partial update is not an authoritative whole-corpus deletion signal.
- A completed write and a search-visible change are different observation stages.
- Verify exact membership, values and visibility without treating freshness as proof of source truth or answer accuracy.
The new document is present, but an old piece is still searchable
A knowledge page changes from “East Hall” to “West Hall.” The new text reaches the search index, yet a retrieval still returns the old location as well. The update did not necessarily fail to write the new text. It may have failed to remove a search record that the new document no longer needs. This distinction becomes important when one source produces several separately stored chunks.
Microsoft describes an indexer as a component that reads a source and populates a search index; optional transformations can split content and generate vectors [1]. A source file and the records derived from it therefore need not have a one-to-one relationship. Saying that a file was updated leaves the state of its former child records unanswered.
We will follow an original two-document example through replacement, cleanup and search visibility. Its retrieval rule only looks for two literal words. No embedding model, language model or search product was run. That small boundary lets us inspect exactly how obsolete text survives without guessing whether a model invented the old answer.
A successful write of a replacement chunk does not establish equality between the intended child set and the indexed child set. In a one-to-many ingestion path, a parent revision may produce fewer child records than its predecessor. An operation that writes only the new keys can leave omitted old keys untouched. Those residual records can remain retrieval candidates despite the presence of the correct new content.
Azure AI Search documents distinct source, indexer and destination-index roles, with optional content transformations such as chunking and vectorization [1]. Those roles motivate the problem but do not specify the behavior of our deliberately incomplete updater. The example is not an observed Azure or framework defect.
We use deterministic line splitting, explicit parent and revision fields, dictionary writes and a literal-token query. A separately copied search view makes intermediate states inspectable. These operations establish a counterexample to “new chunk written implies parent replacement complete”; they do not measure vector recall, model answer quality, replication behavior or production freshness latency.
Keep the source identity and its child identities separate
Call our first source document K and the unrelated second document J. A parent identity tells us which source a record belongs to. A child identity distinguishes the separate pieces produced from that source. In this example, K:0 and K:1 mean K's first and second nonempty lines. J:0 belongs to J. These are teaching labels, not a recommendation for every indexing system.
Each child also records its source revision, the exact source-text hash and the name of the splitting rule. Those fields answer different questions. The child name says which stored entry a write addresses. The parent name says which group a replacement owns. The revision and text tell us whether that entry represents the intended input. A reused child name can hold new text while a different child keeps old text.
Our splitter is intentionally simple: one nonempty line becomes one child. The article does not compare splitting quality. Keeping the rule fixed allows us to examine record replacement without also changing chunk length, overlap or an embedding model.
Let parent identifiers be K and J, and child identifiers be parent-prefixed positions under the declared nonempty-lines-v1 recipe. A record stores id, parentId, sourceRevision, sourceSha256, recipeId and text. Parent ownership is evaluated from parentId, not inferred from an arbitrary filename or from the number of rows returned by a batch.
The positional child identity is stable only within the assumptions of this fixture. Inserting or removing lines can alter positional correspondence; no general identity strategy is established. The source hash records the exact UTF-8 source bytes used here. It is not a factuality certificate, and it does not independently verify that a stored record was actually produced by the declared recipe.
This separation gives us an explicit replacement target: all children owned by K must match the declared output for K's new revision, while records owned by J remain unchanged. A check of one addressed key is weaker than that parent-level invariant. It can pass even when the complete owned set still contains a superseded entry.
The replacement turns two old chunks into one new chunk
K at revision r1 contains two lines. The first says, “The loan kiosk is in East Hall.” The second says, “Map returns use the loan kiosk in East Hall.” They produce K:0 and K:1. J's one line says, “The reading room opens at nine,” producing J:0. All places and statements in this exercise are fictional.
Replace K with revision r2: “The loan kiosk and map returns are in West Hall.” It contains one nonempty line, so its entire desired child set is now just K:0. There should no longer be a K:1 under this recipe. J has not changed. The desired complete index contains the new K:0 and the original J:0.
For a visible query, require both words “map” and “returns,” ignoring capitalization. Initially only K:1 matches. The new K:0 should match after a complete replacement becomes visible. This rule has no ranking or top-result cutoff, so every matching record remains available for inspection.
The initial source map yields three records: K:0 at r1 with the first East Hall sentence, K:1 at r1 with the map-return sentence, and J:0 at r1 with the unrelated reading-room sentence. K's r2 input is the single West Hall sentence. Applying the unchanged line recipe produces desired child set D_K={K:0}.
The expected replacement state is therefore {K:0 at r2, J:0 at r1}. It is not obtained merely by asserting that K:0 exists. The assertion must also exclude obsolete K-owned records and preserve the unrelated J record. The old and new source hashes differ because their stipulated source texts differ.
Define a query that case-folds text, extracts lowercase ASCII word tokens and selects records whose token set contains both map and returns. Results are ordered by record identifier, with no score, embedding or top-k truncation. Under the original view the result is K:1; under the intended final view it is K:0. This deliberately transparent selector reveals membership errors without introducing approximate retrieval uncertainty.
Writing the desired key leaves the omitted key untouched
An upsert in our toy updater means “replace this key if it exists, or add it if it does not.” Submit the new K:0. That entry now contains revision r2 and West Hall. But the operation says nothing about K:1, so K:1 still contains revision r1 and East Hall. J:0 also remains, as it should.
Once this mixed state is made visible to the query, both K:0 and K:1 match “map returns.” One points to West Hall; the other still points to East Hall. A check that searches only for the new sentence would succeed. A check that inspects all matching child identities would reveal the leftover record.
Nothing in this result requires the search system to remember a deleted sentence mysteriously. The sentence is still in an ordinary indexed record. Before blaming answer generation, inspect the actual candidates and their parent revisions. In this constructed case, the inconsistency is already present before any answer is written.
Define the toy upsert U_D as a dictionary update for each key in desired mapping D. It overwrites matching keys and inserts absent ones, but leaves every key outside D unchanged. Starting from the initial index, applying U_D_K yields K:0 at r2, K:1 at r1 and J:0 at r1.
The resulting K-owned key set is {K:0,K:1}, which is a strict superset of D_K. After that state becomes search-visible, the literal query returns both records. The first carries the replacement sentence; the second carries obsolete content. Presence of the new revision and absence of the old revision are independent assertions here.
The failure is specifically a missing membership-removal operation. It is not evidence that an embedding failed to update or that a model hallucinated a location. We do not invoke either component. Inspecting record identifiers, ownership, revisions and exact text is sufficient to locate the inconsistency in this small example.
An operation can be repeatable and still leave the wrong set
Run the same upsert again. K:0 receives the same new value, while K:1 remains untouched. Run it five times and the result is still the same mixed set. Repetition has not supplied an instruction to remove the obsolete child.
This illustrates a useful distinction in the word “idempotent.” An operation is idempotent when repeating it does not keep changing its result. That property can help make a retry predictable. It does not prove that the result satisfies every requirement of a larger task. Our incomplete update is repeatable precisely because it keeps writing the same one key and keeps ignoring the same other key.
The right completion question is therefore larger than “did the retry return successfully?” Ask whether K has exactly the children its current source should produce. A successful repeat can confirm that one write is stable while leaving the parent replacement incomplete. Increasing retry counts cannot repair an operation whose defined scope omits the necessary removal.
For this fixed desired mapping, U_D(U_D(W))=U_D(W). The original script checks five repeated applications after the first upsert and obtains byte-equivalent record values. The key K:1 survives every application because K:1∉D and U_D does not remove keys outside its input.
Idempotence is an algebraic property of an operation, whereas correctness is relative to a stated postcondition. Our postcondition requires owned_K(W)=D_K and matching records for the desired revision. The operation satisfies the idempotence equation but violates the owned-set equality. Neither claim contradicts the other.
A retry strategy therefore needs the right unit of completion. Repeating a child write is not equivalent to retrying a parent reconciliation that includes obsolete-child handling. The example assumes sequential operations and no failures during individual dictionary changes. It establishes the logical distinction without proposing a crash-safe transaction design or claiming that any named service implements this incomplete algorithm.
Compare the old and desired children within the same parent
Before cleanup, the indexed children owned by K are K:0 and K:1. The desired children for K are just K:0. The old-owned set minus the desired set is therefore K:1. Removing that one obsolete child leaves the new K:0 in place and leaves J:0 untouched.
The ownership boundary is doing essential work. We are replacing K, not rebuilding every source in the collection. A record can be absent from K's output because it belongs to J, not because it is obsolete. Keeping the parent identity on each child makes that distinction explicit in the example.
After this reconciliation, check more than the number two. The complete write view should contain K:0 at r2 and J:0 at r1, with their expected texts. A different pair of records could have the same count. Exact membership, revision and content checks explain what the count represents and expose errors that a total alone would hide.
Let C_K={k: W[k].parentId=K} and let D_K be the identifiers produced from the complete replacement source for K. Obsolete children are O_K=C_K∖D_K. Here C_K={K:0,K:1}, D_K={K:0} and O_K={K:1}. After writing desired records and removing O_K, the owned-key invariant holds.
An additional preservation invariant requires every non-K record to retain its previous value. The independent example checks J:0 against its pre-operation record, including text and provenance fields. This prevents an apparently correct K replacement from concealing an unrelated mutation elsewhere in the collection.
Set equality is necessary but not sufficient for this declared record model. Every desired key must also contain the expected revision, recipe identifier and text. A correct key with stale content would pass membership equality while failing replacement. The final check therefore binds identities to values rather than treating a row count or the mere existence of K:0 as a complete outcome.
Absence from a partial batch is not a whole-corpus deletion signal
Now imagine passing only K's replacement records into a batch. That batch contains K:0. If a cleanup wrongly compares it with every indexed key, it sees J:0 missing and may decide to remove J. But J was never part of this update. Its absence says nothing about whether its source still exists.
In our reconciled write view, the whole-index difference against the K-only batch is exactly J:0. The correctly scoped difference for K is empty, because K already matches its desired set. The same input batch therefore produces two very different interpretations depending on which population it claims to describe.
A replacement operation should make that scope explicit: one named parent, a complete named subset, or an authoritative inventory of the whole source collection. Those are different contracts. Our example supplies a complete replacement for K and no deletion instruction for J. It cannot justify a collection-wide cleanup merely because the batch finished without an error.
After reconciliation, W has keys {K:0,J:0}, while the partial update's desired identifiers are {K:0}. Computing keys(W)∖D_K gives {J:0}; computing owned_K(W)∖D_K gives ∅. The first difference answers a whole-index inventory question using a parent-scoped input, so its deletion interpretation is invalid.
The distinction is between an omitted member and a confirmed absent source within an authoritative scope. An incremental batch does not establish a complete inventory by default. Our model explicitly declares K as its covered parent, allowing J's omission to remain neutral. No timestamp, successful batch status or unchanged record count repairs a missing scope declaration.
For a complete inventory, the coverage and observation boundary would need separate definition. This article does not implement distributed enumeration, concurrent updates or a snapshot protocol. The small set calculation demonstrates why a parent-specific desired set cannot be silently promoted into a statement about every parent in the repository.
Correct stored records may not yet be the records a query sees
Our example keeps two views: the records receiving writes and the records currently visible to its query. We copy the write view into the visible view only at named steps. This is a teaching device, not a claim that a real search engine copies a whole dictionary whenever it updates.
After K:1 is removed from the write view, the older visible view still contains both K records. The query continues to return both locations until the next visibility step. At that step it returns only K:0 at r2. Thus a correct cleanup and an observed search result can refer to different completed stages.
Elasticsearch documents refresh as the process that exposes indexed changes to search. Its request options distinguish making changes visible from simply returning after a write; wait_for waits for visibility and has a documented listener-limit case that can force a refresh [3][4]. These are search-index semantics. They do not fetch a newly edited source file or remove an omitted child that no operation addressed.
Define W as the write-state map and V as the search-visible map. A declared pedagogical visibility step assigns V=deepcopy(W). After scoped removal, W contains only the correct K:0 and unchanged J:0, while V can still contain the earlier mixed state. The literal query runs against V, not W.
Elastic's documentation separates indexing from search visibility through refresh [3]. The refresh parameter governs visibility of the changes made by the particular write request; wait_for normally waits, with a specified listener-saturation exception that can force refresh [4]. The article neither assumes a universal refresh interval nor recommends a production setting.
The original copy operation is not a model of Lucene segments, distributed atomicity or replica consistency. It only exposes a necessary observation boundary. Search verification must follow the relevant visibility event, and that event cannot compensate for an incomplete desired-key set or a source revision that was never ingested. Different stages require different evidence.
A cache hit and a document-management record answer different questions
Ingestion systems can keep several kinds of bookkeeping. LlamaIndex documents caching a node together with its transformation. It separately describes document management enabled by a document store, using document identifiers and hashes; the documented changed-hash reprocessing behavior depends on the attached vector-store setup [5]. Those descriptions should be read with their configuration conditions.
For our example, the source hash tells us which exact version of K was supplied, while the recipe name tells us how its children were produced. A cached transformation result could help avoid repeating the same work. It would not, by itself, state which old child identifiers still belong in the destination collection. That requires the replacement membership question we have made explicit.
Do not read the toy upsert failure as a finding about LlamaIndex's implementation. We have not run its pipeline or tested its deletion behavior. The useful comparison is between evidence roles: identifying an input, reusing a transformation and checking destination membership are related tasks with different completion conditions.
The retained LlamaIndex documentation describes a node-plus-transformation cache and a separate document-ID/hash management path. With a docstore and the documented vector-store configuration, changed hashes trigger reprocessing/upsert and unchanged hashes can be skipped; without the vector store, the stated management scope differs [5]. Preserving that condition avoids treating a partial excerpt as universal behavior.
In the original fixture, sourceSha256 and recipeId are provenance fields on each derived record. Equality of an input identifier or a cache key does not imply the parent-level destination invariant owned_K(W)=D_K. The latter concerns the entire current child population, including omitted keys. It cannot be established by inspecting only a newly produced node.
This is a conceptual separation, not an implementation diagnosis. No LlamaIndex code, embedding adapter or vector store is executed, and the article makes no claim about that framework leaving stale children. A real verification record should state which component established each invariant instead of combining several different bookkeeping results into one unqualified “updated” flag.
Removing the source parent is another operation to account for
After a correct replacement becomes visible, remove K from our source map. The indexed K:0 still exists because changing that map does not automatically change either indexed view. Only an explicit operation on K's owned children removes it from the write view; the following visibility step removes it from our query results. J remains throughout.
Microsoft's Azure Storage indexing guidance distinguishes change detection from deletion detection. It also explicitly says that the native and custom-metadata soft-delete approaches discussed on that page do not apply to one-to-many indexing [2]. That limitation matters here: one source producing several search records cannot inherit an automatic cleanup promise from a different documented case.
Our local deletion sequence proves only that K is absent from the declared final search view. It does not prove that every historical copy, log or retained source has been erased. Keep the conclusion tied to the operation and view actually inspected. A source disappearance and a searchable-record disappearance are separate observations.
Deleting parent K from the source dictionary leaves W and V unchanged by construction. A targeted parent-removal event subsequently removes all records whose parentId equals K from W. After the separate visibility step, V contains only J:0 and the literal map/returns query is empty.
Azure's cited storage-indexing page treats deletion detection separately from change detection and explicitly excludes one-to-many scenarios from its native/custom-metadata soft-delete approaches [2]. We retain that exclusion rather than presenting those policies as a general child-reconciliation mechanism. No Azure policy, deletion request or real storage operation is performed.
The target event in this fixture is explicit; it is not inferred from K's omission from an arbitrary partial batch. The final assertion concerns the chosen index view and its records. It makes no retention, privacy-erasure or audit-log claim. Those require their own defined scope and evidence beyond the current retrieval result.
The final set does not describe every intermediate state
There are two simple ways to order our local replacement. Remove K's old children first, then write the new child, and an observer between those steps can find no K result. Write the new child first, then remove the obsolete one, and an observer between those steps can find both locations. Both orders can finish with the same correct pair: new K:0 and unchanged J:0.
The difference concerns what becomes visible during the transition. A final-state check alone cannot tell you whether a temporary gap or mixed revision was observable earlier. Our script records both intermediate states explicitly, without claiming that a particular hosted service exposes them in this way.
If a product requires a stronger replacement guarantee, its actual publication, transaction or version-selection mechanism needs to be assessed. A sequence of correct dictionary operations is not evidence of a distributed atomic replacement. The example helps define the required observation before selecting a mechanism that promises to provide it.
Starting from the original index, delete-first removes both K-owned records and temporarily leaves {J:0}; the query has no matches. Inserting the desired K:0 then reaches the intended final map. Upsert-first temporarily retains K:1 alongside the new K:0, producing two matches, before obsolete-key removal reaches that same final map.
The independently inspectable states distinguish endpoint correctness from transition visibility. Both sequential algorithms satisfy the final membership/value invariants in this fixture, but their intermediate candidate sets differ. A visibility rule could hide or expose a transition; the write order alone does not establish what external readers observe.
No concurrent writer, failure recovery, lock protocol or transaction boundary is modeled. Consequently the example does not recommend either order as a production solution. Its contribution is a concrete requirement question: must readers observe only complete parent revisions, or is a documented intermediate state acceptable? That question must be answered by the actual system contract, not by the existence of a final successful write.
Verify the complete child set and the search view separately
A useful completion record for this example is specific. K's current source is r2; the declared recipe produces only K:0; that stored child contains the West Hall sentence; K:1 is absent; and J:0 is unchanged. After the named visibility step, the literal query returns exactly the new K:0. Each statement answers a question that “update succeeded” leaves open.
The later parent-deletion record is different: K is explicitly removed from the covered source set, its indexed children are removed, and the visible query has no K result while J remains. Keeping those outcomes separate avoids treating one successful source operation as proof that every downstream representation followed it.
This is a freshness and membership explanation, not a complete RAG evaluation. Correctly replacing records does not establish that the source is true, that a semantic retriever will choose the best evidence, or that an answer will use it correctly. It gives the next stage a clearly defined collection whose old and new records can be accounted for.
Bind a replacement result to parent identity, source revision/hash, recipe identity, complete desired child identifiers, exact stored values, obsolete-child absence and preservation of non-owned records. Then bind a retrieval observation to the declared visible state and query definition. In this fixture those checks identify {K:0 at r2,J:0 at r1}, followed by a single K:0 query result.
The parent-removal case has a separate authoritative event and postcondition: no K-owned record in the final visible view, with J:0 preserved. A query returning no results is insufficient on its own if the query could miss an existing child; our direct membership inspection supplies that additional evidence for the bounded example.
These checks establish only the stipulated ingestion and visibility properties. They do not establish source truth, embedding correctness, recall under a different query, authorization enforcement or generated-answer support. Separating those evaluation boundaries makes a stale-record incident easier to investigate without turning one clean result into an unsupported end-to-end guarantee.
Sources & further reading
Explore the references behind this guide. External pages may change after our review.
- Indexer Overview - Azure AI Search | Microsoft LearnMicrosoft Learn · Accessed 7 Nov 2026
- Changed and Deleted Blobs - Azure AI Search | Microsoft LearnMicrosoft Learn · Accessed 7 Nov 2026
- Near real-time searchElastic · Accessed 7 Nov 2026
- The refresh parameterElastic · Accessed 7 Nov 2026
- Ingestion Pipeline | Developer DocumentationLlamaIndex · Accessed 7 Nov 2026
Article updated 7 Nov 2026.
Found something we should revisit?
Good explanations get better with thoughtful feedback.