- Дата публикации
Семантический поиск против полнотекстового: как три модели справились с 10 000 категорий Ozon
Что открыли
Исследователь сравнил, как семантический и классический полнотекстовый поиск работают на реальной базе из 10 019 категорий Ozon. В качестве теста он взял 18 запросов в пяти тематиках — от электроники до зоотоваров — и прогнал их через четыре варианта поиска: встроенный полнотекстовый поиск PostgreSQL и три embedding‑модели.
В эксперименте участвовали:
- Qwen3‑Embedding‑0.6B от Alibaba / Qwen (1024 измерения, локальный запуск через Text Embeddings Inference на GPU).
- EmbeddingsGigaR от GigaChat Сбера (2560 измерений, API
gigachat.devices.sberbank.ru, заточена под русский язык). - text-embedding-3-small от OpenAI (1536 измерений, API
api.openai.com).
Все модели превращали текст категории в вектор и искали ближайшие векторы по косинусному расстоянию через расширение pgvector. Для 10 тысяч документов автор построил IVFFlat‑индекс со 100 кластерами. Полнотекстовый поиск работал через стандартные типы tsvector и tsquery и GIN‑индекс.
Ключевой вывод: полнотекстовый поиск остаётся очень быстрым — медиана около 1,3 мс на 10 000 документов, но резко проигрывает, когда запрос формулируют «по‑человечески», а не словами из базы. Например, запрос «лекарства» не находит категорию «Аптека», потому что в названиях корневых категорий нет слова «лекарства».
Как исследовали
Автор собрал прототип на Next.js + PostgreSQL + pgvector. База данных работала в Docker‑контейнере pgvector/pgvector:pg18, фронтенд — на node:20-alpine. Qwen3‑Embedding‑0.6B запускалась отдельно через Hugging Face Text Embeddings Inference на GPU.
Данные: CSV‑файл с 10 019 категориями Ozon. У каждой категории есть иерархический путь вида:
- «Электроника / Компьютеры / Ноутбук»
- «Строительство и ремонт / Сантехника / Смеситель»
- «Спорт и отдых / Велосипед / Электровелосипед»
- «Товары для животных / Корма и лакомства для кошек и собак»
Этот путь попадал сразу в две системы:
- В полнотекстовый поиск — через
to_tsvector('russian', path)с лемматизацией и удалением стоп‑слов. - В семантический поиск — как сырой текст для генерации вектора.
Для каждой категории три модели параллельно генерировали embedding нужной размерности и сохраняли его в отдельную таблицу:
vector(1024)для Qwen3‑Embedding‑0.6B.vector(2560)для EmbeddingsGigaR.vector(1536)для text-embedding-3-small.
По запросу система отправляла текст одновременно во все три модели, получала три вектора и искала top‑K ближайших документов по косинусному расстоянию. Для полнотекстового поиска запрос проходил через plainto_tsquery('russian', ...), а ранжирование шло по ts_rank.
Запросы подбирали так, чтобы проверить слабые места классического поиска: синонимы, разговорную лексику, отсутствие точных совпадений. Пример — слово «лекарства», которого нет в названиях корневых категорий, хотя есть раздел «Аптека» и подкатегория «Лекарственные средства».
Что это меняет на практике
Главное отличие: полнотекстовый поиск умеет только работать с совпадениями лексем. Он отлично справляется, когда пользователь пишет почти те же слова, что и в базе. Но он не понимает, что «велик» — это «велосипед», а «gaming mouse» — это «игровая мышь». И не свяжет запрос «у меня протекает кран» с категориями сантехники, если в названиях нет этих слов.
Семантический поиск работает иначе: он сравнивает смыслы. Запрос и документы живут в одном векторном пространстве, и «похожие по смыслу» тексты оказываются геометрически близко. Это позволяет находить категории, где вообще нет ни одного совпадающего слова, но совпадает тематика.
На примере запроса «лекарства» различие видно сразу:
- Полнотекстовый поиск возвращает пустой результат, потому что в данных нет слова «лекарств*».
- Семантические модели могут вывести пользователя в раздел «Аптека» или «Лекарственные средства», даже если в запросе и в названии категории нет общих лексем.
Для продуктовых команд это значит, что одной только tsvector‑поисковой строки уже недостаточно. Если нужно работать с естественными запросами, особенно в e‑commerce, медицине, путешествиях, без embeddings‑слоя поиск остаётся «буквальным» и часто бесполезным.
При этом у семантического поиска есть цена: более тяжёлые индексы, потребление памяти, задержка на вызов внешнего API (OpenAI, GigaChat) или необходимость держать GPU для Qwen3‑Embedding‑0.6B. Для миллионов документов автор предлагает смотреть в сторону HNSW‑индекса в pgvector — он даёт лучшую полноту (recall) за счёт ещё большего расхода памяти.
Что это значит для вас
Если вы строите поиск по каталогу, документации или базе знаний и до сих пор используете только tsvector, пользователю часто придётся угадывать «правильные слова». Запросы вроде «лекарства», «велики», «gaming mouse» или «протекает кран» будут работать плохо.
Подключение семантического слоя на базе Qwen3‑Embedding‑0.6B, EmbeddingsGigaR или text-embedding-3-small меняет поведение поиска: он начинает реагировать на смысл, а не только на совпадение слов. Это особенно заметно на русском языке, где разговорные формы и синонимы встречаются постоянно.
Если вы уже используете PostgreSQL, входной порог невысокий: pgvector позволяет хранить векторы прямо в той же базе, где живут документы, и искать по ним с помощью знакомого SQL. Архитектура из эксперимента — Next.js, PostgreSQL, pgvector и один или несколько embedding‑провайдеров — подходит как основа для прототипа продуктового поиска.
Пользовательский эффект прост: меньше «пустых» выдач, меньше ощущения, что поиск «не понимает по‑русски», и больше случаев, когда система догадывается, что за «лекарствами» человек на самом деле идёт в «Аптеку».