RAG · Эксплуатация · Продвинутый уровень
Удаление данных из RAG без призраков: синхронизация источника, индекса, кэша и резервных копий
Удалить документ из исходной системы недостаточно. Его текст может сохраниться в чанках, эмбеддингах, полнотекстовом индексе, кэше ответа, очереди повторной обработки, журнале событий и резервной копии. Ни один отдельный вызов DELETE не решает эту задачу целиком.
Удаление — это распределённый процесс
RAG обычно создаёт несколько представлений одного документа: исходный объект, нормализованный текст, чанки, векторы, поисковые записи и готовые ответы. Эти копии обновляются асинхронно и могут находиться в системах с разными сроками хранения.
Поэтому полезно разделять два обещания:
- Недоступность: документ больше нельзя получить через пользовательские запросы.
- Физическое удаление: активные и допустимые к очистке производные данные уничтожены, а оставшиеся резервные копии учтены политикой хранения.
Первое обещание должно выполняться быстро. Второе может занимать дольше, но обязано иметь измеримый срок, владельца и доказательство завершения.
1. Составьте карту следов документа
Начните не с команды удаления, а с реестра мест, куда документ попадает. Для каждого хранилища укажите стабильный идентификатор, способ поиска, способ удаления, ожидаемую задержку и владельца.
| Слой | Что искать | Как связывать | Что считать завершением |
|---|---|---|---|
| Источник | Файл, запись, вложение | document_id |
Объект удалён или помечен удалённым |
| Промежуточное хранилище | Извлечённый и нормализованный текст | document_id, revision_id |
Активных объектов нет |
| Векторный индекс | Чанки и эмбеддинги | Фильтр по document_id |
Поиск и выборка по ID возвращают ноль |
| Полнотекстовый индекс | Текст чанков и метаданные | document_id |
Записи отсутствуют во всех шардах и репликах |
| Кэш | Результаты поиска и ответы | Теги документа или версия области | Старые ключи недоступны |
| Очереди и задания | Повторная индексация, ретраи | document_id, operation_id |
Удаление не может быть отменено старым событием |
| Резервные копии | Снимки исходных и производных данных | Период, набор данных, ключ шифрования | Зафиксирован срок истечения и защита при восстановлении |
Критическое требование — переносить document_id во все производные записи. Хеш текста для этой роли неудобен: одинаковый фрагмент может принадлежать разным документам, а нормализация меняет хеш.
2. Введите реестр удаления и запрет чтения
Надёжная схема начинается с отдельного реестра удалений. Запись создаётся до очистки производных данных и немедленно становится источником запрета для чтения и повторной индексации.
CREATE TABLE deletion_requests (
operation_id uuid PRIMARY KEY,
tenant_id text NOT NULL,
document_id text NOT NULL,
requested_at timestamptz NOT NULL,
source_version bigint,
status text NOT NULL,
deadline_at timestamptz NOT NULL,
completed_at timestamptz,
UNIQUE (tenant_id, document_id)
);
CREATE TABLE deletion_steps (
operation_id uuid NOT NULL,
component text NOT NULL,
status text NOT NULL,
attempts integer NOT NULL DEFAULT 0,
checked_at timestamptz,
evidence jsonb,
PRIMARY KEY (operation_id, component)
);
Это пример схемы, а не готовая миграция: типы, ограничения и сроки следует согласовать с вашей моделью данных. В поле evidence храните счётчики, версии и технические идентификаторы проверки, но не удаляемый текст.
На пути чтения проверяйте реестр до выдачи контекста модели. Если проверка временно недоступна, для чувствительных областей безопаснее не выдавать спорный документ, чем считать его разрешённым.
function mayUseDocument(tenantId, documentId):
state = deletionRegistry.lookup(tenantId, documentId)
if state is unavailable:
return DENY_AND_RETRY
if state exists:
return DENY
return ALLOW
Этот барьер обеспечивает логическое удаление, пока фоновые исполнители очищают остальные слои.
3. Передавайте событие удаления с версией
Сообщение должно быть идемпотентным: повторная доставка не меняет итог. Добавьте идентификатор операции, версию источника и время запроса.
{
"event_type": "document.deletion.requested",
"operation_id": "пример-uuid",
"tenant_id": "example-tenant",
"document_id": "example-document",
"source_version": 42,
"requested_at": "2030-01-01T10:00:00Z"
}
Значения выше демонстрационные. Не используйте в документации реальные идентификаторы, содержимое документов или секреты.
Версия нужна для защиты от «воскрешения»: задержавшееся событие индексации версии 41 не должно восстановить документ после удаления версии 42. Каждый обработчик обязан сравнивать версию события с удаляющим маркером.
if deletion_version(document_id) >= indexing_event.source_version:
acknowledge(indexing_event)
do_not_index()
Если источник не предоставляет монотонную версию, используйте управляемое поколение документа. Одних временных меток недостаточно при рассинхронизации часов.
4. Очищайте активные слои идемпотентно
Исполнитель обрабатывает компоненты независимо. Ошибка в одном слое не должна возвращать уже очищенные данные в другие.
- Создать или подтвердить tombstone в реестре.
- Удалить исходный объект либо подтвердить его удаление.
- Удалить нормализованный текст и результаты разбора.
- Удалить все чанки по
tenant_idиdocument_id. - Удалить векторные и полнотекстовые записи.
- Инвалидировать кэш поиска, контекста и ответа.
- Отменить ожидающие задания или заставить их учитывать tombstone.
- Выполнить независимую проверку каждого слоя.
Удаляйте по точному составному фильтру. Сначала выполните подсчёт или просмотр выборки, особенно при ручном устранении инцидента.
# Пример безопасного диагностического вызова.
# Адрес, токен и идентификаторы передаются через локально настроенные переменные.
curl --fail-with-body \
--get "${RAG_ADMIN_URL}/internal/chunks/count" \
--data-urlencode "tenant_id=${TARGET_TENANT_ID}" \
--data-urlencode "document_id=${TARGET_DOCUMENT_ID}" \
--header "Authorization: Bearer ${RAG_ADMIN_TOKEN}"
Команда иллюстрирует форму read-only проверки. Конкретный маршрут не является стандартом и должен существовать в вашей административной службе. Не помещайте токены в историю команд, логи или URL.
Для изменяющей операции полезен режим предварительного просмотра:
rag-admin delete-document \
--tenant-id "${TARGET_TENANT_ID}" \
--document-id "${TARGET_DOCUMENT_ID}" \
--operation-id "${DELETE_OPERATION_ID}" \
--dry-run
rag-admin здесь — условное имя внутреннего инструмента. В реальной реализации --dry-run должен показывать только типы хранилищ, число найденных объектов и границы операции, не раскрывая содержимое.
5. Инвалидируйте кэш по зависимости, а не по догадке
Кэш по хешу пользовательского вопроса невозможно надёжно очистить по одному document_id, если зависимость нигде не записана. Есть три практичных варианта:
- хранить рядом с кэшированным результатом список использованных
document_idи индекс обратных зависимостей; - включать в ключ версию коллекции или области доступа и увеличивать её при удалении;
- использовать короткий TTL вместе с обязательной фильтрацией документов при чтении.
Первый вариант точнее, но требует дополнительного индекса. Второй быстро инвалидирует большую область и создаёт всплеск промахов. TTL сам по себе не обеспечивает немедленную недоступность.
Не забудьте про потоковые и промежуточные кэши: кэш ретривера, семантический кэш, кэш ответа модели, CDN и локальную память процесса. Если готовый ответ содержит сведения из удалённого документа, такой ответ тоже является производной копией.
6. Обработайте резервные копии без ложных обещаний
Мгновенно переписать все неизменяемые резервные копии часто невозможно или небезопасно для восстановления. Это не означает, что их можно игнорировать. Для каждого набора резервных копий зафиксируйте:
- какие системы и периоды он покрывает;
- срок хранения и рассчитанную дату окончательного истечения;
- кто и при каких условиях может выполнить восстановление;
- как реестр удалений применяется после восстановления;
- можно ли уничтожить данные криптографически удалением отдельного ключа.
Реестр удалений должен храниться или резервироваться дольше восстанавливаемых данных. После восстановления сначала поднимите запрет чтения, затем воспроизведите tombstone-события и только после этого открывайте поисковый трафик.
restore database snapshot
restore deletion registry
block retrieval traffic
replay deletion tombstones up to restore boundary
rebuild or scrub derived indexes
run residual-copy audit
unblock retrieval traffic
Это последовательность операций, а не команды конкретного продукта. Удаление ключа шифрования подходит только тогда, когда ключ действительно изолирован для нужного объекта или допустимой группы данных и не имеет других доступных копий.
7. Проверьте результат независимо
Успешный ответ от обработчика доказывает лишь выполнение вызова. Проверяющий процесс должен обращаться к хранилищу отдельно и не доверять счётчику, который вернул удаляющий процесс.
Минимальная матрица проверки:
- точечная выборка по
document_idво всех активных таблицах и индексах возвращает ноль; - векторный поиск с фильтром по документу не возвращает совпадений;
- поисковый API не выдаёт документ ни по ID, ни через сохранённый старый запрос;
- кэшированное обращение не возвращает прежний контекст или готовый ответ;
- старое событие индексации не восстанавливает записи;
- очереди ошибок и отложенных задач не содержат необработанных операций для документа;
- для резервных копий рассчитана дата истечения либо зафиксировано подтверждённое уничтожение ключа.
Проверку пользовательского пути проводите с техническим тестовым запросом по идентификатору и известным метаданным, а не копируя чувствительную фразу из удаляемого текста. Нулевой результат семантического запроса сам по себе слабое доказательство: документ мог сохраниться, но не попасть в верхние результаты.
{
"operation_id": "пример-uuid",
"status": "active_layers_verified",
"checks": {
"source": 0,
"normalized_objects": 0,
"vector_chunks": 0,
"text_index_records": 0,
"pending_jobs": 0,
"cache_dependencies": 0
},
"backup_expiry_at": "2030-02-01T00:00:00Z"
}
Такой отчёт является примером структуры доказательства. Состояние active_layers_verified честнее, чем fully_deleted, пока срок резервных копий не истёк.
8. Сделайте процесс наблюдаемым
Полезные метрики должны описывать не только ошибки, но и задержку согласования:
- возраст самой старой незавершённой операции удаления;
- время от запроса до запрета чтения;
- время до очистки каждого компонента;
- число операций, превысивших внутренний срок;
- число попыток повторной индексации, остановленных tombstone;
- число остаточных объектов, найденных независимой проверкой.
В журнал аудита записывайте идентификатор операции, компонент, действие, результат, время и версию программного обработчика. Не записывайте текст чанка, запрос пользователя, эмбеддинг или секрет доступа: журнал не должен становиться новой остаточной копией.
Типовые ошибки
Удаление только по идентификаторам чанков
Список чанков мог быть неполным после повторной нарезки. Надёжнее удалять по стабильному document_id и области арендатора, затем отдельно проверять число остатков.
Физическое удаление tombstone слишком рано
Позднее событие или восстановление из снимка может воскресить документ. Срок хранения tombstone должен покрывать максимальное время жизни очередей, журналов событий и резервных копий.
Проверка тем же кодом, который удаляет
Одинаковая ошибка фильтра даст ложный успех в обоих местах. Используйте отдельный путь чтения или независимое задание аудита.
Инвалидация только кэша ретривера
Готовый ответ модели, история диалога или серверный кэш страницы могут продолжить раскрывать содержание. Карта данных должна включать все производные ответы.
Переиндексация без проверки реестра
Полный rebuild способен снова загрузить удалённые данные из старого снимка. Индексатор обязан применять реестр удалений независимо от источника запуска.
Статус «удалено» сразу после постановки в очередь
Очередь подтверждает принятие задачи, а не очистку. Различайте состояния requested, blocked, active_layers_verified и backup_expired.
Ограничения и границы гарантий
Описанная схема не может автоматически удалить данные из систем, о которых нет сведений в карте потоков: ручных экспортов, снимков разработчиков, внешней аналитики или журналов стороннего сервиса. Такие места требуют организационного контроля и отдельной политики.
Эмбеддинг нельзя считать гарантированно анонимным только потому, что он не содержит читаемого текста. Его следует учитывать как производное представление исходных данных.
Резервная копия может оставаться физически существующей до завершения утверждённого срока хранения. В этот период она должна быть изолирована от обычного чтения, доступ к восстановлению — контролироваться, а удаление — повторно применяться перед возвратом системы в эксплуатацию.
Точные сроки и обязательства зависят от категории данных, договоров и применимых требований. Технический процесс должен реализовывать утверждённую политику, а не подменять её.
Контрольный список готовности
- У каждого документа есть стабильный
document_idи область владельца. - Все производные записи сохраняют эти идентификаторы.
- Запрос удаления сначала создаёт tombstone и блокирует чтение.
- Обработчики идемпотентны и защищены от событий старых версий.
- Кэш хранит зависимости или использует версию области.
- Проверка выполняется независимо для каждого хранилища.
- Статус активной очистки отделён от истечения резервных копий.
- Процедура восстановления повторно применяет реестр удалений до открытия трафика.
- Аудит содержит доказательства выполнения, но не копирует удаляемые данные.
- Просроченные операции видны в метриках и имеют владельца эскалации.
Практический критерий успеха прост: команда может назвать все места, где мог оказаться документ, быстро запретить его использование, очистить доступные копии, доказать отсутствие остатков и объяснить судьбу каждой резервной копии.