Семантический векторный поиск и прочие темы для завоевания друзей и возлюбленных
Полный спектр поиска: точный, нечеткий, семантический, гибридный — и когда их комбинировать.
Поиск — это не одно целое, и семантический поиск не заменяет остальные его виды.
«Найти пользователя с email dan@example.com» и «найти статьи о отладке для нового инженера» оба называют поиском, но как инженерные задачи они почти не пересекаются. В первом случае существует точный ответ и поиск сводится к O(log n) обращению к индексу. Во втором же правильного ответа нет — только релевантность — и требуется понимание языка, намерения и смысла.
Инженеры, которые умеют убедительно аргументировать решения по поиску — те, кто выигрывает дискуссии и поставляет правильную систему — видят всю картину. Они знают, какой инструмент взять и почему, и могут чётко это объяснить.
В этой статье рассматривается семантический слой: что действительно делает векторный поиск, когда он выигрывает, а где следует отойти в сторону. Полезный подход — это не «встраивать всё», а знать, когда векторы должны сосуществовать с лексическим, нечётким и точным поиском в гибридной архитектуре.
Лексическая и нечёткая части картины — tsvector, pg_trgm, pg_search — описаны в Postgres Text Searching Guide 2026.
Кратко о терминах
Embedding — Плотный список чисел с плавающей точкой, полученный моделью, представляющий кусок текста (или изображения, аудио и т.п.) как точку в высокоразмерном пространстве. Семантически связанные материалы оказываются рядом; несвязанные — далеко друг от друга.
Lexical search — Поиск, основанный на точном совпадении слов и токенов. Быстрый, детерминированный и корректный для известных терминов. Не понимает синонимы, перефразировки и кросс‑языковые эквиваленты.
Semantic search — Поиск, опирающийся на смысл, а не на токены. Запрос «how do I handle timeouts» может совпасть с документом под заголовком «configuring retry policies», хотя у них нет общих слов, потому что их эмбеддинги геометрически близки.
Vector — Список чисел. В контексте поиска — вывод модели‑эмбеддинга. «Vector search» находит векторы, ближайшие к вектору запроса, по геометрическому расстоянию.
FTS (Full-Text Search) — Встроенный в Postgres лексический поиск, реализованный через tsvector / tsquery. Токенизирует, стеммирует и индексирует текст для запросов по ключевым словам. Хорош для прозы и точного поиска терминов; не учитывает смысл.
BM25 — Алгоритм ранжирования для лексического поиска (используется в Elasticsearch, Qdrant и др.). Оценивает результаты, учитывая частоту термина и его редкость в корпусе. Лучше, чем простое совпадение ключевых слов; всё равно лексический.
HNSW (Hierarchical Navigable Small World) — Стандартный индекс приближённого поиска ближайших соседей для векторного поиска. Строит многослойный граф близости, обеспечивая быстрые запросы с высоким покрытием. pgvector, Qdrant, Weaviate и большинство остальных используют его.
RRF (Reciprocal Rank Fusion) — Алгоритм объединения ранжированных списков результатов из нескольких систем поиска. Использует только позицию в ранге — нормализация оценок не требуется. Результат, который занимает высокие позиции и в списке FTS, и в векторном списке, получает более сильный комбинированный скор, чем тот, кто доминирует только в одном из них.
Что на самом деле делает семантический поиск
Векторные эмбеддинги преобразуют текст (или изображения, аудио и т.п.) в список чисел — точку в высокоразмерном пространстве. Модель эмбеддинга обучена так, чтобы семантически связанные тексты оказывались рядом в этом пространстве. «Собака» и «пес» окажутся близко. «Бегать марафон» и «запускать скрипт Python» окажутся далеко друг от друга, несмотря на общую словообразующую часть.
Поиск похожести в этом пространстве находит документы, смысл которых ближе всего к смыслу запроса, независимо от точного совпадения слов.
Это означает:
- «Как настроить тайм‑ауты запросов?» может совпасть со статьёй «Настройка пределов соединений и политик повторных попыток» — нет общих ключевых слов, но высокая концептуальная релевантность.
- «Что‑нибудь лёгкое для летнего вечера» может совпасть с рекомендацией вина, хотя в описании продукта нет соответствующих слов.
- Запрос на английском может находить релевантные документы на французском, испанском или японском, если модель эмбеддингов обучена многоязычно.
Лексический поиск (tsvector, pg_trgm) не способен на это. Он работает с словами и символами, а не со смыслом. Инструменты не взаимозаменяемы — у них разные задачи.
Когда pgvector выигрывает
Построение RAG. Retrieval‑Augmented Generation извлекает фрагменты документов, смысл которых ближе всего к вопросу пользователя, а затем передаёт их языковой модели в качестве контекста. Этот шаг извлечения — векторная операция. Полнотекстовый поиск пропустит парафразы, синонимы и концептуальные совпадения, когда релевантный фрагмент выражён иначе. Преимущество pgvector перед отдельным векторным хранилищем: он работает внутри вашего существующего экземпляра Postgres — не требуется отдельный сервис для развертывания, эксплуатации или синхронизации данных.
Пользователи описывают, чего они хотят, а не то, что нужно искать. «Статьи о построении уверенности у нового менеджера» не содержит ключевых слов, которые надёжно встречаются в релевантных постах. «Лёгкий фреймворк для обработки сайд‑эффектов» может не использовать эти точные слова в документации. Векторный поиск сопоставляет намерение, а не написание.
Поиск похожих элементов. Связанные товары, похожие тикеты поддержки, дублирующиеся баг‑репорты, статьи, которые могут также понравиться. «Найдите проблемы, похожие на эту» — это поиск ближайших соседей: векторизуйте элемент, найдите его геометрических соседей. Важное замечание: векторный поиск всегда возвращает результаты, даже когда ничего действительно похожего нет. Для задач дедупликации и рекомендаций фильтруйте по минимальному порогу схожести (например, косинусная схожесть ≥ 0,80), чтобы не показывать низко‑доверительные совпадения как значимые.
Семантическая дедупликация. Перед индексацией контента для RAG или поиска часто требуется выявить почти‑дубли в корпусе — статьи, несколько раз отредактированные, тикеты поддержки, поданные дважды, записи базы знаний, сильно перекрывающиеся. Векторизуйте документы и отфильтруйте по порогу косинусной схожести, чтобы пометить или объединить почти‑дубли до того, как они загрязнят ваш индекс. Это предотвращает возврат нескольких почти‑идентичных фрагментов и размывание окна контекста.
Многоязычный поиск. Многоязычные модели эмбеддингов отображают семантически эквивалентный контент на разных языках в соседние векторы. Запрос на испанском «perder peso» может совпасть с английской статьёй «sustainable weight loss habits» — нет общих токенов, но одинаковый смысл. FTS требует настройки словарей для каждого языка и плохо справляется с кросс‑язычными запросами. pg_trgm нейтрален к языку, но работает орфографически, а не семантически.
Настройка pgvector
От установки расширения до запроса схожести, настройка сводится к нескольким SQL‑операциям:
CREATE EXTENSION IF NOT EXISTS vector;
ALTER TABLE documents ADD COLUMN embedding vector(1536);
-- HNSW обычно первая индексация для наборов среднего размераCREATE INDEX documents_embedding_idx ON documents USING hnsw (embedding vector_cosine_ops);
-- Запрос семантического поискаSELECT id, title, 1 - (embedding <=> $1::vector) AS similarityFROM documentsORDER BY embedding <=> $1::vectorLIMIT 10;<=> — косинусное расстояние. 1 - cosine_distance даёт косинусное сходство (1.0 = идентично, 0.0 = ортогонально). Для ivfflat (старый, быстрее собираемый вариант) в качестве отправной точки используйте lists = sqrt(row_count).
Что pgvector делает плохо
- Точное совпадение токенов — артикулы товаров, коды ошибок, имена функций.
ORD-12345семантически не похож ни на что. Поиск по эмбеддингам может вернутьORD-12344или вовсе нерелевантный результат. Используйте FTS или B‑tree индекс. - Имена и собственные существительные. Пространство эмбеддингов упорядочивает по смыслу, а не по написанию. Запись «Micheal Jordan» не обязательно окажется рядом с «Michael Jordan» в векторном пространстве.
- Короткие строки, где важнее сходство на уровне символов, чем смысл. Для этого подходит
pg_trgm. - Запросы, в которых обязателен точный термин. BM25 и FTS надёжнее для поиска известных терминов.
Гибридный поиск: почему нужны оба подхода
Техническая документация — самый яркий пример, когда ни один инструмент сам по себе не справится.
Пользователи, ищущие «how to configure timeouts», нуждаются в концептуальном совпадении: статья под заголовком «Setting retry policies and connection limits» не содержит общих ключевых слов, но полностью отвечает их запросу.
Те же пользователи ищут withRetry(), ECONNRESET и ERR_SOCKET_TIMEOUT. Эти точные строки обязаны присутствовать — семантический поиск может их надёжно не найти, а ложный положительный результат (концептуально похожий, но не тот API) вводит в заблуждение.
Векторный поиск решает концептуальные запросы. FTS — точные термины. Ни один из методов не справляется с обеими задачами сам по себе.
Решение — гибридный поиск: выполнить оба запроса и объединить результаты.
Reciprocal Rank Fusion
Reciprocal Rank Fusion (RRF) — стандартный алгоритм объединения ранжированных списков из разных систем поиска. Он не требует нормализации оценок между системами — использует только позиции в ранге. Результат, который находится высоко в обоих списках, получает более сильный комбинированный балл, чем тот, который доминирует только в одном.
WITH fts_results AS ( SELECT id, ROW_NUMBER() OVER (ORDER BY ts_rank(search_vector, query) DESC) AS rank FROM documents, to_tsquery('english', $1) query WHERE search_vector @@ query LIMIT 50),vector_results AS ( SELECT id, ROW_NUMBER() OVER (ORDER BY embedding <=> $2::vector) AS rank FROM documents ORDER BY embedding <=> $2::vector LIMIT 50),rrf AS ( SELECT COALESCE(f.id, v.id) AS id, COALESCE(1.0 / (60 + f.rank), 0) + COALESCE(1.0 / (60 + v.rank), 0) AS rrf_score FROM fts_results f FULL OUTER JOIN vector_results v ON f.id = v.id)SELECT d.id, d.title, rrf.rrf_scoreFROM rrfJOIN documents d ON d.id = rrf.idORDER BY rrf_score DESCLIMIT 10;Значение 60 в знаменателе — это константа RRF. Большие числа сглаживают различия в позициях ранжирования; меньшие усиливают их. Значение по умолчанию — 60 — обычно работает хорошо для большинства типов контента.
RRF избавляет от сложной задачи приведения ts_rank (лог‑частотной оценки) к косинусному расстоянию (геометрической метрике). Эти метрики несравнимы. RRF задаёт лишь один вопрос: «на какой позиции оказался результат в каждом из списков?»
Гибридный поиск с триграммами
Для пользовательского поиска по смешанному контенту — когда в одной сессии могут запрашиваться имя человека, концепция или точный термин — трёхстороннее объединение покрывает все случаи:
WITH trgm_results AS ( SELECT id, ROW_NUMBER() OVER (ORDER BY similarity(title, $1) DESC) AS rank FROM documents WHERE title % $1 LIMIT 50),fts_results AS ( SELECT id, ROW_NUMBER() OVER (ORDER BY ts_rank(search_vector, to_tsquery('english', $1)) DESC) AS rank FROM documents WHERE search_vector @@ to_tsquery('english', $1) LIMIT 50),vector_results AS ( SELECT id, ROW_NUMBER() OVER (ORDER BY embedding <=> $2::vector) AS rank FROM documents ORDER BY embedding <=> $2::vector LIMIT 50),rrf AS ( SELECT COALESCE(t.id, f.id, v.id) AS id, COALESCE(1.0 / (60 + t.rank), 0) + COALESCE(1.0 / (60 + f.rank), 0) + COALESCE(1.0 / (60 + v.rank), 0) AS rrf_score FROM trgm_results t FULL OUTER JOIN fts_results f ON t.id = f.id FULL OUTER JOIN vector_results v ON COALESCE(t.id, f.id) = v.id)SELECT d.id, d.title, rrf.rrf_scoreFROM rrfJOIN documents d ON d.id = rrf.idORDER BY rrf_score DESCLIMIT 10;Такой запрос покрывает: нечёткие совпадения имён (триграммы), точные совпадения ключевых слов (FTS) и концептуальные запросы (вектор). Один поисковый ввод может обслуживать все три пользовательских намерения.
Многоуровневые гибридные архитектуры
Реальные приложения почти никогда не имеют единой поисковой поверхности. Их обычно несколько, каждая с разными требованиями:
| Поверхность | Что запрашивают пользователи | Рекомендуемые слои |
|---|---|---|
| Поиск по блогу / документации | Ключевые слова + концепции | FTS + pgvector (RRF) |
| Поиск имени пользователя / клиента | Имена с опечатками | pg_trgm |
| Поиск товаров | Названия, описания, «похожие на» | pg_trgm + FTS + pgvector |
| Дедупликация тикетов поддержки | «Проблемы, похожие на эту» | только pgvector |
| Внутренний поиск SKU/заказов | Точные идентификаторы | B‑tree индекс |
| RAG над большой базой знаний | Вопросы на естественном языке | pgvector (документы разбиты на чанки) |
| E‑commerce «вам также может понравиться» | Поведенческая + семантическая схожесть | pgvector |
| Автодополнение | Префикс, tolerant к орфографическим ошибкам | pg_trgm |
Это не гипотетика. Большинство приложений с большим объёмом контента нуждаются минимум в двух разных поисковых поверхностях с различными формами запросов. Соблазн выбрать один подход и применять его везде — обычно векторный поиск, потому что он сейчас «в моде». Это приводит к дорогим вычислениям эмбеддингов для задач, где триграммный индекс был бы быстрее, дешевле и точнее.
Практический совет
Добавляйте слой, когда появляется режим отказа, который текущий слой не способен исправить:
- Пользователи жалуются, что опечатки не находятся → добавьте
pg_trgm - Пользователи ищут по концепции и пропускают релевантные результаты → добавьте pgvector
- Пользователи ищут точные символы или коды и получают вместо этого концептуальные результаты → добавьте FTS или проверьте, не полагаетесь ли вы чрезмерно на векторный поиск
- Задержка становится проблемой → оцените предфильтрацию, приближённые индексы или выделенное хранилище
Если всё же нужен выделенный векторный магазин
pgvector покрывает большую часть поисковых задач приложения, пока не требуется другая БД. Грубый порог зависит от количества векторов, настроек индекса, скорости записи, фильтров, аппаратуры и уровня параллелизма, поэтому любую «правилу — менее 10 млн векторов» следует воспринимать как отправную точку для бенчмарков, а не как ограничение продукта. Когда вы действительно перерастаете его — очень высокая конкуренция, требования к p99‑задержке в десятки миллисекунд, миллиарды векторов или серьёзные требования к изоляции в многопользовательской среде — рынок специализированных векторных баз данных широк и заслуживает изучения.
Что на самом деле означают столбцы матрицы
Гибридный поиск — это одновременный запуск BM25‑поиска по ключевым словам и поиска по векторному сходству в одном запросе, объединённых через RRF. Без него вы либо выбираете один режим поиска, либо сами объединяете два запроса.
Sparse vectors идут дальше BM25. Разреженный вектор SPLADE имеет ~30 000 измерений (по одному на каждый термин словаря), ~98 % нулей. Ненулевые позиции показывают, какие термины важны и насколько. Запрос «dogs» также весит «canine» и «pet» — точность уровня BM25 плюс расширение терминов внутри векторного индекса. Если этот столбец false, понадобится отдельный слой FTS для точных терминальных запросов.
# SPLADE: ~30,000 dims, ~60 non-zero — only relevant vocabulary positions firedef encode_splade(text: str) -> dict: tokens = tokenizer(text, return_tensors="pt", truncation=True, max_length=512) with torch.no_grad(): output = model(**tokens) vec = torch.log1p(torch.relu(output.logits)).max(dim=1).values.squeeze() return {"indices": vec.nonzero().squeeze().tolist(), "values": vec[vec != 0].tolist()}SQL / SQL‑like по сути про фильтрацию. Векторный поиск без фильтров — демо. Всё равно нужны ограничения по арендаторам, диапазонам дат, правам доступа и категориям. Полный SQL (pgvector, LanceDB) позволяет выразить это рядом с вашими обычными JOIN‑ами. Специализированные базы используют JSON‑объекты фильтров (Qdrant, Pinecone), DSL запросов (Elasticsearch, Milvus) или GraphQL (Weaviate). Они работают; SQL становится более привлекательным, когда логика фильтрации усложняется.
-- pgvector: vector similarity is just another expressionSELECT id, title, 1 - (embedding <=> $1) AS scoreFROM documentsWHERE tenant_id = $2 AND category = ANY($3::text[]) AND created_at > NOW() - INTERVAL '90 days'ORDER BY embedding <=> $1LIMIT 10;# Qdrant: equivalent filter as a Python object — same result, more ceremonyresults = client.query_points( collection_name="documents", query=query_embedding, query_filter=models.Filter(must=[ models.FieldCondition(key="tenant_id", match=models.MatchValue(value=tenant_id)), models.FieldCondition(key="category", match=models.MatchAny(any=categories)), models.FieldCondition(key="created_at", range=models.DatetimeRange(gte=cutoff)), ]), limit=10,)Multimodal native означает, что база поставляется с моделями эмбеддингов для не‑текстового контента. Вы передаёте ей сырой URL изображения — она сама векторизует. Большинство баз нейтральны к эмбеддингам — поток эмбеддингов выстраивается самостоятельно. Marqo и Weaviate (через модули CLIP/ImageBind) замыкают этот цикл.
# Marqo: POST raw images, query with text — no external embedding stepmq.index("products").add_documents( [{"id": "shoe-001", "image": "https://cdn.example.com/shoes/001.jpg"}], tensor_fields=["image"])results = mq.index("products").search(q="lightweight shoes for summer")# Returns shoe-001 despite zero keyword overlap — CLIP handles the cross-modal matchDisk‑based index — это рычаг стоимости. HNSW‑индексы, живущие в RAM, могут требовать несколько гигабайт ОЗУ на миллион 1536‑мерных векторов, учитывая сырые векторы, графовую нагрузку и метаданные. Дисковые альтернативы (Milvus DiskANN, Elasticsearch DiskBBQ, формат Lance в LanceDB, объектный слой Turbopuffer) часто жертвуют небольшим ростом задержки запросов ради снижения инфраструктурных расходов. Для RAG‑нагрузок, где задержка модели уже доминирует, такой компромисс часто оправдан и требует бенчмаркинга.
Max dimensions — скрытая миграция в вашей архитектуре. text-embedding-3-large использует 3072 измерения, Jina v3 может выдавать ещё больше, а исследовательские модели продолжают расти. Некоторые управляемые сервисы публикуют жёсткие лимиты на размер; другие указывают лишь высокие пределы или вовсе не ограничивают типичные модели. Проверьте актуальную документацию перед тем, как фиксировать выбор. Выбирайте с запасом; миграция векторного индекса из‑за достижения потолка по измерениям — болезненный спринт.
Ландшафт
| Database | Deployment | License | Hybrid Search | Sparse Vectors | SQL / SQL-like | Multimodal | Disk Index | Max Dims | Sweet Spot |
|---|---|---|---|---|---|---|---|---|---|
| pgvector | Self-host / managed (Supabase, Neon, RDS) | OSS (PostgreSQL) | Manual (RRF via SQL) | ❌ | ✅ Full SQL | ❌ | ✅ HNSW on disk | 16,000 storage; 2,000 indexed vector | Already on Postgres; moderate vector counts |
| Qdrant | Self-host / Cloud | Apache 2.0 | ✅ Native BM25 | ✅ Mature support | ❌ (REST/gRPC) | ❌ | ✅ | 65,535 | Filtered queries at scale; complex metadata |
| Weaviate | Self-host / Cloud | BSD 3 | ✅ Native BM25 + RRF | ✅ | ❌ (GraphQL / gRPC) | ✅ via modules | ✅ | 65,535 | GraphQL access patterns; built-in vectorization |
| Pinecone | Cloud only | Proprietary | ✅ (added 2024) | ✅ | ❌ | ❌ | ✅ (serverless) | 20,000 | Managed simplicity; no ops team |
| Milvus / Zilliz | Self-host / Cloud (Zilliz) | Apache 2.0 | ✅ Native | ✅ | ✅ SQL-like (Milvus Query Language) | ✅ | ✅ DiskANN | 32,768 | Billion-scale; enterprise on-prem |
| Chroma | Embedded / self-host | Apache 2.0 | ❌ | ❌ | ❌ | ❌ | ❌ | 65,535 | Local dev and prototyping only |
| LanceDB | Embedded / Cloud | Apache 2.0 | ✅ | ❌ | ✅ SQL via DataFusion | ✅ Native | ✅ (Lance format) | Unlimited | Edge / serverless; multimodal lakehouse |
| Orama | Embedded / Cloud | Apache 2.0 | ✅ Full-text + vector | ❌ | ❌ | ❌ | ❌ | Varies | JS/edge apps; lightweight site/app search |
| Turbopuffer | Cloud only (serverless) | Proprietary | ✅ BM25 + vector | ❌ | ❌ | ❌ | ✅ (object storage) | 16,000 | Multi-tenant SaaS; millions of namespaces |
| Elasticsearch | Self-host / Elastic Cloud | SSPL / AGPLv3 | ✅ RRF + ELSER sparse | ✅ (ELSER) | ✅ Query DSL | ❌ | ✅ DiskBBQ | 4,096 | Already on Elastic stack; hybrid enterprise search |
| OpenSearch | Self-host / AWS managed | Apache 2.0 | ✅ RRF + Neural Search | ✅ | ✅ Query DSL | ❌ | ✅ FAISS + HNSW | 16,000 | AWS-native; open-source Elastic alternative |
| Vespa | Self-host / Cloud | Apache 2.0 | ✅ Native | ✅ Tensors / lexical ranking | ✅ YQL | ✅ Tensors | ✅ | Effectively unbounded | Search + ranking + recommendation systems |
| ClickHouse | Self-host / Cloud | Apache 2.0 | Manual | ❌ | ✅ Full SQL | ❌ | ✅ Columnar + HNSW | Varies | Analytics/logs with vector search beside OLAP |
| MongoDB Atlas | Cloud / self-host | SSPL | ✅ Built-in | ❌ | ✅ MQL + aggregation | ❌ | ✅ HNSW | 8,192 | Already on MongoDB; document + vector in one |
| Redis (VSS) | Self-host / Redis Cloud | RSALv2 / SSPL | ✅ (RediSearch) | ✅ | ❌ | ❌ | ❌ RAM-only | 32,768 | Ultra-low latency; cache-layer vector search |
| Marqo | Cloud / self-host | Apache 2.0 | ✅ | ❌ | ❌ | ✅ Native focus | ✅ | Varies | End-to-end multimodal: image + text + video |
Несколько пунктов, которые не помещаются в таблицу
Мультиарендность Turbopuffer построена вокруг огромного количества пространств имён. В публичных позиционированиях и клиентских историях подчеркивается использование в сценариях вроде крупного, насыщенного пространствами имён корпуса Notion. Если каждому пользователю или организации требуется изолированный векторный поиск, такая архитектура меняет экономику, но всё равно следует протестировать собственный профиль арендаторов.
Встроенный режим LanceDB – это самое близкое к «SQLite для векторного поиска». Он работает в процессе, не требует сервера и может использоваться в Lambda, Cloudflare Workers и других edge‑окружениях. Колоночный формат Lance делает встроенную работу практичной даже при реальном масштабе.
Chroma лучше всего подходит для разработки/тестирования и небольших приложений. Если вы планируете работать с очень большими корпусами, требуете высокой доступности, дисковой нагрузки или полноценного гибридного поиска, оцените хранилище, ориентированное на продакшн, прежде чем продвигать прототип в инфраструктуру.
Vespa — это то, к чему вы прибегаете, когда поиск составляет лишь половину продукта. Она объединяет лексический поиск, поиск ближайших соседей, тензоры, ранжирующие выражения, группировку и онлайн‑обслуживание. Эта мощь реальна, но также реальны и операционная, и модельная сложность. Подходит командам, занимающимся поиском/рекомендациями, а не тем, кто просто «добавит семантический поиск в мое CRUD‑приложение».
ClickHouse имеет смысл рассматривать, когда поиск связан с аналитикой. Если ваш источник правды — события, логи, трассы или метрики, ClickHouse хранит векторные расстояния, фильтрацию, агрегацию и серьёзный полнотекстовый индекс в одном SQL‑движке. Это не специализированная векторная БД, но часто правильный, хоть и скучный, вариант для аналитического извлечения.
Разреженные векторы позволяют получить совпадения по ключевым словам качества BM25 внутри векторного индекса — без запуска отдельного полнотекстового движка. Здесь особенно зрелые реализации у Qdrant и Elasticsearch. Если гибридный поиск критичен, а двухсистемная архитектура неприемлема, поддержка разреженных векторов — это то, что нужно искать.
Выбор, когда pgvector уже не хватает
- SaaS‑продукт с изоляцией per‑tenant → Turbopuffer
- Сложная фильтрация метаданных в масштабе → Qdrant
- Уже используете Elastic/ELK‑стек → Elasticsearch с DiskBBQ
- AWS‑компания, желающая open‑source → OpenSearch
- Платформа поиска/рекомендаций с серьёзными требованиями к ранжированию → Vespa
- Аналитика, наблюдаемость, поиск по логам/событиям → ClickHouse
- Масштаб в миллиарды записей on‑prem / self‑hosted → Milvus
- Edge / serverless / мультимодальный → LanceDB
- Небольшое JS‑приложение, сайт документации или edge‑нативный UI поиска → Orama
- Ноль операций, стоимость вторична → Pinecone
- Мультимодальный в первую очередь (изображения, видео, аудио) → Marqo
- Уже на MongoDB → Atlas Vector Search
- Уже на Postgres, нужен больший запас → Supabase Vector или Neon (оба управляют pgvector, с лучшими инструментами)
Одна вещь, которой нельзя заниматься
Не используйте векторный поиск как нечёткий текстовый поиск для задач, где есть однозначный ответ.
«Найдите пользователя с email dan@example.com» — это не задача векторного поиска. «Найдите заказ с ID ORD-12345» — тоже не подходит. Встраивание ORD-12345 и поиск по косинусному сходству вернёт что‑то — но это может быть неверно. Идентификатор имеет единственно правильный ответ. Приблизительное совпадение по идентификатору — это ошибка.
Векторный поиск возвращает наиболее похожий элемент в наборе данных, даже если ничего действительно релевантного нет. Он не умеет определить, что подходящего ответа нет. Для связанных документов это приемлемо, но для точного поиска записи это серьёзная проблема: уверенный неправильный ответ хуже, чем пустой результат.
То же самое верно и в обратном направлении: не используйте полнотекстовый поиск (FTS) для запросов, где пользователь описывает концепцию. «Статьи о принятии тяжёлых решений в условиях неопределённости» не содержит надёжных ключевых слов. FTS либо выдаст шум, либо ничего не вернёт. Выбирайте правильный инструмент под форму запроса.
Полная картина
Большинству производственных систем поиска требуется более одного уровня:
pg_trgmдля имён, опечаток, автодополнения- FTS /
pg_searchдля поиска по ключевым словам в тексте - pgvector для семантических и концептуальных запросов
- RRF fusion для сценариев, где пользователи смешивают типы запросов
- Обычные индексы для точных идентификаторов, фильтров и отсортированных списков
Это не конкурирующие инструменты. Они дополняют друг друга. Хорошо построенная система поиска выбирает подходящий слой для каждой формы запроса — и когда формы запросов перекрываются, запускает несколько слоёв и объединяет результаты.
Команды, которые выпускают качественные функции поиска, понимают весь стек. Те, кто не понимает, берут векторную базу, встраивают всё подряд и удивляются, почему точные поиск иногда возвращает неверную запись.