Перейти до вмісту
51studio
Новини

Векторний пошук у Postgres з pgvector

Автор: Sam Hollis9 хв читання

Дефолтна порада для будь-якого проєкту з embeddings тепер — «використовуйте Pinecone». Порада правильна на масштабі і неправильна у розмірі, який більшість проєктів насправді мають.

Для більшості embedding-навантажень (RAG над docs-сайтом, семантичний пошук по блогу, similarity-метчинг для product-каталогу) правильна відповідь — pgvector. Той самий Postgres, який ви вже запускаєте. Та сама авторизація. Ті самі бекапи. Те саме dev-середовище. На одного вендора менше у рахунку.

Цей пост — envelope (коли pgvector працює, коли ні), налаштування, що насправді швидке, і операційна частина, яку ніхто не згадує.

Що таке pgvector

pgvector — це розширення Postgres, що додає тип колонки vector плюс типи індексів для approximate nearest neighbour (ANN) пошуку. Встановлюєте його раз на базу, зберігаєте embeddings у звичайній колонці і запитуєте їх новими SQL-операторами (<-> для відстані, <#> для inner product, <=> для cosine).

Встановлення:

sql
CREATE EXTENSION vector;

Усе. Підтримується на Neon, Supabase, RDS Postgres, Cloud SQL, Crunchy і будь-якому сучасному self-hosted Postgres. На більшості managed-провайдерів він уже доступний; просто увімкніть.

Що він уміє і на якому масштабі

pgvector добре обробляє векторний пошук у такому envelope:

  • До ~1-5 мільйонів векторів на таблицю. Поза цим час запиту починає рости, і захочеться dedicated векторну БД.
  • До ~1536 вимірів. OpenAI text-embedding-3-small — це 1536, text-embedding-3-large — 3072. Обидва працюють, але 3072 ближче до верхнього краю.
  • Затримка у діапазоні 5-50ms для ANN-запитів з правильно побудованим HNSW-індексом на low-million рядків.
  • Throughput у сотні QPS на помірно великому Postgres-інстансі.

Це покриває більшість проєктів:

  • RAG-чат-бот над docs вашої компанії (від кількох сотень до кількох тисяч векторів).
  • Семантичний пошук по блогу з 10 000 статей (10K векторів).
  • Product similarity для маркетплейсу зі 100K товарів (100K векторів).
  • Customer support deflection з 50K історичних тікетів (50K векторів).

Якщо у вас менше мільйона векторів — ви глибоко у комфортній зоні. Якщо між 1M і 5M — у зоні, де pgvector усе ще працює, але вибір індексу і конфігурація починають мати значення. Понад 5M — кейс для dedicated векторної БД посилюється.

Налаштування

Схема:

sql
CREATE TABLE documents (
  id BIGSERIAL PRIMARY KEY,
  content TEXT NOT NULL,
  embedding vector(1536) NOT NULL,
  metadata JSONB,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

Індекс — і тут більшість команд обирає неправильно:

sql
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

Pgvector пропонує два типи індексів: IVFFlat і HNSW. Чесне порівняння:

  • IVFFlat — швидший у побудові, повільніший у запиті, потребує знання розподілу даних наперед. Нормально для batch-оновлень і read-heavy воркфлоу.
  • HNSW — повільніший у побудові, швидший у запиті, працює без попереднього знання даних. Кращий для більшості use cases.

За замовчуванням обирайте HNSW, поки немає специфічної причини. Час побудови довший (хвилини проти секунд для IVFFlat на малих даних, години проти хвилин на великих), але latency запиту стабільно нижча.

Параметри m і ef_construction обмінюють час побудови і пам'ять на recall. Дефолти (m=16, ef_construction=64) розумні для більшості даних. Підіймайте, якщо потрібен вищий recall (ближчий до точного пошуку); опускайте, якщо пам'ять у дефіциті.

Запит

Запит семантичного пошуку:

sql
SELECT id, content, metadata,
       embedding <=> $1 AS distance
FROM documents
WHERE metadata->>'category' = 'tutorial'
ORDER BY embedding <=> $1
LIMIT 10;

$1 — це query embedding (згенерований тією ж моделлю, що й документи). <=> — це cosine distance. WHERE-клауза фільтрує спочатку, потім ранжує; планер зазвичай це ефективно обробляє, якщо metadata-колонка проіндексована.

У TypeScript з Drizzle:

ts
import { sql } from "drizzle-orm";

const results = await db
  .select({
    id: documents.id,
    content: documents.content,
    distance: sql<number>`${documents.embedding} <=> ${queryEmbedding}::vector`,
  })
  .from(documents)
  .orderBy(sql`${documents.embedding} <=> ${queryEmbedding}::vector`)
  .limit(10);

Невелика мука: система типів Drizzle поки що не моделює vector-колонки нативно (на момент написання), тож робите танок з sql template literals. Працює.

Цифри продуктивності

Бенчмарки на реальному проєкті (RAG над docs-сайтом, ~50K векторів на 1536 вимірів, на $19/місяць Neon-інстансі):

  • Час побудови індексу: ~90 секунд
  • Latency запиту p50: 12ms
  • Latency запиту p99: 38ms
  • Пам'ять, використана індексом: ~600MB

Для $19-інстансу ці цифри — відмінні. Те саме навантаження на стартовому тарифі Pinecone порівняне за latency, але коштує $70/місяць і ще один вендор для управління.

На більших масштабах (ми тестували 500K векторів на $99/місяць Neon-інстансі):

  • Час побудови індексу: ~25 хвилин
  • Latency запиту p50: 22ms
  • Latency запиту p99: 75ms

Все ще глибоко в envelope, де pgvector — правильна відповідь.

Коли переходити на dedicated векторну БД

Конкретні сигнали:

  • Кількість векторів понад 5 мільйонів. Час побудови індексу стає надокучливим, latency запиту починає рости. Виникне спокуса шардувати; це сигнал.
  • Потрібні real-time вставки на високому throughput. HNSW-індекс pgvector має per-insert overhead. Pinecone і подібні оптимізовані під streaming-вставки.
  • Потрібна multi-tenant ізоляція, яку row-level security Postgres не може чисто виразити.
  • Потрібні векторні операції поза similarity (clustering, dimensionality reduction на час запиту). Векторні БД пропонують більше analytics-стильних операцій.
  • Використовуєте managed RAG-фреймворк, що припускає Pinecone або подібне. Іноді ціна інтеграції більша за ціну інфраструктури.

Якщо нічого з цього не застосовно — pgvector. Якщо два або більше — оцінюйте Pinecone, Qdrant, Weaviate або подібне.

Операційні нотатки

Речі, що кусають команди у перший місяць:

Connection pooling. Запити pgvector важкі по CPU на базі. Pool, замалий — викликає queue backpressure; завеликий — насичує CPU. Стартуйте з 20 з'єднань, моніторте, коригуйте.

Холодні індекси. Після свіжого рестарту HNSW-індекс не у пам'яті. Перші кілька запитів повільні. Якщо у вас передбачуваний паттерн трафіку, прогрівайте, запускаючи кілька репрезентативних запитів на старті.

Бекапи. pgvector-дані бекапляться звичайними Postgres-бекапами. Жодної нової історії з бекапами. Це одна з недооцінених перемог.

Міграції схеми. Додавання нової колонки до таблиці не перебудовує індекс. Додавання рядків — викликає інкрементальні оновлення індексу. Bulk-вставки повільніші за невекторні таблиці; батчуйте вставки і подумайте про drop/rebuild індексу для дуже великих початкових завантажень (>100K рядків).

Апгрейди embedding-моделей. Якщо перемикаєтесь з text-embedding-3-small на text-embedding-3-large — треба переембедити все і перебудувати індекс. Планируйте; не міняйте моделі недбало.

Ціна. Найбільша прихована ціна — це генерація embeddings, не зберігання. text-embedding-3-small OpenAI — це $0.02 за 1M токенів. Переембеддинг корпусу зі 100K документів (~500 токенів кожен) — $1 за прохід. Відстеження цього має значення, якщо часто міняєте моделі.

Паттерн, що працює для RAG

Для більшості RAG-застосунків архітектура, яку ми відвантажуємо:

  1. Ingest pipeline: документи → чанки → embeddings → збережено у pgvector.
  2. Query pipeline: питання користувача → embedding → ANN-пошук у pgvector → топ 5-10 чанків → LLM з чанками у контексті → відповідь.
  3. Фільтр метаданих: чанки тегнуто джерелом, датою, категорією. Фільтрувати на час запиту, щоб обмежити відповіді.

Інфраструктура — один Postgres-інстанс і LLM API. Жодного vector-DB вендора. Жодного vector-DB SDK. Жодного окремого дашборда. Та сама база, що тримає юзерів і контент застосунку, тримає й embeddings.

Цей паттерн масштабується, поки не перестає. «Поки не перестає» зазвичай означає мільйон-плюс векторів і високий QPS, у який момент міграція — проєкт, але tractable.

Коли pgvector — overkill

Якщо у вас менше 10 000 векторів — ANN-індекс не потрібен. Linear scan з cosine distance — достатньо швидко.

sql
SELECT id, content, embedding <=> $1 AS distance
FROM documents
ORDER BY embedding <=> $1
LIMIT 10;

Без індексу, повний scan, 10ms latency на 10K рядках. Додайте HNSW-індекс, коли перейдете 50-100K векторів і починаєте бачити повзіння latency вгору.

Найменше налаштування — це: vector-колонка, без індексу, linear-scan запит. Це робоча система семантичного пошуку в, можливо, 50 рядках коду.

Підсумок

Для більшості проєктів з embeddings:

  • Використайте Postgres + pgvector, поки немає специфічної причини використати dedicated векторну БД.
  • HNSW-індекс, дефолтні параметри.
  • Linear scan, якщо під 10K векторів; HNSW вище.
  • Переоцініть на 5M векторів.

Дефолтна порада була «використовуйте Pinecone» два роки. Кращий дефолт для більшості проєктів — «використовуйте базу, яку ви вже запускаєте». pgvector достатньо зрілий, щоб кейс додавання нового вендора потребував підтвердження, а не припущення.

Якщо будуєте веб-застосунок з векторним пошуком і хочете розумне налаштування з першого дня, подивіться, як ми працюємо з веб-застосунками. Postgres + pgvector, швидкі запити, на одного вендора менше у рахунку.

Схожі статті