Перейти к содержанию

Обновление, удаление и смена модели

Наш поиск читает только активную ревизию документа и выбранное пространство векторов. Теперь рассмотрим три обычных события в библиотеке: редактор изменил текст, убрал материал из выдачи или выбрал другую модель эмбеддингов. Каждое событие меняет доступность источников, но требует своего перехода. Простое удаление файла не описывает все эти случаи.

В состоянии lesson-10 появляется maintenance.py. Сохраняем предыдущий импорт и добавляем две операции: скрытие документа и удаление его неактивных ревизий. Операции автором не выполнялись. Работать с ними следует только в отдельной учебной базе после понимания того, какие записи они затрагивают.

Изменение текста создаёт новую ревизию

Второй урок связал ревизию с документом, алгоритмом разбиения и пространством эмбеддингов. Если редактор исправил абзац, расчёт должен дать другой идентификатор. Импорт сначала готовит новые фрагменты и векторы, затем переключает указатели в транзакции. Пока подготовка не закончена, поиск продолжает использовать прежний набор.

Это удобно для исправления объяснения про кэш. Представим, что к старому тексту добавлено условие о приватных ответах. Нельзя показать новые метаданные с прежними фрагментами или смешать начало старой ревизии с окончанием новой. Активные указатели определяют, какой набор считается действующим.

При ошибке подготовки старый документ остаётся доступным. Но такой договор подходит не для любого изменения. Если старое содержание срочно нужно убрать из-за ошибочного разрешения, ожидание успешного импорта может быть недопустимо. Тогда сначала скрывают документ, а исправленную ревизию активируют отдельным решением.

Наша лаборатория предполагает одного оператора импорта. Блокировка строки предотвращает одновременную неполную запись, но не устанавливает, какой исходный файл новее. Поздно завершившийся импорт старого файла может переключить указатель назад. Для параллельных редакторов нужны очередь событий, номер изменения или условное переключение относительно ожидаемой ревизии.

Скрытие и физическое удаление

Функция tombstone ставит deleted=true. Поисковые запросы уже содержат условие, исключающее такие документы. Текст остаётся в базе, но не участвует в новой выдаче. Это быстрый логический переход, который можно рассматривать отдельно от очистки истории.

Логическое скрытие не означает уничтожения данных. Они могут оставаться в таблицах, копиях базы, журнале изменений, ранее сформированном ответе или сохранённой странице. Если задача требует полного удаления определённых данных, нужен отдельный процесс с описанными хранилищами и сроками. Наш флаг решает только доступность для последующих запросов лаборатории.

from db import connect
def tombstone(doc_id):
    with connect() as conn:
        return conn.execute('UPDATE documents SET deleted=true WHERE doc_id=%s RETURNING doc_id',(doc_id,)).fetchone()
def prune_inactive(doc_id):
    with connect() as conn:
        conn.execute('SELECT doc_id FROM documents WHERE doc_id=%s FOR UPDATE',(doc_id,)).fetchone()
        return conn.execute('DELETE FROM revisions r USING documents d WHERE r.doc_id=d.doc_id AND r.doc_id=%s AND (r.revision IS DISTINCT FROM d.active_revision OR r.space IS DISTINCT FROM d.active_space) RETURNING r.revision',(doc_id,)).fetchall()

Если идентификатор неизвестен, операция не получает строку результата. Это отличается от успешного скрытия существующего документа. При подготовке интерфейса оператора стоит показывать эти состояния отдельно. Иначе опечатка в идентификаторе может выглядеть как завершённое изменение.

Отсутствие документа в следующем corpus.json само по себе не вызывает tombstone. Мы намеренно не считаем каждый неполный входной файл полным перечнем библиотеки. Импорт малого поднабора не должен случайно скрывать все остальные статьи. Для синхронизации полного корпуса нужен явный договор полноты и отдельное подтверждение списка исчезнувших документов.

Повторный импорт скрытого документа

Импорт устанавливает deleted=false, когда активирует подготовленную ревизию. Следовательно, явный импорт скрытого документа возвращает его в выдачу, даже если содержание совпадает с прежним. Это выбранное поведение лаборатории, которое оператор обязан учитывать.

Нельзя автоматически отправлять весь старый корпус после ручного скрытия и рассчитывать, что запрет сохранится. Если в настоящем проекте архивные документы должны оставаться скрытыми, состояние публикации следует хранить отдельно от содержания и сделать его изменение самостоятельной операцией. Политика не должна зависеть от случайного порядка запуска скриптов.

Пример восстановления должен включать разрешения. Возвращая документ, проверьте не только заголовок и URL, но и visibility. Служебный текст с правильным содержимым всё равно не должен становиться публичным из-за неверного поля. В нашей схеме изменение метаданных происходит вместе с активной ревизией.

Очистка неактивной истории

Функция prune_inactive блокирует строку документа и удаляет ревизии, которые не совпадают с действующими указателями. Связанные фрагменты удаляются через внешний ключ с ON DELETE CASCADE. Активная ревизия должна остаться, потому что именно её читает поиск.

Блокировка согласована с импортом, который тоже блокирует эту строку перед переключением. Она сокращает риск очистить набор, пока другой процесс собирается назначить его действующим. Это локальный договор наших функций; запись напрямую в таблицы или другой порядок блокировок могут его нарушить.

После очистки история перестаёт быть средством возврата к прежнему тексту. Поэтому до операции нужно решить, где хранится исходник соответствующей версии и как восстановить индекс. Для Markdown-библиотеки таким источником может быть конкретная ревизия репозитория, но один только Git не сохраняет артефакт модели или состояние базы.

Замена модели меняет пространство

Переход на другой digest эмбеддингов создаёт другое значение space, даже если размерность осталась 768. Нельзя сравнивать новый вопрос со старыми векторами и называть результат совместимым. Совпадение числа координат говорит о форме, а не о смысле координат.

Для небольшой лаборатории удобно полностью подготовить новый корпус, записать выбранную конфигурацию и затем использовать её для запросов. Пока документы переключаются по одному, новый поиск может видеть лишь часть библиотеки. Если нужна атомарная смена всей коллекции, добавьте отдельный указатель поколения корпуса и активируйте его после подготовки всех документов.

Старая модель ответа и новая модель эмбеддингов также решают разные задачи. Изменение генератора может повлиять на формулировки без изменения результатов поиска. Поэтому запись опыта должна различать версии разбиения, пространства поиска, генератора и правил контекста. Иначе причину изменения ответа трудно установить.

Ожидаемое состояние после урока — понятные отдельные переходы: новая ревизия активируется целиком, скрытый документ не попадает в новые запросы, очистка затрагивает лишь неактивную историю. На следующем шаге подготовим вопросы, которые позволят читателю самостоятельно оценивать эти свойства вместе с качеством поиска, не подменяя наблюдения выдуманными числами.

Исходники и документация

Оглавление серии · Все состояния проекта. Для этого урока откройте папку lesson-10.

pgvector, транзакции Psycopg, Ollama Embed, структурированные ответы.

Примеры подготовлены как исходники. Их выполнение, замеры, тесты и публикация при написании не проводились. Описанные результаты ожидаемые.