Смена векторной базы: перенос эмбеддингов без переиндексации
Вы подняли RAG на pgvector, а через полгода упёрлись в производительность или неудобный API — и хочется переехать на Qdrant или Milvus. Первая мысль пугает: неужели придётся заново прогонять сотни тысяч документов через модель эмбеддингов, платя за это временем и деньгами? Нет — если модель эмбеддингов не меняется, векторы можно перенести напрямую, без единого обращения к модели. Разберём, что переносится бесплатно, а что придётся всё равно делать заново.
Содержание
Почему векторы вообще можно перенести без пересчёта
Эмбеддинг — это просто массив чисел фиксированной длины (для многих моделей — 384, 768 или 1536 float-значений), который модель один раз посчитала для куска текста. Дальше этот массив живёт своей жизнью: он не «принадлежит» pgvector, Qdrant или Milvus — это просто данные, которые база хранит и по которым умеет искать ближайших соседей. СУБД не участвует в вычислении смысла, она только индексирует уже готовые числа.
Отсюда и вывод: если у вас есть возможность выгрузить эти числа из старой базы в каком-то переносимом формате (JSON, Parquet, NumPy-массив, CSV), вы можете загрузить их в новую базу как есть. Модель эмбеддингов в этой операции не участвует вообще — ни разу не вызывается. Экономия ощутимая: пересчёт эмбеддингов для, скажем, 500 000 фрагментов через API вроде OpenAI или через локальную модель — это часы работы и заметный счёт (для платных API) или ощутимая нагрузка на GPU/CPU (для локальных моделей). Перенос готовых векторов — на порядки быстрее: по сути, копирование файла и массовая вставка в новую таблицу или коллекцию.
Важно понимать границу: переносимость — свойство самих чисел, а не «совместимости» между базами. Ни одна пара векторных СУБД не имеет прямого протокола миграции друг в друга «из коробки» — перенос всегда идёт через промежуточный дамп, который вы контролируете сами.
Критическое условие: модель эмбеддингов должна остаться той же
Здесь нужно расставить акценты предельно чётко, потому что это самая частая путаница. Всё сказанное выше работает только если модель эмбеддингов не меняется. Смена СУБД (pgvector → Qdrant, Qdrant → Milvus, Chroma → Weaviate) — это перенос хранилища. Смена модели эмбеддингов (например, text-embedding-3-small → text-embedding-3-large, или одна модель на HuggingFace → другая, или даже смена версии одной и той же модели с изменившимися весами) — это совершенно другая операция, и переноса без пересчёта тут не бывает никогда.
Причина в математике, а не в инженерии. Разные модели эмбеддингов обучались на разных данных, разными методами, и производят векторы в принципиально разных, несовместимых координатных пространствах. У модели A вектор для фразы «настроить бэкап» может быть похож (по косинусному расстоянию) на вектор для «сделать резервную копию». У модели B те же два вектора могут оказаться в совершенно разных областях пространства — не потому что B «хуже», а потому что оси её пространства заданы иначе. Расстояния между векторами из разных моделей математически не сопоставимы: смешивать в одном индексе эмбеддинги от двух моделей — гарантированный мусор на выходе поиска, даже если размерность векторов случайно совпадает.
Поэтому если вместе со сменой СУБД вы решили заодно перейти на другую (более новую, более точную, более дешёвую) модель эмбеддингов — это уже не тема данной статьи: там нужен полный пересчёт всего корпуса документов через новую модель, и переносить в этом случае просто нечего. Подробнее о выборе модели см. статью про выбор эмбеддинг-модели для RAG. Ниже мы говорим только о случае «модель та же, база другая».
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть ИИ на сервереЧто переносится напрямую, а что нужно строить заново
Стоит разделить три слоя данных, потому что с ними обращаются по-разному:
| Слой | Переносится напрямую? | Что происходит при переезде |
|---|---|---|
| Сырые векторы (числа) | Да | Копируются как есть в новую базу |
| Метаданные (текст фрагмента, id документа, источник) | Да | Копируются синхронно с векторами |
| Индекс приближённого поиска (ANN) | Нет | Строится заново на новой СУБД |
Первые два слоя — это данные. Третий — структура, которую конкретная СУБД строит поверх данных для быстрого поиска. Разные векторные базы используют разные алгоритмы приближённого поиска ближайших соседей (ANN): HNSW (Hierarchical Navigable Small World) — самый распространённый, используется в Qdrant, Weaviate, pgvector; IVF (Inverted File Index) с квантованием — характерен для Milvus и FAISS.
Даже когда обе базы используют один и тот же алгоритм, внутреннее представление графа — не переносимая структура: параметры построения (m, ef_construction у HNSW), формат хранения связей, оптимизации под конкретный движок — всё специфично для реализации. Граф из pgvector в Qdrant напрямую не переносят — переносят векторы, а граф строит заново новая база, обычно при вставке данных или отдельной командой переиндексации внутри самой СУБД.
Хорошая новость: построение ANN-индекса — операция на порядки быстрее пересчёта эмбеддингов моделью. Она не требует обращения к модели, почти никогда не требует GPU и упирается в CPU и память самой базы. Для сотен тысяч векторов это обычно минуты, максимум десятки минут — в отличие от часов пересчёта через API или нагрузки на GPU/CPU для локальной модели. Точное время зависит от объёма, размерности и мощности сервера — не берите цифры как гарантию для вашего случая.
Почему метаданные нельзя переносить отдельно от векторов
Вектор сам по себе бесполезен без контекста. Если у вас есть массив чисел [0.021, -0.184, 0.097, ...], но нет привязанного к нему текста фрагмента, источника документа и, желательно, позиции в документе — вы получили результат поиска «похоже на что-то, но не знаем на что». Именно метаданные превращают вектор обратно в осмысленный ответ: это они попадают в промпт модели как найденный контекст, а не сами числа.
Поэтому при экспорте из старой базы каждая запись должна тащить за собой связку id → вектор → метаданные как единое целое, а не как два отдельных файла, которые потом придётся сопоставлять по порядку строк (это частый источник багов: одна база отдаёт записи в порядке вставки, другая — в произвольном, и если экспортировать векторы и метаданные раздельно, а потом просто «склеить по индексу», связка почти наверняка съедет).
Практическое правило: экспортируйте одной операцией на запись — либо построчный JSON (JSONL), где в одном объекте лежат id, вектор и все поля метаданных, либо таблицу/датафрейм, где вектор — это одна из колонок наравне с текстом и источником. Так связка физически не может разъехаться, потому что она никогда не разделялась на два потока.
Пошаговый план переноса
- Инвентаризация метаданных старой базы. Прежде чем экспортировать, выпишите точную схему: какие поля хранятся у каждого вектора (текст фрагмента,
document_id,source_urlили путь к файлу, номер чанка, дата индексации). Если поле есть в старой базе, оно должно попасть в дамп.
- Экспорт векторов + метаданных в промежуточный формат. Разберём на конкретном сценарии: RAG на PostgreSQL с pgvector, около 200 000 фрагментов, эмбеддинги размерностью 768, переезд на Qdrant ради более быстрого поиска. Экспорт из pgvector (вектор хранится в типе
vector, при экспорте в CSV превращается в строку[0.012,-0.045,...], которую нужно распарсить при импорте):
COPY (
SELECT id, embedding::text AS vec, chunk_text, document_id, source, chunk_position
FROM documents ORDER BY id
) TO '/tmp/pgvector_dump.csv' WITH (FORMAT csv, HEADER true);
Для Qdrant экспорт делают снапшотом коллекции или постраничной выгрузкой через API scroll; для Milvus — через query() с явным указанием полей и пагинацией (offset/limit), потому что разовый экспорт миллионов записей упирается в лимиты одного запроса.
- Проверка целостности дампа. Сравните число записей в дампе со счётчиком в исходной базе (
SELECT COUNT(*)для pgvector, статистика коллекции для Qdrant/Milvus). Расхождение — сигнал, что экспорт оборвался или пагинация настроена неверно.
- Импорт в новую базу. Создайте коллекцию с параметрами, совпадающими с исходными векторами: та же размерность, та же метрика расстояния (косинусное, евклидово или скалярное произведение — она должна совпадать с той, на которой считались векторы, иначе релевантность сломается даже при идеально перенесённых числах). Скрипт разбора дампа и загрузки в Qdrant пачками:
import csv, ast
from qdrant_client import QdrantClient
from qdrant_client.models import PointStruct, VectorParams, Distance
client = QdrantClient(url="http://localhost:6333")
client.create_collection(
"docs", vectors_config=VectorParams(size=768, distance=Distance.COSINE)
)
batch, batch_size = [], 500
with open("/tmp/pgvector_dump.csv") as f:
reader = csv.DictReader(f)
for row in reader:
vector = ast.literal_eval(row["vec"]) # "[0.01,...]" -> список float
batch.append(PointStruct(
id=int(row["id"]), vector=vector,
payload={"chunk_text": row["chunk_text"], "document_id": row["document_id"],
"source": row["source"], "chunk_position": row["chunk_position"]},
))
if len(batch) >= batch_size:
client.upsert(collection_name="docs", points=batch)
batch = []
if batch:
client.upsert(collection_name="docs", points=batch)
Загружайте пачками, а не по одной записи — так в разы быстрее и меньше нагружает базу. Для 200 000 записей это обычно занимает от нескольких минут до пары десятков минут в зависимости от железа — не считайте это гарантией для вашего объёма. Модель эмбеддингов в скрипте не вызывается ни разу.
- Построение ANN-индекса на новой базе. В большинстве СУБД индекс строится автоматически при вставке (Qdrant, Weaviate) или требует явной команды после массовой загрузки (некоторые конфигурации Milvus, где выгоднее сначала залить все данные, а индекс построить одной операцией
create_index). Проверьте, какой режим у вашей версии базы по умолчанию.
- Тестирование через контрольные запросы. Возьмите 10-20 реальных вопросов с заранее известным ожидаемым результатом (в идеале — сохранённый топ-3/топ-5 из старой базы на каждый запрос). Прогоните их через новую базу и сравните. Небольшие расхождения в ранжировании — нормальны, разные ANN-реализации ищут приближённо, а не точно. Резкое падение релевантности — сигнал, что разъехались метаданные, метрика расстояния или сами векторы попали не полностью.
- Параллельная работа старой базы до подтверждения. Не выключайте старую базу сразу. Держите её в режиме read-only некоторое время (неделя-две в проде — разумный ориентир), пока не убедитесь, что новая база стабильно отдаёт релевантные результаты на живом трафике, а не только на заготовленных тестах.
Если у вас ещё нет такой связки RAG на своём сервере, процесс с нуля собран в статье про RAG-конвейер с нуля, а принцип превращения текста в координаты — в материале про эмбеддинги и смысл.
Частые ошибки при переносе
- Смена размерности вектора «на глаз». Если в новой коллекции указана размерность 1536, а реальные векторы — 768, импорт либо упадёт с ошибкой, либо молча обрежет/дополнит вектор нулями — и поиск станет бессмысленным без явной ошибки.
- Несовпадение метрики расстояния. Вектор, нормализованный под косинусную метрику, при поиске по евклидовому расстоянию даёт другой порядок результатов. Проверяйте, под какую метрику оптимизирована модель эмбеддингов.
- Экспорт без пагинации на больших объёмах. Разовый запрос на выгрузку миллиона записей упирается в лимиты API или таймаут — выгружайте постранично и сверяйте итоговое число записей.
- Отсутствие эталонных запросов «до» переноса. Без сохранённых результатов старой базы сравнивать после переноса будет не с чем.
- Удаление старой базы сразу после переноса. Даже успешный тест на заготовленных запросах не гарантирует, что реальный трафик не выявит непокрытые кейсы.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть ИИ на сервереНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли перенести эмбеддинги, если старая база уже не работает и снапшота нет?
Только если сырой дамп векторов остался где-то ещё (бэкап, экспорт из пайплайна расчёта эмбеддингов). Без доступа к самим числам вариант один — пересчитать эмбеддинги заново той же моделью, если она всё ещё доступна.
Что если изменилась версия модели эмбеддингов, а не сама модель?
Многие провайдеры документируют, что зафиксированная версия (например, text-embedding-3-small) даёт стабильные векторы, а «тихое» изменение весов под тем же именем — редкость. Если не уверены — считайте, что векторы могли измениться, и сверьтесь с документацией провайдера.
Нужно ли переносить все метаданные, даже неиспользуемые поля?
Не обязательно — перенесите то, что реально используется в RAG-конвейере (текст фрагмента, источник, позиция). Служебные поля старой СУБД можно не тащить.
Что делать, если новая база не поддерживает ту же метрику расстояния?
Проверьте, можно ли перенормировать векторы под доступную метрику (косинусное сходство эквивалентно скалярному произведению на L2-нормализованных векторах) — это операция над уже посчитанными числами, а не пересчёт эмбеддингов моделью.
Стоит ли переносить старый ANN-индекс, если формат похож?
Нет — внутренний формат индекса привязан к конкретной реализации СУБД, совместимости между движками (и часто между версиями одного движка) нет. Всегда стройте индекс заново из перенесённых векторов.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →