Почему «загрузить документы один раз» недостаточно
RAG-система полезна ровно настолько, насколько актуален её поисковый индекс. Если сотрудник отменил регламент, изменил цену или закрыл доступ к инструкции, старые фрагменты не должны продолжать участвовать в поиске. Иначе модель может уверенно сослаться на документ, которого в рабочем контуре уже нет.
Проблема возникает не на этапе генерации ответа, а раньше — между источником данных и индексом. Ночной импорт или ручная кнопка «переиндексировать» оставляют окно, в котором операционная система уже живёт по новым правилам, а помощник отвечает по старым. Полная пересборка индекса уменьшает это окно, но по мере роста базы становится дорогой и медленной.
Для изменяемых структурированных источников практичнее передавать в RAG не снимок таблицы, а поток событий: запись создана, изменена или удалена. Такой подход обычно называют change data capture, или CDC. Для PostgreSQL изменения можно получать через logical decoding; Debezium превращает их в события, а обработчик обновляет только затронутые документы и фрагменты.
Что подтверждают первичные источники
PostgreSQL описывает logical decoding как преобразование записей write-ahead log в понятный внешнему потребителю поток изменений. Репликационный слот хранит позицию чтения и позволяет продолжить обработку после остановки. При этом документация прямо предупреждает: после сбоя клиент может повторно получить уже виденные изменения и обязан обрабатывать повтор безопасно.
Debezium для PostgreSQL формирует события на уровне строк для операций insert, update и delete. Для удаления коннектор может дополнительно отправлять tombstone — запись с тем же ключом и пустым значением. Это не «мусорное» событие, а явный сигнал потребителю: объект больше не должен присутствовать в производном представлении.
Qdrant хранит точку как идентификатор, вектор и необязательный payload. Операции изменения сначала фиксируются в собственном write-ahead log; повторный upsert с тем же идентификатором обновляет точку вместо создания независимой копии. Точки можно удалять по идентификаторам или фильтру. Эти свойства позволяют построить идемпотентный обработчик без полной пересборки коллекции.
Независимая исследовательская работа DBLog формализует задачу согласования первоначального снимка с живым журналом изменений. Практический вывод для RAG прост: первичная загрузка и последующий поток должны иметь согласованную границу, иначе часть обновлений будет пропущена или применена дважды.
Минимальная архитектура для малого бизнеса
Необязательно начинать с большого Kafka-кластера. Для одной базы и умеренного потока документов достаточно шести компонентов:
- PostgreSQL или другая система учёта остаётся единственным источником истины;
- CDC-коннектор читает журнал и выдаёт события с ключом, типом операции и позицией;
- небольшая очередь отделяет источник от медленной обработки файлов и эмбеддингов;
- обработчик собирает актуальную версию документа и режет её на фрагменты;
- сервис эмбеддингов работает локально или в разрешённом внешнем контуре;
- векторное хранилище принимает upsert и delete, а таблица состояния хранит прогресс.
Если Kafka уже есть, Debezium естественно подключается к нему. Если её нет, можно использовать Debezium Server или собственный потребитель logical replication, но обязательные свойства не меняются: устойчивое хранение позиции, повторная доставка, очередь ошибок и возможность переиграть событие.
Стабильный идентификатор важнее номера фрагмента
Распространённая ошибка — присваивать чанкам последовательные номера: документ 42, фрагменты 1–12. После редактирования абзац в начале сдвигает все номера, и система либо оставляет старые точки, либо переписывает почти весь документ.
Практичнее формировать идентификатор из стабильного ключа источника, версии представления и отпечатка нормализованного содержимого. Например:
- `source_id` — первичный ключ записи или неизменяемый идентификатор файла;
- `representation` — тип представления: карточка товара, инструкция, приложение;
- `content_hash` — SHA-256 нормализованного текста фрагмента;
- `tenant_id` и метка доступа — обязательная часть payload, но не секрет в самом ID.
При update обработчик сначала получает новую целевую совокупность идентификаторов, затем делает upsert новых точек и удаляет прежние точки этого `source_id`, которых нет в новой версии. Такой set reconciliation устойчив к повтору: второе применение того же события приводит к тому же состоянию.
Как обрабатывать удаление
Удаление нельзя сводить к флагу в исходной таблице, если индекс продолжает возвращать старые точки. Tombstone или delete-событие должно пройти по отдельному короткому пути:
1. определить источник и арендатора по ключу события;
2. запретить новые ответы по объекту ещё до пересчёта векторов;
3. удалить все точки с соответствующим `source_id` и областью доступа;
4. очистить производные полнотекстовые и кэшированные представления;
5. записать подтверждение вместе с позицией журнала.
Для юридически значимых данных само содержимое удалённого документа лучше не класть в журнал приложения. Достаточно идентификатора, типа операции, позиции и технического результата. Аудит должен доказывать выполнение удаления, но не создавать вторую бесконтрольную копию текста.
Порядок операций без распределённой транзакции
PostgreSQL и векторная база обычно не участвуют в одной транзакции. Попытка имитировать «ровно один раз» между разными системами усложняет проект и всё равно не отменяет сбои сети. Практичнее принять доставку как минимум один раз и сделать каждую операцию идемпотентной.
Безопасная последовательность выглядит так:
- получить событие и зафиксировать его ключ дедупликации;
- прочитать актуальное состояние источника, если событие не является delete;
- подготовить фрагменты и эмбеддинги;
- применить upsert, затем убрать лишние точки;
- записать контрольную сумму результата и только после этого подтвердить событие.
Если процесс упал после upsert, но до подтверждения, событие придёт снова. Стабильные идентификаторы не создадут дублей. Если удаление временно недоступно, событие остаётся в очереди повторов, а объект можно немедленно заблокировать фильтром `active=false` или deny-list на уровне retrieval gateway.
Где нужны контрольные сверки
CDC сокращает задержку, но не отменяет периодическую проверку. Репликационный слот может отстать, обработчик — попасть в бесконечный retry, а изменение схемы — нарушить сборку документа. Поэтому раз в сутки или неделю полезно выполнять дешёвую сверку:
- количество активных объектов по источнику и по индексу;
- выборочный hash текущего текста и сохранённой версии;
- возраст последнего успешно применённого события;
- размер очереди ошибок и максимальное число повторов;
- наличие точек для уже удалённых `source_id`;
- доля запросов, где найден документ старее допустимого SLA.
Полная переиндексация остаётся аварийным инструментом и способом сменить модель эмбеддингов, а не штатным способом доставки каждого изменения.
Данные, доступы и инфраструктура
CDC-пользователю нужны минимальные права на чтение журнала и только тех таблиц, которые входят в базу знаний. Секреты подключения хранятся отдельно от конфигурации коннектора. Сеть должна разрешать движение от источника к коннектору и от обработчика к индексу, но не давать векторной базе прямого доступа к производственной БД.
В событие не стоит переносить все поля строки «на будущее». Передавайте технический ключ и минимум данных, необходимых для маршрутизации; полный документ обработчик может собрать через контролируемый read API. Так проще применять маскирование, права арендатора и правила исключения полей.
При изменении прав доступа документ нужно обрабатывать так же строго, как при изменении текста. Новый payload должен попасть в индекс, старые точки с прежней областью видимости — исчезнуть, а retrieval gateway обязан всегда добавлять фильтр подтверждённого пользователя.
Ограничения и экономика
CDC не нужен для папки из пяти PDF, которые обновляются раз в квартал: там надёжнее простой манифест с хешами и ежедневная синхронизация. Поток изменений оправдан, когда источник меняется в течение дня, ошибка устаревшего ответа материальна или полная пересборка уже мешает эксплуатации.
Экономику следует считать не по цене коннектора, а по полному процессу. В модель входят часы инженера, очередь и наблюдаемость, вычисление эмбеддингов только для изменённых фрагментов, хранение версий и стоимость ошибочного ответа. Инкрементальная схема обычно экономит GPU и время пересборки, но добавляет эксплуатационные проверки.
Для пилота достаточно модельного расчёта с явными допущениями: число изменённых документов в день, среднее количество фрагментов, время эмбеддинга, допустимая задержка обновления и стоимость ручной проверки. Если меняется менее процента корпуса, пересчёт только затронутых объектов обычно выглядит рациональнее полного nightly rebuild; окончательное решение подтверждают измерением на собственной базе.
Пилот за две недели
Начните не со всего хранилища, а с одного изменяемого процесса: например, прайс-листа, базы регламентов или карточек обслуживания.
- Зафиксируйте SLA свежести: например, изменение должно исчезнуть из ответов не позднее чем через пять минут.
- Выберите 30–50 документов и подготовьте сценарии create, update, delete и смены прав.
- Выполните согласованный первичный снимок и запустите поток с сохранённой позиции.
- Дважды примените несколько событий и убедитесь, что число точек не растёт.
- Удалите документ и проверьте поиск, кэш, цитирование и журналы.
- Остановите обработчик, накопите изменения, запустите снова и проверьте восстановление.
- Добавьте метрики задержки, очереди повторов и «осиротевших» точек.
Критерий готовности — не красивый ответ модели, а воспроизводимое состояние индекса: после любого повтора, падения или удаления он сходится к данным источника. Внутрик может помнить многое; бизнесу важнее, чтобы он вовремя забывал отменённое.
