Семантический векторный поиск и другие темы для завоевания друзей и возлюбленных
Полная картина поиска: точный, нечеткий, семантический, гибридный — и когда сочетать их все.
На этой странице
- Обзор векторного поиска: сравнение 16 вариантов
- Термины, используемые в этом руководстве
- Как эмбеддинги находят связанный контент
- Когда pgvector выигрывает
- Сочетайте ключевые слова и векторы для смешанных запросов
- Соотносите каждый интерфейс поиска с типами запросов
- Когда пора выходить за пределы pgvector
- Не встраивайте идентификаторы и не ждите точного совпадения
- Стройте поиск вокруг формы запроса
Поиск — это не что-то одно, и семантический поиск не заменяет все остальные его разновидности.
Обзор векторного поиска: сравнение 16 вариантов
Сравним способы развертывания, лицензирование, возможности поиска и соответствие рабочим нагрузкам. Пояснения к столбцам и примеры SQL приведены ниже.
| База данных | Развертывание | Лицензия | Гибридный поиск | Разреженные векторы | Интерфейс запросов | Встроенное мультимодальное получение эмбеддингов | Индекс на диске | Ограничения на размерность вектора | Оптимальный вариант |
|---|---|---|---|---|---|---|---|---|---|
| pgvector | Собственный хостинг / управляемый сервис (Supabase, Neon, RDS) | OSS (PostgreSQL) | Вручную (RRF через SQL) | ❌ | ✅ Полный SQL | ❌ | ✅ HNSW на диске | 16 000 для хранения; 2 000 для индексируемого vector | Вы уже используете Postgres; умеренное количество векторов |
| Qdrant | Собственный хостинг / Cloud | Apache 2.0 | ✅ Нативный BM25 | ✅ Зрелая поддержка | ❌ (REST/gRPC) | ❌ | ✅ | 65 535 | Фильтруемые запросы в большом масштабе; сложные метаданные |
| Weaviate | Собственный хостинг / Cloud | BSD 3 | ✅ Нативный BM25 + RRF | ✅ | ❌ (GraphQL / gRPC) | ✅ через модули | ✅ | 65 535 | Сценарии доступа через GraphQL; встроенная векторизация |
| Pinecone | Только Cloud | Проприетарная | ✅ (добавлено в 2024 году) | ✅ | ❌ | ❌ | ✅ (serverless) | 20 000 | Простота управляемого сервиса; отсутствие команды эксплуатации |
| Milvus / Zilliz | Собственный хостинг / Cloud (Zilliz) | Apache 2.0 | ✅ Нативный | ✅ | ✅ SQL-подобный (Milvus Query Language) | ✅ | ✅ DiskANN | 32 768 | Масштаб до миллиардов объектов; корпоративный on-premises |
| Chroma | Встраиваемый / собственный хостинг | Apache 2.0 | ❌ | ❌ | ❌ | ❌ | ❌ | 65 535 | Только локальная разработка и прототипирование |
| LanceDB | Встраиваемый / Cloud | Apache 2.0 | ✅ | ❌ | ✅ SQL через DataFusion | ✅ Нативный | ✅ (формат Lance) | Не ограничена | Edge / serverless; мультимодальное lakehouse-хранилище |
| Orama | Встраиваемый / Cloud | Apache 2.0 | ✅ Полнотекстовый + векторный | ❌ | ❌ | ❌ | ❌ | Зависит от конфигурации | JS/edge-приложения; легковесный поиск по сайту или приложению |
| Turbopuffer | Только Cloud (serverless) | Проприетарная | ✅ BM25 + векторный | ❌ | ❌ | ❌ | ✅ (объектное хранилище) | 16 000 | Мультитенантный SaaS; миллионы пространств имён |
| Elasticsearch | Собственный хостинг / Elastic Cloud | SSPL / AGPLv3 | ✅ RRF + разреженный ELSER | ✅ (ELSER) | ✅ Query DSL | ❌ | ✅ DiskBBQ | 4 096 | Вы уже используете стек Elastic; корпоративный гибридный поиск |
| OpenSearch | Собственный хостинг / управляемый AWS | Apache 2.0 | ✅ RRF + Neural Search | ✅ | ✅ Query DSL | ❌ | ✅ FAISS + HNSW | 16 000 | Нативная интеграция с AWS; открытая альтернатива Elastic |
| Vespa | Собственный хостинг / Cloud | Apache 2.0 | ✅ Нативный | ✅ Тензоры / лексическое ранжирование | ✅ YQL | ✅ Тензоры | ✅ | Фактически не ограничена | Системы поиска, ранжирования и рекомендаций |
| ClickHouse | Собственный хостинг / Cloud | Apache 2.0 | Вручную | ❌ | ✅ Полный SQL | ❌ | ✅ Колончатый + HNSW | Зависит от конфигурации | Аналитика и логи с векторным поиском рядом с OLAP |
| MongoDB Atlas | Cloud / собственный хостинг | SSPL | ✅ Встроенный | ❌ | ✅ MQL + агрегация | ❌ | ✅ HNSW | 8 192 | Вы уже используете MongoDB; документы и векторы в одной системе |
| Redis (VSS) | Собственный хостинг / Redis Cloud | RSALv2 / SSPL | ✅ (RediSearch) | ✅ | ❌ | ❌ | ❌ Только в RAM | 32 768 | Минимальная задержка; векторный поиск на уровне кеша |
| Marqo | Cloud / собственный хостинг | Apache 2.0 | ✅ | ❌ | ❌ | ✅ Нативный фокус | ✅ | Зависит от конфигурации | Сквозной мультимодальный сценарий: изображения + текст + видео |
«Найти пользователя с email dan@example.com» и «найти мне статьи о том, как начинающему инженеру заниматься отладкой» — оба запроса называются поиском, но как инженерные задачи почти никак не связаны. В первом есть правильный ответ и индексный поиск за O(log n). Во втором правильного ответа нет — есть только релевантность, — и требуется понимать язык, намерение и смысл.
Инженеры, которые убедительнее всего отстаивают решения в области поиска, — те, кто выигрывает споры и поставляет правильную систему, — видят всю картину целиком. Они знают, к какому инструменту обратиться и почему, и умеют ясно это объяснить.
В этой статье рассматривается семантический слой: что на самом деле делает векторный поиск, когда он выигрывает и где ему лучше не мешать. Рабочая формула — не «получить эмбеддинги для всего». Нужно понимать, когда векторы должны работать рядом с лексическим, нечетким поиском и поиском по точному совпадению в гибридной архитектуре.
Лексическая и нечеткая половина этой картины — tsvector, pg_trgm, pg_search — разобрана в руководстве Текстовый поиск в Postgres: руководство на 2026 год.
Термины, используемые в этом руководстве
Эмбеддинг — плотный список чисел с плавающей точкой, полученный от модели и представляющий фрагмент текста (или изображения, аудио и т. д.) как точку в многомерном пространстве. Семантически связанный контент оказывается рядом, а несвязанный — далеко.
Лексический поиск — поиск на основе точного совпадения слов и токенов. Быстрый, детерминированный и корректный для известных терминов. Не понимает синонимы, перефразирование и эквиваленты на других языках.
Семантический поиск — поиск на основе смысла, а не токенов. Запрос «как обрабатывать тайм-ауты» может найти документ с заголовком «настройка политик повторных попыток», даже если в них нет общих слов, потому что их эмбеддинги геометрически близки.
Вектор — список чисел. В контексте поиска — результат работы модели эмбеддингов. «Векторный поиск» находит векторы, ближайшие к вектору запроса, используя геометрическое расстояние.
FTS (полнотекстовый поиск) — встроенный в 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 находит фрагменты документов, наиболее близкие по смыслу к вопросу пользователя, а затем передаёт их языковой модели в качестве контекста. Этот этап поиска выполняется с помощью векторов. FTS пропустит перефразирования, синонимы и концептуальные совпадения, которые релевантный фрагмент может выражать иначе. Преимущество pgvector перед отдельным векторным хранилищем в том, что он работает внутри уже существующего экземпляра Postgres — не нужно разворачивать, эксплуатировать и синхронизировать данные с отдельным сервисом.
Пользователи описывают, что им нужно, а не что искать. В запросе «Статьи о том, как новому руководителю обрести уверенность» нет ключевых слов, которые гарантированно встретятся в релевантных публикациях. «Лёгкий фреймворк для обработки побочных эффектов» может не использовать в документации именно эти слова. Векторный поиск сопоставляет намерение, а не написание.
Поиск похожих объектов. Связанные товары, похожие обращения в поддержку, дубликаты баг-репортов, статьи, которые могут вам понравиться. «Найди проблемы, похожие на эту» — это поиск ближайших соседей: нужно получить эмбеддинг объекта и найти его геометрических соседей. Важная оговорка: векторный поиск всегда возвращает результаты, даже если ничего действительно похожего нет. Для дедупликации и рекомендаций задавайте минимальный порог схожести (например, cosine similarity ≥ 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 is usually the first index to try for moderate-size datasetsCREATE INDEX documents_embedding_idx ON documents USING hnsw (embedding vector_cosine_ops);
-- Semantic search querySELECT 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).
Где векторный поиск возвращает неправильный ответ
- Точное совпадение токенов — артикулы продуктов, коды ошибок, имена функций.
ORD-12345семантически не похоже ни на что. Поиск на основе эмбеддингов может вернутьORD-12344или вообще нерелевантный результат. Используйте FTS или B-tree-индекс. - Имена и имена собственные. Векторное пространство организовано по смыслу, а не по написанию. Запись пользователя «Micheal Jordan» не обязательно окажется рядом с «Michael Jordan» в векторном пространстве.
- Короткие строки, где сходство на уровне символов важнее смысла. С этим справится
pg_trgm. - Запросы, в которых точный термин должен присутствовать в результате. BM25 и FTS надёжнее подходят для поиска по известным терминам.
Сочетайте ключевые слова и векторы для смешанных запросов
Техническая документация — самый наглядный пример того, где ни один из инструментов не справляется в одиночку.
Пользователям, которые ищут «как настроить таймауты», нужно сопоставление по смыслу: статья с заголовком «Настройка политик повторных попыток и лимитов подключений» не содержит пересекающихся ключевых слов, но именно она им нужна.
Те же пользователи могут искать withRetry(), ECONNRESET и ERR_SOCKET_TIMEOUT. Эти точные строки должны присутствовать в результате — семантический поиск может находить их ненадёжно, а ложное совпадение (концептуально похожий, но не тот API) активно вводит в заблуждение.
Векторный поиск обрабатывает концептуальные запросы. FTS — точные термины. По отдельности ни один из них хорошо не справляется с обеими задачами.
Решение — гибридный поиск: выполняйте оба запроса и объединяйте результаты.
Объединяйте результаты с ранжированием через RRF
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 |
| Автодополнение | По префиксу, с устойчивостью к опечаткам | pg_trgm |
Это не гипотетические сценарии. Большинству приложений с большим объёмом контента нужны как минимум два разных интерфейса поиска с разной формой запросов. Соблазн выбрать один подход и использовать его везде — обычно векторный поиск, поскольку сейчас это модный выбор. В результате вы тратитеся на вычисление эмбеддингов там, где триграммный индекс был бы быстрее, дешевле и точнее.
Добавляйте уровень поиска, когда тип запроса не обрабатывается
Добавляйте уровень, когда появляется сценарий отказа, который текущий уровень не может исправить:
- Пользователи жалуются, что запросы с опечатками не находят совпадения → добавьте
pg_trgm - Пользователи ищут по смыслу и пропускают релевантные результаты → добавьте pgvector
- Пользователи ищут точные символы или коды, а получают концептуальные результаты → добавьте FTS или проверьте, не полагаетесь ли вы слишком сильно на векторный поиск
- Задержка становится проблемой → оцените предварительную фильтрацию, приближённые индексы или специализированное хранилище
Когда пора выходить за пределы pgvector
pgvector закрывает множество задач прикладного поиска ещё до того, как вам понадобится другая база данных. Грубая граница зависит от количества векторов, настроек индекса, скорости записи, фильтров, оборудования и уровня параллелизма. Поэтому любое правило вроде «до 10 млн векторов» воспринимайте как исходное предположение для бенчмарка, а не как ограничение продукта. Когда вы действительно перерастаете pgvector — из-за очень высокой конкуренции запросов, жёстких требований к p99-задержке, миллиардов векторов или серьёзных требований к изоляции арендаторов — рынок специализированных векторных баз широк, и в нём стоит разобраться.
Как читать сравнение
Гибридный поиск означает, что поиск по ключевым словам BM25 и поиск по векторному сходству выполняются в одном запросе, а результаты объединяются через RRF. Без этого вам придётся либо выбрать один режим поиска, либо самостоятельно объединять результаты двух запросов.
Разреженные векторы идут дальше BM25. Разреженный вектор SPLADE содержит около 30 000 измерений (по одному на каждый термин словаря), примерно 98% из которых равны нулю. Ненулевые позиции показывают, какие термины важны и насколько. Запрос для «собак» также повышает вес слов «пёс» и «домашнее животное» — точность уровня 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-подобный синтаксис — это, по сути, вопрос фильтрации. Векторный поиск без фильтрации годится для демо. Вам всё равно нужны область арендатора, диапазоны дат, права доступа и фильтры по категориям. Полный 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,)Нативная мультимодальность означает, что база поставляется с моделями эмбеддингов для нетекстового содержимого. Вы передаёте ей 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 matchИндекс на диске — это рычаг управления стоимостью. HNSW-индексы, целиком размещённые в RAM, могут требовать несколько гигабайт памяти на каждый миллион векторов размерностью 1536 после учёта исходных векторов, накладных расходов графа и метаданных. Альтернативы, изначально рассчитанные на работу с диском (Milvus DiskANN, Elasticsearch DiskBBQ, формат Lance в LanceDB, уровень объектного хранилища Turbopuffer), часто обменивают часть задержки запроса на снижение стоимости инфраструктуры. Для рабочих нагрузок RAG, где задержка модели уже доминирует, такой компромисс часто стоит проверить бенчмарком.
Максимальная размерность — это миграция, которая прячется в вашей архитектуре. text-embedding-3-large использует 3072 измерения, Jina v3 может выдавать эмбеддинги большей размерности, а исследовательские модели продолжают двигаться вверх. Некоторые управляемые сервисы публикуют жёсткие ограничения на размерность; другие указывают высокие лимиты или не устанавливают практически значимого ограничения для типичных моделей эмбеддингов. Перед фиксацией решения проверьте актуальную документацию. Выбирайте вариант с запасом; миграция векторного индекса из-за достижения потолка размерности — болезненный спринт.
Операционные компромиссы, которых не видно в таблице
Мультитенантность Turbopuffer рассчитана на очень большое количество пространств имён. В публичном позиционировании и историях клиентов подчёркиваются сценарии вроде крупного, насыщенного пространствами имён корпуса Notion. Если каждому пользователю или организации нужен изолированный векторный поиск, такая архитектура может изменить экономику решения, но форму ваших арендаторов всё равно нужно проверить собственным бенчмарком.
Встроенный режим LanceDB — это, пожалуй, самый близкий аналог «SQLite для векторного поиска». Он работает внутри процесса, не требует сервера и подходит для Lambda, Cloudflare Workers и периферийных окружений. Колонночный формат Lance делает встроенную работу практически применимой и при реальном масштабе.
Chroma лучше всего подходит для разработки, тестирования и небольших приложений. Если вы нацелены на очень большие корпуса данных, высокую доступность, интенсивную работу с диском или полноценный гибридный поиск, оцените production-ориентированное хранилище, прежде чем превращать прототип в инфраструктуру.
Vespa — это то, к чему обращаются, когда поиск составляет лишь половину продукта. Она объединяет лексический поиск, поиск ближайших соседей, тензоры, выражения ранжирования, группировку и обслуживание запросов в реальном времени. Эта мощь вполне реальна, но реальны и операционная и модельная сложность. Vespa больше подходит командам, занимающимся поиском и рекомендациями, чем задаче «добавить семантический поиск в моё CRUD-приложение».
ClickHouse стоит рассматривать, когда поиск связан с аналитикой. Если источником истины служат события, логи, трейсы или метрики, ClickHouse объединяет в одном SQL-движке вычисление векторного расстояния, фильтрацию, агрегацию и серьёзную полнотекстовую индексацию. Это не специализированная векторная база данных, но часто именно тот скучный и правильный вариант для аналитического поиска.
Разреженные векторы позволяют получить качество сопоставления ключевых слов уровня BM25 прямо внутри векторного индекса — без отдельного полнотекстового движка. Особенно зрелые реализации есть у Qdrant и Elasticsearch. Если гибридный поиск критичен, а архитектура из двух систем неприемлема, ищите поддержку разреженных векторов.
Выбирайте хранилище по рабочей нагрузке
- SaaS-продукт с изоляцией арендаторов → Turbopuffer
- Сложная фильтрация метаданных в большом масштабе → Qdrant
- Вы уже используете стек Elastic/ELK → Elasticsearch с DiskBBQ
- Команда работает в AWS и хочет open source → OpenSearch
- Платформа поиска и рекомендаций с серьёзными требованиями к ранжированию → Vespa
- Аналитика, наблюдаемость, поиск по логам и событиям → ClickHouse
- Миллиарды объектов on-prem / самостоятельное развёртывание → Milvus
- Периферийные окружения / serverless / мультимодальность → LanceDB
- Небольшое JS-приложение, сайт с документацией или edge-native поисковый UX → Orama
- Нулевые операционные затраты, стоимость вторична → Pinecone
- Мультимодальность в приоритете (изображения, видео, аудио) → Marqo
- Вы уже используете MongoDB → Atlas Vector Search
- Вы уже используете Postgres, но нужен больший запас по нагрузке → Supabase Vector или Neon (оба предоставляют управляемый pgvector с более удобными инструментами)
Не встраивайте идентификаторы и не ждите точного совпадения
Не используйте векторный поиск как нечёткий текстовый поиск там, где существуют правильные ответы.
«Найди пользователя с email dan@example.com» — это не задача для векторного поиска. «Найди заказ с ID ORD-12345» — тоже. Если встроить ORD-12345 в вектор и искать по косинусному сходству, вы что-нибудь получите — но это может оказаться неправильным результатом. Для идентификатора существует правильный ответ. Приблизительное совпадение по идентификатору — это баг.
Векторный поиск возвращает наиболее похожий объект из вашего набора данных, даже если на самом деле релевантных объектов нет. Он не понимает, что хорошего ответа не существует. Для связанных документов это нормально. Для точного поиска записи это серьёзная проблема: уверенно выданный неправильный ответ хуже пустого результата.
Обратное тоже верно: не используйте FTS для запросов, в которых пользователь описывает понятие. В запросе «статьи о принятии сложных решений в условиях неопределённости» нет надёжных ключевых слов. FTS вернёт либо шум, либо ничего. Используйте инструмент, соответствующий форме запроса.
Стройте поиск вокруг формы запроса
Большинству production-систем поиска нужно больше одного слоя:
pg_trgmдля имён, опечаток и автодополнения- FTS /
pg_searchдля полнотекстового поиска по ключевым словам - pgvector для семантических и концептуальных запросов
- RRF fusion для интерфейсов, где пользователи смешивают разные типы запросов
- Обычные индексы для точных идентификаторов, фильтров и отсортированных списков
Это не конкурирующие инструменты. Они дополняют друг друга. Хорошо построенная поисковая система выбирает подходящий слой для каждой формы запроса — а когда формы пересекаются, запускает несколько слоёв и объединяет результаты.
Команды, которые выпускают хорошие поисковые функции, понимают весь стек. Остальные хватаются за векторную базу данных, встраивают всё подряд, а потом удивляются, почему точный поиск иногда возвращает не ту запись.
