Как обновлять базу знаний без полной переиндексации
Как обновлять базу знаний без полной переиндексации: сравниваем delta indexing, tombstones и очереди обновления для больших хранилищ.

Почему полная переиндексация мешает работе
Когда в хранилище лежат миллионы файлов, полный прогон бьет сразу по двум местам: по инфраструктуре и по времени. Система снова читает все документы, заново режет их на фрагменты, пересчитывает эмбеддинги и пересобирает индекс, даже если со вчерашнего дня изменились только 2-3% данных.
На бумаге это выглядит просто. На деле ночной прогон часто не укладывается в ночь. Пока индексатор обрабатывает старые PDF, новые договоры, письма и регламенты уже попали в источник, но поиск их еще не видит.
Так появляется отставание поиска от реальных данных. Утром сотрудник открывает поиск и получает версию документа за прошлую неделю, хотя в системе уже лежит свежая редакция. Для внутренней базы знаний это просто раздражает. Для банка, телекома или госсектора это уже риск рабочих ошибок.
Затраты тоже растут рывками. CPU уходит на парсинг, OCR и нормализацию. GPU или отдельные воркеры заняты пересчетом эмбеддингов для документов, которые никто не менял. Хранилище раздувается из-за временных сегментов индекса, новых версий чанков и служебных логов.
На большом архиве это быстро чувствуется в деньгах. Если у вас 10 миллионов документов, даже повторная обработка лишнего миллиона файлов уже стоит заметно дороже, чем точечное обновление. И это расходы, которые никак не улучшают поиск.
Отдельная проблема - удаления. Полная переиндексация редко обрабатывает их аккуратно. Если документ исчез из источника утром, а следующий полный прогон случится ночью, весь день поиск может показывать то, чего уже не должно быть в выдаче. Пользователь открывает старую карточку, а система ссылается на удаленный файл.
Еще хуже, когда полный прогон падает на середине. Тогда часть старых документов остается в индексе, часть новых уже попала туда, а часть удалений так и не доехала. В итоге поиск начинает дублировать записи, путать версии и возвращать устаревшие фрагменты.
Какие изменения реально приходят в хранилище
Поток изменений почти никогда не выглядит как полная замена старого набора документов новым. В живой базе знаний новые файлы приходят каждый день, старые правят по частям, а часть событий вообще не касается текста.
Поэтому сначала стоит разобрать сами типы изменений. Обычно система видит не один сценарий, а сразу несколько, и у каждого свой риск для поиска:
- появляется новый документ или новая версия существующего;
- меняется только часть текста, например абзац, таблица или приложение;
- отдельно меняются права доступа, владелец, теги или срок хранения;
- документ удаляют, архивируют или возвращают из архива.
Новые документы обрабатывать проще всего. Их можно индексировать как добавление: разбить на чанки, посчитать эмбеддинги, записать метаданные. Но на этом поток не заканчивается.
С правками сложнее. Редактор редко переписывает весь файл. Обычно он исправляет сумму в договоре, добавляет пункт в регламент или меняет один раздел инструкции. Если система каждый раз пересобирает индекс целиком, она тратит лишние ресурсы и дольше держит поиск в старом состоянии.
Отдельная история - права доступа. В банке или телекоме документ может остаться тем же, но доступ к нему сегодня есть у команды закупок, а завтра только у юристов. Поиск должен учесть это сразу. Иначе пользователь увидит лишнее или, наоборот, не найдет нужный документ, хотя текст вообще не менялся.
С удалениями и архивированием тянуть нельзя. Если сотрудник убрал устаревший приказ или архивировал набор внутренних инструкций, индекс должен быстро перестать их отдавать. Иначе поиск будет показывать то, чего уже не должно быть в выдаче.
Есть и менее очевидный случай - откат версии назад. Такое бывает после ошибочной публикации. Вчера в индекс попала новая редакция, а сегодня команда вернула предыдущую. Для поиска это не просто очередное обновление, а возврат к старому содержимому, старым чанкам и иногда старым правам.
Хорошая схема обновления начинается не с индекса, а с карты событий. Пока вы не разделили новые документы, частичные правки, смену доступа, удаления и откаты версий, индексация будет либо медленной, либо неточной.
Где подходит дельта-индексация
Если в хранилище каждый день меняется малая часть документов, нет смысла заново прогонять весь корпус. Дельта-индексация хорошо работает там, где документы живут долго, а правки приходят кусками: новая редакция регламента, добавленный договор, исправленный абзац в инструкции, свежий тикет в базе знаний.
Схема проста: система смотрит не на весь архив, а только на то, что изменилось с прошлого цикла. Для этого обычно хватает стабильного ID документа и одного признака изменения: version, checksum или updated_at. Если источник надежный, дельта-проход занимает минуты, а не всю ночь.
Хороший эффект виден в больших хранилищах, где поиск строится по чанкам. Если редактор поправил один раздел на две страницы, не нужно заново считать эмбеддинги для файла на 80 страниц. Система находит затронутые чанки, пересчитывает только их и обновляет связи с исходным документом. Лаг после правок заметно падает, а новая версия быстрее попадает в поиск.
Пример простой. В банке есть архив внутренних инструкций на 2 миллиона документов. За день меняются 8-10 тысяч файлов, и чаще всего только один раздел. Полная переиндексация грузит парсеры, очередь эмбеддингов и векторное хранилище. Дельта-схема обрабатывает только измененные документы и чанки, поэтому поиск не ждет до утра, а команда не тратит GPU и CPU на одинаковый текст.
Когда нужен полный пересчет
Дельта-индексация не решает все задачи. Полный пересчет все еще нужен, если:
- вы сменили модель эмбеддингов;
- вы поменяли правила чанкинга;
- OCR или парсер начали извлекать текст по-другому;
- в старых данных нет надежных версий, checksum или времени обновления;
- важные поля для ранжирования массово изменились во всем архиве.
Есть и еще одна неприятная проблема - тихие изменения. Документ может сохранить старый updated_at, хотя текст уже другой. Или система меняет вложение, но не поднимает версию. В таких местах лучше сверять checksum содержимого, а не верить одной дате.
Если говорить честно, дельта-схема отлично подходит для частых локальных правок, но не заменяет полный пересчет после системных изменений.
Зачем нужны tombstones
Tombstone - это запись о том, что документ больше нельзя показывать. Она нужна там, где индекс, векторное хранилище и кэши живут отдельно и обновляются не в одну миллисекунду. Если просто удалить файл в исходной системе, поиск еще какое-то время может отдавать старые чанки.
Поэтому схему лучше строить так: сразу после события delete система ставит tombstone для document_id и версии документа. Это дешевле и надежнее, чем пытаться мгновенно вычистить все следы во всех индексах. Метка появляется быстро, а тяжелую очистку можно сделать чуть позже.
Для поиска и RAG это особенно важно. Пока фоновые воркеры не добрались до индекса, слой выдачи уже должен видеть tombstone и скрывать документ, его чанки и связанные эмбеддинги. Иначе пользователь задаст вопрос, а система процитирует текст, который юристы или владелец данных уже убрали.
То же правило работает при смене прав доступа. Документ могли не удалить физически, но закрыть для отдела, подрядчика или внешнего клиента. Для выдачи это почти то же, что удаление: старые чанки надо убрать из поиска и RAG сразу, иначе контроль доступа останется только на бумаге.
Обычно tombstone хранит несколько полей:
- идентификатор документа;
- версию или ревизию;
- причину удаления или закрытия доступа;
- время события;
- срок жизни записи.
Срок жизни tombstone часто недооценивают. Очереди могут отставать, воркеры могут перезапускаться, а часть индекса может восстановиться из бэкапа. Если метка исчезнет слишком рано, один из потребителей пропустит событие и вернет документ в поиск. Для больших документных хранилищ это частая и неприятная ошибка.
Физическую очистку лучше запускать только после подтверждения от всех потребителей, которые реально убрали данные. Обычно это текстовый индекс, векторный индекс и кэш выдачи. Пока подтверждения нет, tombstone стоит держать живым.
Простой пример: в архиве банка удалили регламент и загрузили новую версию. Если команда не использует tombstones, старые чанки могут еще несколько часов всплывать в ответах RAG. Если метка приходит сразу, система скрывает документ в тот же момент, а индекс дочищает следы уже без спешки.
Как работают очереди обновления
Очередь обновления нужна, когда документы меняются постоянно, а индекс не должен отставать на часы. Вместо большого ночного прогона система кладет в очередь каждое событие отдельно: новый документ, правку, смену метаданных, удаление. За счет этого индексатор обрабатывает не весь архив, а только то, что реально изменилось.
Одна задача в очереди обычно описывает один документ и одно действие. Такой формат проще контролировать. Если у документа поменялся заголовок, не нужно заново гонять весь раздел базы знаний. Достаточно поставить задачу на обновление именно этого документа.
Самая частая ошибка - терять порядок событий для одного документа. Представьте: сначала пришло обновление текста, потом удаление, а затем старая задача на обновление доехала позже всех. Если система не сохраняет порядок, удаленный документ может снова появиться в индексе. Поэтому события одного документа обычно привязывают к одному partition или хотя бы проверяют версию документа перед записью.
Очередь решает сразу несколько практических задач:
- держит изменения по документам в понятном потоке;
- дает повторные попытки для упавших задач;
- позволяет замедлить обработку в пиковые часы;
- изолирует новые события от проблемных повторов.
Повторные попытки особенно полезны, когда падает не вся система, а один шаг: парсинг PDF, расчет эмбеддингов или запись в индекс. В таком случае задача уходит в retry-очередь и пробуется еще раз через паузу. Полный прогон не нужен. Это экономит часы работы и не создает лишнюю нагрузку.
Во время пиков очередь помогает не уронить продакшен. Если днем в хранилище пришли десятки тысяч изменений, индексатор может брать задачи порциями, например по 100 или 500 штук, и держать фиксированную скорость. Пользователи продолжают работать, а индекс догоняет поток без резких всплесков CPU и I/O.
Новые события и повторные попытки лучше разводить. Иначе несколько тяжелых ретраев забьют весь поток, а срочные удаления и точечные правки застрянут в хвосте.
Как собрать эти подходы в одну схему
Если смотреть на большие документные хранилища без иллюзий, один механизм не решает все. Новые и измененные файлы лучше обрабатывать через дельта-индексацию, удаления и отзыв доступа - через tombstones, а между источником и индексом нужна очередь. Такая схема дает предсказуемое обновление без ежедневного полного прогона.
На практике поток обычно выглядит так:
- источник фиксирует событие: создан документ, изменен текст, изменены метаданные, документ удален, доступ закрыт;
- событие попадает в очередь и ждет обработки без перегрузки индекса;
- воркер читает событие, проверяет версию документа и решает, что именно нужно обновить;
- индексатор меняет только затронутые части, а не весь корпус;
- tombstone сразу скрывает удаленный документ из поиска, даже если физическая очистка пройдет позже.
Здесь полезно разделить правила обновления. Текст документа меняют не так часто, но если он изменился, обычно нужно заново разбить его на чанки и пересчитать эмбеддинги. Метаданные живут быстрее: статус, теги, срок действия, владелец, уровень доступа. Их можно обновлять отдельно, без пересчета векторного слоя, если смысл текста не поменялся.
Удаления и закрытие доступа не стоит смешивать с обычными обновлениями. Для них лучше сразу писать tombstone по doc_id или chunk_id. Тогда поиск перестает возвращать документ почти мгновенно. Это особенно полезно там, где есть требования по 152-ФЗ, внутренним политикам доступа и аудит-трейлам: пользователь не должен видеть файл до тех пор, пока фоновая очистка не дойдет до индекса.
Как это работает в живой системе
Представьте архив договоров. Юристы часто меняют карточку документа: номер версии, подразделение, статус согласования. Сам текст меняется реже. В такой схеме система обновляет метаданные сразу, текст переиндексирует только при реальном изменении содержимого, а эмбеддинги пересчитывает только для затронутых чанков. Если договор отозвали из доступа, tombstone скрывает его из выдачи сразу.
Полная сверка все равно нужна, но редко. Не каждый день, а по расписанию: например, раз в неделю или после крупных сбоев. Она ищет пропущенные события, битые версии и расхождения по счетчикам. Обычно рабочая схема именно так и устроена: не один механизм, а связка дельта-индексации, tombstones, очереди и редкой сверки.
Пошаговый план внедрения
Если вы строите такую схему с нуля, не начинайте с очереди или индекса. Сначала зафиксируйте, какая система говорит правду о состоянии документа в любой момент.
Обычно это основное хранилище: CMS, DMS, каталог договоров или архив файлов с метаданными в БД. У каждого документа должно быть стабильное ID и поле версии. Подойдет счетчик ревизий, timestamp изменения или хеш содержимого. Без этого вы не поймете, что уже попало в индекс, а что нет.
- Опишите события create, update и delete так, будто их будет читать не разработчик индекса, а дежурный инженер ночью. Для create хватит ID, версии и минимального набора метаданных. Для update добавьте причину обновления, чтобы отличать правку текста от смены прав доступа.
- Задайте единый формат задачи для очереди. В задаче обычно лежат ID документа, тип события, версия, время создания и число попыток. Если формат плавает между сервисами, очередь быстро превращается в источник споров.
- Включите tombstones для удалений. Индексу мало знать, что документа больше нет в исходной базе. Ему нужен явный маркер удаления с датой, версией и сроком хранения, иначе старые куски текста останутся в поиске или в RAG.
- Сразу задайте срок жизни tombstone. Для небольшого архива хватает нескольких дней, для большого юридического или финансового хранилища срок часто держат дольше, чтобы пережить отставание воркеров, повторную доставку задач и восстановление после сбоя.
- С первого дня замеряйте три вещи: лаг между изменением и обновлением индекса, долю ошибок по типам событий и стоимость обработки одного документа. Лаг покажет, где очередь задыхается. Ошибки покажут, ломаются ли парсеры, права доступа или дедупликация. Стоимость быстро отрезвляет, если обновления гоняют слишком много лишних чанков.
Полезно начать с одного типа документов, например с внутренней базы регламентов, где много правок и мало бинарных вложений. Если этот поток проходит спокойно, потом уже добавляют тяжелые PDF, сканы и архивные наборы. Такой порядок почти всегда лучше, чем сразу тащить в новый процесс все хранилище.
Частые ошибки
Когда команда уходит от полной переиндексации, проблемы обычно начинаются не на уровне моделей, а в данных и очередях. Ошибки выглядят мелкими, но потом поиск выдает старые фрагменты, лишние документы и ответы не для тех пользователей.
Первая ошибка - брать время изменения из разных систем как есть. Один источник пишет время в UTC, другой в локальной зоне, третий округляет до секунд, а четвертый меняет поле updated_at даже после пересохранения без реальных правок. В итоге пайплайн либо пропускает обновление, либо гоняет один и тот же документ по кругу. Время нужно нормализовать, хранить версию источника и не полагаться только на timestamp.
Вторая ошибка - переобрабатывать весь документ, если редактор поправил один абзац. Для больших архивов это дорого и медленно. Если документ разбит на чанки, обновляйте только те части, которые реально изменились, а не весь набор эмбеддингов. Иначе маленькая правка в сноске запускает лишнюю работу на минуты или часы.
Третья ошибка заметна не сразу: файл удалили в источнике, а его чанки остались в индексе. Пользователь уже не может открыть исходник, но поиск все еще находит цитаты из него. Так появляются "призраки" в выдаче. Tombstones нужны именно для этого: они явно помечают удаление и заставляют систему убрать все связанные части, а не только карточку документа.
Отдельно ломает схему смена прав доступа. Текст мог не меняться месяцами, но ACL изменился сегодня. Если индекс не получил это событие быстро, сотрудник увидит лишнее или, наоборот, потеряет доступ к нужным материалам. Для закрытых баз это уже инцидент, а не мелкая неисправность.
Еще одна ошибка - смешивать массовый импорт и срочные правки в одном потоке. Представьте: ночью система грузит 5 миллионов документов, а утром юристы правят один регламент, который нужен всем прямо сейчас. Если у вас одна очередь без приоритетов, срочное обновление встанет в хвост. Обычно лучше разделить хотя бы два потока: bulk и быстрый поток для удалений, прав доступа и точечных правок.
Хороший признак зрелой схемы простой: команда может объяснить, почему этот документ попал в индекс, какая версия у него сейчас, какие чанки удалены и когда обновились права доступа. Если на любой из этих вопросов нет быстрого ответа, ошибки уже копятся.
Пример для большого архива документов
Представьте архив договоров у банка или телеком-компании. В нем миллионы файлов, поиск по тексту и по чанкам уже работает, сотрудники с утра ищут актуальные версии приложений, тарифов и допсоглашений. Полный ночной прогон тут быстро становится лишней роскошью: он долгий, дорогой и легко уезжает в рабочее время.
Рабочий сценарий выглядит очень приземленно. Юрист меняет в договоре один раздел, например пункт про сроки оплаты. Система не трогает весь документ заново. Она сравнивает новую версию со старой, находит только затронутые чанки и пересчитывает индекс лишь для них.
Если договор был разбит на 120 чанков, а изменились 3, в работу уходят только эти 3. Остальные записи, эмбеддинги и метаданные остаются как есть. На больших массивах это экономит часы и не забивает GPU или CPU пустой работой.
С удалениями нужен другой подход. Допустим, сотрудник загрузил исправленный файл, а старый договор удалил. Если просто ждать фоновой очистки, старый текст еще какое-то время будет всплывать в поиске. Поэтому система сразу пишет tombstone - метку, что документ или его версия больше не должны участвовать в выдаче.
Поиск читает эту метку раньше, чем показывает результат. Из выдачи удаленный файл исчезает почти сразу, хотя физическую чистку хранилища можно сделать позже, в спокойном режиме.
Отдельная проблема приходит после массовой загрузки. Например, ночью в архив добавили 300 тысяч документов из филиалов. Очередь обновления индекса в такой момент легко раздувается, но нормальная схема переживает пик, если у нее есть приоритеты:
- срочные правки и удаления идут первыми;
- новые документы обрабатываются батчами;
- повторные сбои уходят в retry без ручной паники;
- тяжелые задачи не блокируют мелкие обновления.
За счет этого утренний поиск остается актуальным. Пользователь уже видит новый раздел в договоре, не видит удаленный файл и не ждет, пока система полностью переварит ночную загрузку.
Что проверить перед запуском и что делать дальше
Когда схема уже собрана, не спешите выкатывать ее в прод. Чаще всего систему ломает не сама дельта-индексация, а слепые зоны в контроле очередей, удалений и прав доступа.
Первый сигнал здоровья - не средняя задержка, а возраст самой старой задачи в очереди. Если одна задача висит 40 минут, а остальные проходят за секунды, индекс уже отстает от источника, даже если общий график выглядит терпимо. Эту метрику лучше вынести в отдельный алерт.
С удалениями тоже нужен жесткий тест. Возьмите один документ, удалите его в источнике и проверьте, что за один проход исчезают сам документ, его чанки, векторные представления и служебные записи. Если для этого нужны ручные скрипты или повторный прогон всей коллекции, схема еще сырая.
Права доступа лучше проверять отдельно от текста. Частая ошибка выглядит так: команда меняет ACL, а пайплайн гонит документ через полную переобработку, хотя содержимое не менялось. Гораздо спокойнее держать отдельный путь для permission updates, чтобы поиск и RAG не отдавали текст тем, кто уже не должен его видеть.
Перед запуском полезно пройтись по короткому списку:
- видно ли, сколько ждет самая старая задача;
- проходит ли удаление целиком без ручной чистки;
- обновляются ли права без повторной индексации текста;
- есть ли регулярная сверка индекса с исходным хранилищем;
- понятен ли порядок действий при отставании очереди.
Сверка с источником нужна даже в аккуратной архитектуре. Раз в день или раз в неделю сравнивайте выборку документов: идентификатор, версию, статус удаления, число чанков. Иначе тихие расхождения копятся неделями, а находят их обычно после жалобы пользователя.
После запуска лучше идти от малого к большому: включить схему на одном типе документов, собрать статистику по задержке и ошибкам, а потом расширять охват. Хороший пилот обычно показывает узкие места быстрее, чем еще одна неделя обсуждений.
Если вы строите RAG в РФ, рядом с индексом можно использовать RU LLM как совместимый с OpenAI API-шлюз. Для многих команд это удобный слой вызова моделей поверх уже собранного поиска: можно сменить base_url на api.rullm.com и оставить текущие SDK, код и промпты без изменений, а data residency, биллинг и поддержка останутся внутри РФ.
Часто задаваемые вопросы
Когда дельта-индексация лучше полного прогона?
Если в день меняется малая часть архива, берите дельта-индексацию. Она читает только новые и измененные документы, поэтому поиск быстрее догоняет источник, а CPU и GPU не тратятся на старый текст.
По какому полю лучше отслеживать изменения документа?
Сначала опирайтесь на version или checksum. Поле updated_at удобно, но оно часто врет из-за пересохранения, разных часовых поясов или ошибок источника. Если сомневаетесь, сверяйте checksum содержимого.
Нужно ли пересчитывать эмбеддинги для всего файла после правки одного абзаца?
Нет, весь документ гонять не надо. Если у вас есть чанки, найдите измененный фрагмент, пересчитайте эмбеддинги только для него и обновите связи с документом. Так маленькая правка не запускает лишнюю работу на весь файл.
Когда без полной переиндексации не обойтись?
Полный пересчет нужен после системных изменений. Например, вы сменили модель эмбеддингов, правила чанкинга, OCR или парсер. Еще он нужен там, где источник не хранит надежную версию документа и вы не можете понять, что реально изменилось.
Зачем нужен tombstone, если документ уже удалили из хранилища?
Потому что удаление в источнике не очищает индекс, векторное хранилище и кэши в ту же секунду. Tombstone сразу говорит слою поиска: этот документ больше не показывай. Потом фоновые воркеры спокойно дочищают остатки.
Как понять, сколько хранить tombstone?
Держите метку до тех пор, пока все потребители не подтвердят очистку. Если очередь отстает, воркер перезапустился или часть индекса поднялась из бэкапа, раннее удаление tombstone вернет старый документ в поиск.
Как очередь обновления защищает от старых версий в индексе?
Сохраняйте порядок событий для одного документа и проверяйте версию перед записью в индекс. Иначе старая задача на update может приехать после delete и вернуть удаленный файл в выдачу.
Нужно ли отдельно обрабатывать смену прав доступа?
Да, и лучше делать это отдельным путем. При смене ACL текст часто остается тем же, но выдача должна измениться сразу. Для банка, телекома или госсектора это вопрос не удобства, а контроля доступа и 152-ФЗ.
Какие метрики смотреть после запуска новой схемы?
Смотрите на возраст самой старой задачи в очереди, лаг между изменением и обновлением индекса, ошибки по типам событий и цену обработки одного документа. Эти метрики быстро показывают, где вы теряете время, деньги или точность поиска.
С чего начать внедрение без полной переиндексации?
Начните с одного понятного потока, например с базы регламентов или архива договоров. Зафиксируйте стабильный doc_id, версию, формат событий create, update, delete, включите tombstones и только потом подключайте очередь и частичный пересчет чанков.