Short answer: Detect RAG index drift by reconciling source records, chunk hashes, deletion markers, embedding versions, and retrieval results against the source of truth. A production RAG system should prove that deleted content no longer retrieves, updated content replaces stale chunks, duplicate embeddings are suppressed, and freshness filters are tested before users find stale citations.

The boring production RAG failures are usually the expensive ones. A document is updated, but the old chunks still rank. A source row is deleted, but its embedding remains active. A re-ingestion job runs twice, creates near-duplicates, and retrieval starts returning conflicting evidence.

That is RAG index drift. It is not a model problem first. It is a lifecycle problem across source data, chunks, embeddings, metadata, indexes, and evaluation.

Key takeaways:

  • Treat the source of truth as authoritative, not the vector index.
  • Track chunk lifecycle state explicitly: current, superseded, deleted, failed, or quarantined.
  • Test deletion and duplicate handling as production behaviours, not cleanup chores.

What is RAG index drift?

Short answer: RAG index drift happens when the retrieval index no longer matches the authoritative source. The source changed, but chunks, embeddings, metadata, or retrieval filters did not change with it. The result is stale evidence, orphan embeddings, duplicate chunks, wrong citations, and answers grounded in content that should no longer be active.

A RAG system has at least two views of the world:

LayerWhat it believes
Source systemThe current document, row, policy, ticket, or file state
Retrieval systemThe chunks, embeddings, metadata, and indexes available to search

Drift appears when those views diverge. It can happen after a failed ingestion job, a partial batch update, a source-system delete, an embedding-model migration, a parser change, or a re-ingestion run that does not enforce idempotency.

Key takeaways:

  • Drift is a source-to-index consistency problem.
  • Vector search can faithfully retrieve content that should no longer exist.
  • Freshness metadata is only useful if retrieval filters enforce it.

Why do deleted documents still show up in retrieval?

Short answer: Deleted documents still show up when deletion is handled in the source system but not propagated to chunk rows, embedding rows, metadata filters, and search indexes. A delete event must either remove derived retrieval records or mark them inactive before they can appear in top-k results.

“We deleted it from the database” and “it no longer shows up in retrieval” are different claims. RAG creates derived artifacts: chunks, embeddings, summaries, cached answers, and sometimes reranker inputs. If only the source row is deleted, derived records can continue to rank.

Use explicit deletion semantics:

Delete patternRiskSafer production behaviour
Hard delete source onlyOrphan chunks remain searchableCascade or reconcile derived records
Soft delete sourceRetriever ignores source stateAdd mandatory is_current and is_deleted filters
Re-ingest after deleteDeleted content returns as a new chunkUse source IDs and tombstones
Cache survives deleteBot cites removed contentInvalidate answer and retrieval caches

Key takeaways:

  • Deletion must propagate to every derived retrieval artifact.
  • Tombstones help prevent deleted content from returning during re-ingestion.
  • The retrieval query should exclude deleted and superseded content by default.

How do I detect stale chunks, orphan embeddings, and when to refresh RAG embeddings?

Short answer: Compare the retrieval tables with the source of truth on a schedule. Check source IDs, source modified timestamps, chunk hashes, current-version flags, deletion markers, embedding model IDs, and index participation. Then run challenge queries that verify stale and deleted evidence cannot appear in top-k results.

A reconciliation job should answer simple questions:

  • Does every active chunk point to an active source?
  • Does every active source have the expected current chunks?
  • Did the source text change without the chunk hash changing?
  • Did the chunk text change without a new embedding?
  • Are deleted or superseded chunks still searchable?
  • Are chunks embedded with an old model mixed into the current retrieval path?

The existing RAG evaluation notebook pattern is useful here. Reuse the separation between retrieval methods, challenge questions, exported metrics, and a run manifest. Extend the challenge set with deletion, stale-version, duplicate, and orphan-vector cases.

Key takeaways:

  • Reconciliation detects corpus integrity problems.
  • Retrieval evaluation detects whether those problems affect user-visible answers.
  • Keep a run manifest so drift checks are repeatable and debuggable.

How should I handle re-ingestion duplicates?

Short answer: Make ingestion idempotent. Use stable source IDs, chunk ordinals, canonical text hashes, parser-version metadata, embedding-model metadata, and uniqueness rules. Near-duplicate chunks should be merged, superseded, or quarantined before they compete in retrieval.

Duplicates are not harmless. Two near-identical chunks can both rank, crowd out better evidence, or disagree because one is stale. The user sees this as confused citations or answers that mix versions.

Use duplicate controls:

Duplicate typeDetection signalAction
Same source, same chunk hashExact duplicateSkip insert or update existing row
Same source, new chunk hashSource changedSupersede old chunk and embed new chunk
Different source, near-identical textPossible copied contentKeep both only if provenance differs meaningfully
Same text, different embedding modelModel migration artifactRoute by active embedding model version
Same source, different parser versionParser migration artifactcompare retrieval quality before promoting

Key takeaways:

  • Idempotent ingestion is a production requirement.
  • Chunk hashes prevent unnecessary re-embedding.
  • Near-duplicates need policy because they can degrade retrieval quality.

What should a RAG reconciliation job check?

Short answer: A RAG reconciliation job should compare source state, chunk state, embedding state, and retrieval behaviour. It should produce counts, examples, and failed challenge queries rather than only saying the job succeeded.

Use a reconciliation report like this:

