Как я переехал на новую модель эмбеддингов и не уронил поиск

Как я переехал на новую модель эмбеддингов и не уронил поиск

Как я переехал на новую модель эмбеддингов и не уронил поиск

Понедельник, 09:47. В чат техподдержки пишет продажник: «Поиск по клиентам отвалился. Ищу поставщика гидравлики из Самары — отдаёт стоматологов и автосервисы». Я открываю мониторинг и вижу: за выходные выкатили новую модель, recall@5 на синтетике вырос, а на проде — полный кошмар.

Это классическая история миграции эмбеддингов. Тесты врут, пользователи ругаются, а индекс на полтора миллиона документов просто продолжает жить по-старому. Расскажу, как я теперь провожу такие переезды.

Зачем вообще менять модель

Эмбеддинги в проде живут годами. У меня был клиент, который два года сидел на paraphrase-multilingual-MiniLM-L12-v2. Потом понадобился поиск по техописаниям с цифрами и по смешанному русско-английскому тексту. Старая модель начала проседать.

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

Почему «выключить и переиндексировать» — плохая идея

Самый простой план — ночью пересчитать все векторы и перестроить индекс. На практике у меня это заняло 4+ часа, куча ошибок парсинга и race condition: пока шёл пересчёт, новые документы продолжали индексироваться старой моделью и потом затирались.

После такого инцидента я выработал правило: никогда не перезаписывать эмбеддинги на месте.

Двухколоночная схема

Теперь я всегда добавляю новую колонку:

ALTER TABLE documents
    ADD COLUMN embedding_v2 vector(1024),
    ADD COLUMN embedding_v2_model text;

Старая колонка остаётся в покое. Новый воркер фоном заполняет embedding_v2. Все свежие документы сразу пишутся в обе колонки. Да, на время миграции расход на эмбеддинги растёт вдвое, но это плата за спокойствие.

Теневой режим вместо синтетики

Перед переключением я запускаю shadow-трафик на две недели. Каждый запрос уходит и в старую, и в новую колонку. Логирую overlap@5, recall по реальным кликам пользователя и latency.

На одном проекте синтетика обещала +13 пунктов, а по реальным данным получилось +5, при этом overlap был всего 0.41. Это сразу показало: просто так кнопку жать нельзя.

Постепенный rollout и откат

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

Старую колонку держу ещё месяц-два на всякий случай.

Что я вынес

Эмбеддинги — это не просто параметр, а актив в базе. К нему нужно относиться как к схеме: версионировать, мигрировать постепенно и всегда иметь откат. Три недели работы и +30–40 % расходов на время миграции, но зато не приходится объяснять продажникам, почему поиск внезапно стал хуже.