Как обновлять базу знаний RAG, не останавливая систему
Демо живёт на статичном корпусе, продакшн — на меняющемся. Разбираем инкрементальное обновление вместо полной переиндексации, подмену индекса без рестарта, удаление документов и то, какие метрики показывают деградацию поиска раньше, чем о ней сообщат пользователи.
8 мин чтения
Почему обновление — отдельная инженерная задача
На демонстрации корпус загружают один раз, и вопрос обновления не возникает. В работе документы меняются каждый день, и система, которая отвечает по прошлой редакции регламента, хуже, чем отсутствие системы: она звучит так же уверенно, но подсказывает отменённое правило.
Поэтому обновление проектируется вместе с поиском, а не добавляется потом. Три вопроса, на которые нужен ответ до запуска: как документ попадает в индекс, как он из него исчезает и что видит пользователь в момент, когда индекс меняется.
Инкрементальное обновление вместо полной переиндексации
Полная переиндексация корпуса — операция на часы и заметные деньги на эмбеддингах, и делать её при изменении одного документа незачем. Рабочая схема: хеш содержимого на уровне документа и на уровне куска, пересчитываются только изменившиеся куски, остальные остаются как есть.
У этого есть неочевидное следствие: нарезка должна быть детерминированной. Если один и тот же документ режется каждый раз по-разному, хеши не совпадут и «инкрементальное» обновление превратится в полную переиндексацию под другим названием.
Подмена индекса без рестарта
Перезапуск сервиса ради обновления базы — не решение, а откладывание проблемы: он роняет открытые сессии и делает обновления редкими, потому что каждое становится событием. Стандартный приём — версионированный индекс и указатель на него: новая версия собирается рядом, проверяется, потом указатель переключается атомарно, а старая версия держится до конца текущих запросов.
Ключевое здесь — проверка до переключения, а не после. Индекс, собранный с ошибкой в конвейере, переключается так же гладко, как правильный, и обнаруживается только по жалобам.
Удаление: то, о чём вспоминают последним
Документ убрали из хранилища, а его куски остались в индексе — и система продолжает отвечать по тому, чего уже нет. В чувствительных доменах это не неудобство, а нарушение: сотрудник видит данные, доступ к которым отозван.
Минимально рабочая схема: источник истины — хранилище документов, индекс — производная от него; удаление и изменение прав распространяются тем же конвейером, что и добавление, и проверяются тем же тестом. Права доступа при этом хранятся в метаданных куска и применяются фильтром до поиска, а не отсечением в ответе.
Что меняется в качестве поиска при обновлении
Пополнение индекса меняет не только состав, но и конкуренцию за топ выдачи: новые куски вытесняют старые по похожим формулировкам. Отдельная ловушка — смена модели эмбеддингов: старые и новые векторы несопоставимы, и такой переход требует полной пересборки, а не дозаписи.
- Доля вопросов, где нужный фрагмент попал в топ выдачи, — до и после обновления, на одном и том же наборе.
- Доля ответов с пустой выборкой: рост означает, что что-то отвалилось на конвейере.
- Возраст самого свежего документа в индексе — простейший индикатор того, что обновления вообще идут.
- Стоимость обновления: сколько кусков пересчитано на одну правку документа.
Что достаточно на старте
Не всякому проекту нужно обновление в реальном времени. Если регламенты меняются раз в квартал, ночной пересбор с проверкой перед переключением закрывает задачу полностью и стоит несопоставимо дешевле в поддержке.
Разница между «раз в сутки» и «в тот же час» — это настройка конвейера, а не другая архитектура. А вот разница между «есть удаление» и «нет удаления» архитектурная, и закладывать её нужно сразу.