CheckWhat it catchesExample failure
Active chunk has missing sourceOrphan embeddingSource document deleted but chunk still active
Source updated after chunk hashStale chunkPolicy changed but old text remains searchable
Deleted source returned in top-kDelete propagation failureBot can cite removed document
Duplicate active chunks by source and hashNon-idempotent ingestionBatch job inserted the same chunk twice
Near-duplicate active chunksRe-ingestion or parser driftOld and new versions compete in retrieval
Embedding model mismatchPartial migrationOld vectors mixed with current vectors
Current-version flag mismatchBad lifecycle stateSuperseded chunk marked current

The report should be actionable. Include source IDs, chunk IDs, timestamps, hashes, model versions, retrieval route, and sample queries that expose the issue.

Key takeaways:

  • Reconciliation needs both data checks and retrieval checks.
  • Counts are not enough; include examples engineers can inspect.
  • Failed reconciliation should block promotion of a new ingestion run.

How do I measure whether drift is affecting answers?

Short answer: Add drift cases to the same evaluation harness used for retrieval quality. Test whether deleted documents are absent, current versions beat stale versions, duplicate chunks do not crowd out better evidence, and generated answers cite only active evidence. Track retrieval metrics and answer-quality checks separately.

Start with a small drift challenge set:

  • Ask for a document that was deleted and should not be cited.
  • Ask a question where old and new versions both exist; only the current one should rank.
  • Ask an exact-ID query after source deletion; retrieval should return no active evidence.
  • Ask a question affected by duplicate chunks; top-k should not be crowded by copies.
  • Ask after an embedding-model migration; results should come from the active model path.

For retrieval, measure whether forbidden evidence appears in top-k. For answers, measure groundedness, citation validity, and abstention quality. If retrieval returns deleted content, the answer generator is already in a bad position.

Key takeaways:

  • Drift tests belong in the production evaluation set.
  • Forbidden evidence is as important as required evidence.
  • Do not publish freshness claims without traceable evaluation results.

How do I keep generated code from using stale database patterns?

Short answer: Treat AI-generated implementation code as another drift surface. Coding assistants can produce outdated connection-pooling or driver patterns when they are not grounded in current documentation. For database-backed RAG, verify generated code against the current driver docs, pin package versions, and add tests for pool creation, acquisition, release, health, and shutdown.

This matters because production RAG is not only retrieval logic. It is also connection handling, pooling, timeouts, retries, lifecycle management, and observability. An assistant can generate code that looks plausible but reflects an older driver style or misses the current recommended pooling API.

Use a documentation-grounded review loop:

Code areaDrift riskReview check
Connection pool creationDeprecated or outdated API shapeMatch current driver docs
Pool sizingToo many sessions or no backpressureSet min, max, increment, and queue behaviour deliberately
Connection acquisitionLeaks under errorsUse context managers or explicit release paths
Health checksDead connections reusedTest pool health and recovery behaviour
Async codeSync pool used in async pathVerify async pool APIs separately
ShutdownPool left openClose the pool during app teardown

The practical rule is simple: do not accept generated infrastructure code just because it runs once. Point the assistant at the current docs, then test the behaviour you expect in production.

Key takeaways:

  • AI-generated code can drift from current database-driver practices.
  • Connection pooling should be reviewed against current python-oracledb documentation.
  • Add runtime tests for connection acquisition, release, health, and shutdown.

How to implement this with Oracle AI Database

Short answer: Use Oracle AI Database to keep source metadata, chunks, embeddings, SQL filters, provenance, and deletion state close together. Store lifecycle fields next to retrieval fields, enforce freshness and delete filters in SQL, and run keyword, vector, and hybrid retrieval against the same governed metadata.

A practical Oracle AI Database design should store:

  • source ID and source system
  • source modified timestamp and ingestion timestamp
  • chunk ID, chunk ordinal, and chunk hash
  • parser version and embedding model version
  • current, superseded, deleted, and quarantined flags
  • tenant, ACL, classification, and provenance fields
  • retrieval-route metrics and challenge-set results

Useful docs and runnable assets:

The important architecture point is proximity. If chunks, vectors, metadata, permissions, and deletion state live together, reconciliation can be expressed as database checks plus retrieval tests. If every layer lives in a different system, deletion and freshness become distributed-systems problems.

Key takeaways:

  • Keep lifecycle metadata in the retrieval path, not in a separate spreadsheet or job log.
  • Apply is_current, is_deleted, tenant, and permission filters before generation.
  • Use evaluation artifacts and manifests when promoting parser, chunking, or embedding changes.

Production checklist for RAG freshness

Short answer: A production RAG system is fresh only if source updates, deletes, parser changes, embedding changes, cache invalidation, and retrieval filters are observable and tested. If the first signal of drift is a user complaint, the system is missing reconciliation.

Use this checklist before calling a RAG deployment production-ready:

  • Every active chunk has an active source.
  • Every active source has expected current chunks.
  • Deleted sources cannot appear in retrieval.
  • Superseded chunks are excluded by default.
  • Chunk hashes prevent duplicate inserts.
  • Embedding model versions are recorded and filterable.
  • Parser and chunking versions are recorded.
  • Retrieval challenge sets include stale, deleted, and duplicate cases.
  • Caches invalidate on source update and delete.
  • Reconciliation failures create tickets or block promotion.

Key takeaways:

  • Freshness is not an ingestion schedule; it is a tested invariant.
  • Reconciliation should run before users notice drift.
  • The vector index is not the source of truth. The source system is.