Обновление индекса RAG: ingestion, версии и удаление данных
Надёжный retrieval начинается с versioned контракта source-to-index
Idempotent ingestion, staged promotion, access-aware removal и reconciliation
Авторский INDEX-UPDATE-8 workflow с pseudocode, failure modes и acceptance gates
обновление индекса RAG, ingestion pipeline, версии RAG, удаление данных, RAG architecture и INDEX-UPDATE-8

$ update rag-index --contract INDEX-UPDATE-8
> receive: source event / version / scope
> stage: extract / normalize / chunk / write
> verify: hash / count / access / retrieval
> promote: current / retry / quarantine / remove
> reconcile: source registry / index receiptИндекс RAG — не статичная база, которую один раз собрали для демо. Это проекция исходных документов в конкретный момент. Когда меняется policy, исправляется источник, отзывается доступ или документ нужно удалить, retrieval-слой обязан прийти к новому состоянию без смешивания старых и новых evidence.
Этот материал отвечает на узкий технический запрос «обновление индекса RAG»: ingestion-контракты, версии, удаление, failure modes и production-проверки. Более широкую продуктовую задачу покрывает страница RAG-систем. Для архитектурного ревью команда может начать с AI engineering brief; статья даёт критерии реализации, а не заменяет коммерческую landing page.
Начните с контракта source of truth
Обновление индекса должно начинаться с authoritative source event, а не с изменённого embedding. У source record нужны стабильный sourceId, tenant или access scope, origin, lifecycle, content version, hash, timestamps и ссылка, которую может открыть человек. Каждый derived chunk получает детерминированный ID и сохраняет source version и access attributes, которые сделали его eligible.
Полезный вопрос — не «принял ли vector store upsert?», а «может ли запрос увидеть только нужную current и разрешённую версию source?». Тогда ingestion становится задачей reconciliation: source registry объявляет desired state, workers приводят индекс к нему, а verifier доказывает результат.
SOURCE CHANGE
-> validate source и access attributes
-> assign immutable sourceVersion + content hash
-> create idempotent ingestion job
-> extract / normalize / chunk с pinned configuration
-> write versioned candidates и retrieval filters
-> verify count, version, access и citations
-> promote current | retry | quarantine | removeНе используйте имя файла, время upload или порядок записей vector store как identity. Эти поля легко меняются и делают retry либо частичный rollback неоднозначными. Детерминированный ключ chunk, например sourceId:version:chunkOrdinal:chunkerVersion, даёт практическую границу восстановления.
Архитектура versioned ingestion
Набросок INDEX-UPDATE-8 сохраняет видимыми границы ответственности. Это архитектурный паттерн, а не предписание конкретного вендора.
| Компонент | Контракт | Безопасное поведение при сбое |
|---|---|---|
| Source registry | Объявляет source ID, current version, lifecycle, scope и locator | Отклоняет update без identity или ownership |
| Change collector | Превращает upload, webhook или scan в idempotent job | Дедуплицирует повторные events и хранит event key |
| Extractor и normalizer | Возвращает inspectable text и extraction status | Quarantine для нечитаемого input, не индексировать догадку |
| Chunker | Использует pinned configuration и source offsets | Создаёт новый derived version, не мутирует неизвестные chunks |
| Index writer | Хранит vectors, lexical fields, filters и version lineage | Пишет в staged state, не продвигает partial work |
| Promotion gate | Делает одну проверенную версию current для retrieval | Сохраняет prior known-good version до успешной проверки |
| Removal worker | Применяет delete или tombstone во всех derived stores | Fail closed для source до подтверждённого удаления |
Разделяйте immutable source version и derived-index version. Сам документ может не меняться, а extraction, OCR, parser, chunking или embedding configuration — измениться. Сохранение обеих версий позволяет осознанно rebuild и объяснить, почему два retrieval run дали разный результат.
Ingestion должен быть idempotent и observable
Events часто дублируются, задерживаются или приходят не по порядку. Retry-safe pipeline даёт каждому запросу event ID, а каждому target state — version. Повторение того же event должно оставить тот же candidate set, а не дублировать chunks. Более новая source version не должна быть перезаписана поздним worker для старой.
async function ingest(event: SourceChanged) {
const source = await registry.get(event.sourceId);
assert(source.version === event.version);
assert(policy.allowsIngestion(source.scope));
const job = await jobs.claim({ eventId: event.id, sourceId: source.id, version: source.version });
if (job.alreadyCompleted) return job.receipt;
const artifact = await extractNormalizeAndChunk(source, pinnedConfig);
await stagedIndex.upsert(artifact.chunks, { sourceId: source.id, version: source.version });
await verifyCandidateSet(source, artifact);
await registry.promoteCurrent(source.id, source.version, artifact.receipt);
}Псевдокод намеренно не скрывает важные решения: access metadata входит до indexing, promotion идёт после verification, а registry — не память worker — определяет current state. Если worker не может установить это состояние, он должен retry или quarantine, а не публиковать сомнительный индекс.
Update, replacement и removal — разные операции
Update обычно создаёт новую version и затем её promotes. Replacement меняет retrieval target только после полного candidate set. Removal меняет eligibility: source не должен попадать в vector retrieval, keyword retrieval, reranking input, answer citations, caches или diagnostics, которые раскрывают контент.
Используйте tombstone или removal registry, когда physical deletion асинхронен. Tombstone — немедленно применяемый retrieval filter; downstream workers затем стирают или compact derived artifacts. Сохраняйте request, owner, scope, observed completion time и exceptions. Не называйте source удалённым, если обновилась лишь одна collection.
Удаление имеет границы за пределами index. Backups, audit logs, legal retention, observability и vendor-managed replicas могут подчиняться отдельному retention policy. Явно моделируйте эти границы и привлекайте owner данных; это engineering pattern, а не юридическая консультация.
Failure modes, которые нужно спроектировать
Partial update становится current. Часть chunks записалась, но extraction упал на 48-й странице. Promotion требует expected chunk count, content hash, retrieval probes и source-version evidence, а не только успешный exit job.
Поздний event перезаписывает новую version. Retry version 7 завершается после version 8. Используйте compare-and-set source version при promotion и отклоняйте либо quarantine stale work.
Deletion пропускает secondary path. Vector index очищен, но lexical index, cache, reranker или citation lookup хранит старый passage. Ведите единый removal inventory и проверяйте все retrieval path на distinguishable fixture.
Metadata расходятся с content. Документ переехал между командами, но inherited chunk permissions остались старыми. Access scope и lifecycle — versioned ingestion inputs; при их изменении переобрабатывайте или блокируйте affected source.
Scan видит незавершённый upload. Collector индексирует файл, пока он дописывается. Нужны stable-object signal, checksum или source-side finalization event до extraction.
Метрики скрывают опасный случай. Средняя ingestion latency выглядит хорошо, пока stale sources сохраняются. Отдельно измеряйте backlog age, promotion lag, current-version coverage, removal lag и failed verification.
Соберите production acceptance set
Acceptance set строится на безопасных fixture documents с уникальными фразами, известными source IDs и разными permissions. Прогоняйте его через те же event, worker и index path, что в production.
| Gate | Какое evidence сохранить |
|---|---|
| Idempotency | Duplicate event оставляет один canonical chunk set и receipt |
| Ordering | Старое completion не заменяет новую promoted version |
| Completeness | Expected chunks, hash, source version и locators совпадают до promotion |
| Retrieval | Query возвращает новую permitted version и её citation locator |
| Access | Scope change удаляет ineligible chunks до retrieval |
| Removal | Deleted fixture отсутствует в vector, lexical, cache, citation и trace probes |
| Recovery | Failed candidate не становится promoted; prior version или no-answer route явен |
Проверьте happy-path update и сбои, которые операторам действительно придётся восстанавливать: timeout после write, duplicate webhook, malformed file, re-run с новым chunking, revoke доступа и delete во время retry worker. Храните небольшой evidence record каждого run, чтобы production incident можно было восстановить без догадок по transient logs.
Контролируемый путь от prototype к production
Начните с одной source family и ограниченного corpus. Назначьте source owner, ingestion owner и on-call decision path. Зафиксируйте parser, chunker и embedding configuration. Задайте явный maximum promotion lag и безопасное поведение при его превышении: сохранить prior verified version, если policy позволяет, или вернуть прозрачный no-answer.
Затем добавьте reconciliation scan. Одна доставка event не доказывает completeness: периодическое сравнение source registry и index receipt находит missing events, orphaned chunks и failed removals. Соседние гайды о метаданных и фильтрах RAG, правах доступа в RAG и цитатах RAG разбирают контракты, которые делают индекс надёжным во время query.
До расширения corpus проверьте failure budget: что происходит, когда index отстаёт, source оспорен, приходит запрос на deletion или verification не запускается? Надёжный update path — не самый быстрый путь записи vectors. Это путь, который может показать, какая version searchable, кто может её видеть, почему она current и как безопасно перестать её выдавать.
require(source.id && source.version && source.hash);
require(event.idempotencyKey && policy.allows(source.scope));
require(candidate.chunkCount === expected.chunkCount);
require(candidate.scope === source.scope);
require(testSet.duplicate && testSet.outOfOrder && testSet.remove);
if (!verification.complete) route = "retry-or-quarantine";
if (removal.pending) route = "exclude-from-retrieval";