pgvector: вектора прямо в PostgreSQL, без отдельной базы
Если в проекте уже крутится PostgreSQL с пользователями, заказами и обычным контентом, а теперь понадобился векторный поиск для RAG или рекомендаций — не спешите поднимать рядом Qdrant или Milvus. pgvector добавляет тип данных для векторов и операторы поиска ближайших соседей прямо в существующую базу, одним расширением. Ниже — как это работает на практике, с реальными SQL-командами, и когда такого решения действительно достаточно, а когда пора смотреть в сторону специализированной СУБД.
Содержание
Зачем добавлять векторы в реляционную базу, а не рядом
Стандартный путь под RAG или семантический поиск — завести отдельную векторную базу: Qdrant, Milvus, Weaviate, Chroma. Это рабочий вариант, но он тянет за собой вторую систему хранения данных в архитектуре, где уже есть первая — обычно PostgreSQL под пользователей, заказы, каталог, логи. Две базы означают две вещи, которые надо обслуживать параллельно: согласованность данных (что делать, если документ обновился в Postgres, а его эмбеддинг в векторной базе — ещё нет) и операционную нагрузку (бэкапы, мониторинг, обновления, права доступа — всё х2).
pgvector решает именно эту проблему упрощением, а не производительностью. Расширение добавляет в PostgreSQL нативный тип vector и операторы расстояния — эмбеддинг документа хранится в той же строке, что и его текст, автор, дата создания и любые другие реляционные поля. Обновили документ — обновили и вектор одним UPDATE в той же транзакции. Удалили запись — вектор исчез вместе с ней, никакого рассинхрона по определению, потому что это одна и та же строка одной и той же таблицы.
Для проекта, где нет экстремальной нагрузки на векторный поиск (не миллиарды векторов и не тысячи запросов в секунду к поиску по эмбеддингам), это чаще всего разумный компромисс: меньше движущихся частей, меньше точек отказа, меньше того, что может разъехаться между двумя базами данных.
Установка расширения
pgvector — это расширение PostgreSQL, а не отдельный сервис. На Ubuntu/Debian с подключённым репозиторием PGDG (см. установку PostgreSQL на VPS) пакет ставится штатным менеджером пакетов — имя пакета зависит от версии PostgreSQL:
sudo apt update
sudo apt install postgresql-16-pgvector
Если версия PostgreSQL другая — замените 16 на свою (postgresql-15-pgvector, postgresql-17-pgvector и так далее). Если готового пакета под вашу версию/дистрибутив нет, расширение собирается из исходников — репозиторий проекта на GitHub (pgvector/pgvector) содержит Makefile, сборка занимает пару минут при наличии postgresql-server-dev-<версия> и стандартных инструментов сборки (build-essential):
sudo apt install postgresql-server-dev-16 build-essential git
git clone --branch v0.8.0 https://github.com/pgvector/pgvector.git
cd pgvector
make
sudo make install
Версию тега (v0.8.0 в примере) стоит свериться с актуальным релизом в репозитории на момент установки — проект развивается, и точный номер последней версии лучше смотреть на странице releases, а не полагаться на цифру из статьи.
Если PostgreSQL развёрнут в Docker — проще взять готовый образ с уже вкомпилированным расширением вместо сборки внутри контейнера:
docker run -d --name pg-vector \
-e POSTGRES_PASSWORD=changeme \
-p 5432:5432 \
pgvector/pgvector:pg16
После установки пакета или образа расширение всё ещё нужно включить — на уровне конкретной базы данных, не сервера целиком:
CREATE EXTENSION IF NOT EXISTS vector;
Выполняется один раз в каждой базе, где нужны векторные колонки. Проверить, что расширение подключилось, можно так:
SELECT extname, extversion FROM pg_extension WHERE extname = 'vector';
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть ИИ на сервереПрактический пример: документы и эмбеддинги в одной таблице
Ключевая идея pgvector — колонка типа vector(N), где N — размерность эмбеддинга конкретной модели (для основ работы с самими эмбеддингами и тем, что означает эта размерность, см. как embedding превращает смысл в координаты). Заводим обычную таблицу документов с добавленной колонкой вектора:
CREATE TABLE documents (
id bigserial PRIMARY KEY,
title text NOT NULL,
body text NOT NULL,
author_id bigint REFERENCES users(id),
created_at timestamptz NOT NULL DEFAULT now(),
embedding vector(1536)
);
Размерность 1536 здесь — под конкретную модель эмбеддингов (одна из моделей семейства OpenAI text-embedding использует именно такую размерность на момент написания; для других моделей — своё число, обычно 384, 768 или 1024, уточняйте в документации конкретной модели). Важно, что в этой же таблице живут вполне обычные реляционные поля — заголовок, автор, дата, внешний ключ на пользователей. Никакой отдельной сущности «векторное хранилище» не появляется.
Вставка строки — тоже обычный INSERT, эмбеддинг передаётся как массив чисел в квадратных скобках (в реальном коде это результат вызова модели эмбеддингов, а не ручной ввод):
INSERT INTO documents (title, body, author_id, embedding)
VALUES (
'Как настроить резервное копирование PostgreSQL',
'Полный текст статьи...',
42,
'[0.0123, -0.0456, 0.0789, ...]'
);
На практике вектор в приложении обычно приходит из клиентской библиотеки (Python, Node.js) как массив float, а драйвер PostgreSQL (psycopg, pg с расширением pgvector) сериализует его в нужный формат сам — вручную строку с числами собирать не приходится.
Поиск ближайших соседей — это обычный SELECT с сортировкой по оператору расстояния и LIMIT:
SELECT id, title, embedding <-> '[0.011, -0.04, 0.081, ...]' AS distance
FROM documents
ORDER BY embedding <-> '[0.011, -0.04, 0.081, ...]'
LIMIT 5;
Никакого отдельного API, никакого другого протокола — та же база, тот же SQL-клиент, тот же connection pool, что и для остальных запросов приложения.
Операторы расстояния и индексы
pgvector даёт три оператора расстояния, и выбор между ними зависит от того, как считалась похожесть в вашей модели эмбеддингов:
| Оператор | Метрика | Когда использовать |
|---|---|---|
<-> | Евклидово расстояние (L2) | Общий случай, если модель не заточена под косинус |
<=> | Косинусное расстояние | Большинство современных моделей эмбеддингов (OpenAI, многие open-source) |
<#> | Отрицательное скалярное произведение | Когда важна скорость и векторы нормализованы |
Без индекса запрос ORDER BY ... LIMIT выполняет полный перебор (sequential scan) — на таблице в тысячи строк это ещё терпимо, но на сотнях тысяч и выше запросы начинают заметно тормозить. pgvector поддерживает два типа индексов приближённого поиска:
-- HNSW: обычно лучше по качеству поиска и скорости чтения,
-- дороже по времени построения и по памяти
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);
-- IVFFlat: быстрее строится, требует ANALYZE после наполнения таблицы
-- и подбора параметра lists под объём данных
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
Суффикс _cosine_ops в обоих индексах должен соответствовать оператору, который вы используете в запросах (_l2_ops — под <->, _ip_ops — под <#>). Несовпадение — частая причина, почему индекс существует, но запрос его не использует и по-прежнему делает Seq Scan: проверяйте это через EXPLAIN ANALYZE на реальном запросе.
Оба индекса — приближённые (approximate nearest neighbor), то есть результат может изредка отличаться от точного перебора на пару позиций в выдаче — это осознанный компромисс ради скорости, а не баг. Для большинства сценариев RAG и рекомендаций разница не заметна пользователю.
Честное сравнение со специализированными векторными базами
Здесь стоит остановиться подробнее, потому что маркетинг вокруг pgvector иногда обещает больше, чем расширение реально даёт. Специализированные векторные СУБД — Qdrant, Milvus, Weaviate — строились с нуля именно под векторный поиск в больших объёмах, и это даёт им реальные преимущества на масштабе (подробнее о том, какую базу и по каким критериям выбирать, — в статье про выбор специализированной векторной базы):
- Масштаб. Специализированные базы изначально проектировались под шардирование десятков и сотен миллионов векторов по нескольким узлам. pgvector такое умеет через штатные механизмы PostgreSQL (партиционирование, репликация), но это требует ручной настройки, тогда как у Qdrant или Milvus распределённый режим — часть коробочной функциональности.
- Специализированные алгоритмы фильтрации. Поиск с одновременной фильтрацией по метаданным (например, «ближайшие соседи среди документов конкретного автора за последний месяц») в специализированных базах обычно оптимизирован эффективнее, чем комбинация HNSW-индекса и обычного
WHEREв PostgreSQL на очень больших таблицах. - Инструменты эксплуатации под конкретно векторный поиск. Встроенный мониторинг качества поиска, hybrid search (комбинация векторного и полнотекстового поиска) из коробки, квантование векторов для экономии памяти — во многих специализированных базах это готовые фичи, в pgvector — то, что придётся собирать самому или искать сторонние расширения.
Но у этих преимуществ есть цена — вторая система хранения данных в архитектуре, со своим API, своим протоколом бэкапов, своим мониторингом и необходимостью держать данные (или хотя бы их метаданные) синхронизированными с основной базой. Если у вас нет десятков миллионов векторов и запросы к поиску идут не тысячами в секунду — эта цена обычно не окупается выигрышем в производительности, которого вы на таком масштабе даже не заметите. Если всё же переходите со специализированной базы на pgvector или обратно — сами эмбеддинги переносятся без пересчёта, если модель не меняется (детали — в статье про смену векторной базы и перенос эмбеддингов).
Когда pgvector достаточно, а когда пора переезжать
Практический ориентир: если PostgreSQL уже часть стека, а объём эмбеддингов измеряется десятками или сотнями тысяч (не десятками миллионов), pgvector — разумная отправная точка. Внедрить его — это одна команда CREATE EXTENSION и одна дополнительная колонка в существующей таблице, а не развёртывание, настройка и обучение команды новой системе.
Признаки, что стоит присмотреться к специализированной базе, — не гипотетические, а фактически измеренные на вашей нагрузке:
EXPLAIN ANALYZEпоказывает, что векторные запросы стабильно упираются в время отклика, неприемлемое для продукта, даже после подбора индекса и его параметров;- объём векторов вырос настолько, что индекс HNSW/IVFFlat перестал помещаться в
shared_buffersи активно уходит на диск при каждом запросе; - нужен hybrid search или сложная фильтрация по метаданным в реальном времени, и штатными средствами PostgreSQL это не решается приемлемо.
Пока ни один из этих пунктов не подтверждён на реальных данных вашего проекта — переезд на отдельную векторную базу «на всякий случай» добавляет операционную сложность без ощутимой пользы. Переходите, когда упрётесь в конкретное измеримое ограничение, а не превентивно.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть ИИ на сервереНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Нужна ли отдельная версия PostgreSQL под pgvector?
Нет, расширение ставится поверх обычного PostgreSQL (актуальные версии поддерживают довольно широкий диапазон релизов сервера — уточняйте совместимость на странице проекта под конкретную версию PostgreSQL и pgvector).
Можно ли хранить несколько эмбеддингов на одну строку?
Да, просто заведите несколько колонок типа vector(N) — например, отдельно эмбеддинг заголовка и эмбеддинг тела документа, если это нужно бизнес-логике.
Что будет, если вставить вектор неправильной размерности?
PostgreSQL вернёт ошибку на уровне ограничения типа — колонка vector(1536) не примет вектор другой длины, это защищает от случайного смешения эмбеддингов разных моделей в одной таблице.
Нужен ли ANALYZE после создания индекса?
Для IVFFlat — да, обязательно после того, как таблица уже наполнена реальными данными (индекс строится на основе кластеризации существующих данных). Для HNSW отдельный ANALYZE не требуется — индекс достраивается по мере вставки.
Работает ли pgvector с обычными репликами PostgreSQL?
Да, векторная колонка реплицируется тем же механизмом, что и остальные данные — потоковая репликация не делает разницы между типами колонок.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →