pgvector — расширение PostgreSQL, которое добавляет типы для векторов и поиск ближайших соседей. Оно нужно, когда embeddings появляются как ещё одно поле: семантический поиск, рекомендации, похожие документы, дедупликация и RAG. Вместо отдельной векторной базы остаются транзакции, фильтры, JOIN, репликация и резервное копирование. В 2026 году pgvector практичен для миллионов векторов на одном узле; для миллиардов нужны шардирование и отдельная инфраструктура.

Зачем векторы в обычной СУБД

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

Преимущество pgvector — совместимость. Можно отфильтровать документы по правам, дате или категории, соединить с обычными таблицами и отдать всё в одной транзакции. Резервное копирование и репликация остаются привычными. Отдельная векторная база оправдана при объёмах, требующих шардирования.

Установка расширения

Проще поставить пакет из PGDG. Имя зависит от версии PostgreSQL: для 17-й это postgresql-17-pgvector. Если пакета нет, расширение собирают из исходников: нужны заголовки сервера и компилятор.

sudo apt install postgresql-17-pgvector
sudo -u postgres psql -c "CREATE EXTENSION vector;"
sudo -u postgres psql -c "SELECT extversion FROM pg_extension WHERE extname = 'vector';"

Расширение включается в каждой базе отдельно. Версию фиксируйте в миграциях и проверяйте после обновления сервера. При сборке из исходников используйте make и make install, затем CREATE EXTENSION vector;. Для обновления версии есть ALTER EXTENSION vector UPDATE;.

Таблица и типы данных

Основной тип — vector(n), где n — размерность модели. Для компактного хранения есть halfvec(n), для бинарных хешей — bit(n), для разреженных данных — sparsevec(n). Размерность столбца должна совпадать с запросами.

CREATE TABLE documents (
  id bigserial PRIMARY KEY,
  title text NOT NULL,
  category_id int NOT NULL,
  embedding vector(3) NOT NULL
);
INSERT INTO documents (title, category_id, embedding)
VALUES ('Первый документ', 10, '[0.12, 0.44, 0.91]');
SELECT id, title, vector_dims(embedding) FROM documents;

В примере размерность 3, чтобы код был коротким. В проекте ставьте 768, 1024 или 1536 — столько, сколько выдаёт модель. Векторы загружаются через COPY, обновляются через UPDATE и вставляются с ON CONFLICT. Нулевые векторы не индексируются для косинуса, а NULL пропускается индексом.

Расстояния и точный поиск

Операторы запоминаются быстро: <-> — евклидово расстояние, <=> — косинусное, <#> — отрицательное скалярное произведение, <+> — манхэттенское. Для косинусной близости из расстояния вычитают единицу.

SELECT id, title,
       1 - (embedding <=> '[0.10, 0.40, 0.90]') AS cosine_similarity
FROM documents
ORDER BY embedding <=> '[0.10, 0.40, 0.90]'
LIMIT 5;

Без индекса PostgreSQL перебирает все строки и даёт точный результат. Для индекса нужны ORDER BY по оператору и LIMIT; выражение 1 - distance уже не подойдёт.

Индексы HNSW и IVFFlat

HNSW строит многослойный граф и даёт лучшее соотношение скорости и полноты, но требует памяти. IVFFlat делит векторы на списки, быстрее строится и меньше ест памяти, но зависит от объёма данных.

SET maintenance_work_mem = '2GB';
CREATE INDEX CONCURRENTLY documents_embedding_hnsw
  ON documents USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 64);
SET hnsw.ef_search = 100;

m задаёт число связей, ef_construction — список кандидатов. Выше значения — лучше recall и дольше сборка. hnsw.ef_search управляет компромиссом при запросе. Для IVFFlat индекс создают после загрузки, число списков близко к rows / 1000.

HNSW поддерживает vector до 2000 измерений, halfvec — до 4000. Если модель выдаёт 3072, используйте halfvec. CREATE INDEX CONCURRENTLY не блокирует запись, но не работает в транзакции.

Фильтры и гибридный поиск

С approximate-индексом WHERE применяется после обхода графа. Если фильтр оставляет мало строк, результатов может не хватить. С версии 0.8.0 есть итеративное сканирование: индекс продолжает работу, пока не наберётся достаточно строк.

BEGIN;
SET LOCAL hnsw.iterative_scan = relaxed_order;
SELECT id, title
FROM documents
WHERE category_id = 10
ORDER BY embedding <=> '[0.10, 0.40, 0.90]'
LIMIT 10;
COMMIT;

Если категорий немного, помогает частичный индекс с WHERE category_id = 10. Если много — партиционирование. Для гибридного поиска объединяйте векторный результат с полнотекстовым: PostgreSQL умеет tsvector, а списки сливаются по Reciprocal Rank Fusion.

Диагностика и производительность

Смотрите план с буферами, размер индекса и прогресс построения. Если планировщик выбрал последовательное сканирование, проверьте ORDER BY и LIMIT, совпадение оператора с классом операторов и размер таблицы.

EXPLAIN (ANALYZE, BUFFERS)
SELECT id FROM documents
ORDER BY embedding <=> '[0.10, 0.40, 0.90]' LIMIT 5;
SELECT pg_size_pretty(pg_relation_size('documents_embedding_hnsw'));
SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS pct
FROM pg_stat_progress_create_index;

Для проверки recall выполните запрос с SET enable_indexscan = off; и сравните выдачу, затем верните настройку. Если поиск теряет документы, поднимите hnsw.ef_search или ivfflat.probes. VACUUM важен: удалённые строки остаются в индексе.

  • проверьте совпадение размерности столбца и модели;
  • сравните точный и приближённый поиск на выборке;
  • измерьте размер индекса и попадание в память;
  • посмотрите EXPLAIN (ANALYZE, BUFFERS);
  • убедитесь, что после фильтров остаётся достаточно кандидатов.

Эксплуатация

Векторы — обычные данные PostgreSQL, поэтому pg_dump и физическая репликация работают без особенностей. При восстановлении расширение должно быть установлено в целевой базе. Версии расширения на источнике и реплике лучше держать совместимыми.

pg_dump -Fc app_db > app_db.dump
ALTER EXTENSION vector UPDATE;
REINDEX INDEX CONCURRENTLY documents_embedding_hnsw;
VACUUM (ANALYZE) documents;

Для больших таблиц используйте halfvec и бинарное квантование с повторным ранжированием. Индекс должен помещаться в память, но он делит её с shared_buffers. При нескольких арендаторах разделяйте данные партициями.

Итог

pgvector добавляет семантический поиск в PostgreSQL без отдельного сервиса. Начните с точного поиска на небольшой таблице, измерьте recall и только потом стройте HNSW или IVFFlat. Следите за размерностью, фильтрами и памятью — там чаще всего возникают сюрпризы.

  • установите расширение и проверьте extversion;
  • выберите тип вектора и размерность;
  • сравните точный поиск с индексом;
  • настройте ef_search или probes;
  • добавьте проверку восстановления из копии.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Поделиться: Telegram ВКонтакте