← Назад к кейсу
Пайплайн архитектуры

+25% конверсии в поиске за счёт семантического поиска по каталогу

Индустрия: Ритейл (мультикатегорийный каталог от нескольких поставщиков, ~120 тыс. SKU, растёт на ~3 тыс./неделю)
Область работ: Поиск и рекомендации на базе RAG по всему каталогу, замена поиска по ключевым словам
pgvectorOpenAI embeddingsCohere rerankFastAPI
+25% конверсии в поиске · −35% задержки запроса · покрытие каталога 100k+ SKU
Такая глубина анализа стоит за каждым нашим проектом. Мы публикуем её как подтверждение того, как мы на самом деле работаем — реальные варианты, которые мы взвешивали, цифры, которые их отсеяли, архитектура, пережившая столкновение с реальным каталогом клиента — чтобы процесс был для клиентов таким же прозрачным, как и результат. В этом проекте «высекание из камня» было не про выбор модели — это было про решение, что НЕ строить самим, потому что главная ловушка на поисковом проекте — изобретать инфраструктуру, которая уже существует.

Бриф

У клиента была витрина ритейлера среднего размера с каталогом, наполняемым несколькими поставщиками — около 120 000 SKU, растущим на несколько тысяч в неделю по мере подключения новых поставщиков. Поиск работал через SQL ILIKE по названиям товаров. Он давал плохие совпадения на всё, кроме точных попаданий по ключевым словам — покупатель, ищущий «тёплые непромокаемые ботинки», не получал ничего, если ни в одном названии товара не встречались буквально все три слова, даже если под другой формулировкой существовал десяток подходящих товаров.

Команда мерчандайзинга из четырёх человек компенсировала это вручную: вела таблицы ручных синонимов поиска и постоянно тушила пожары «почему X не находится по запросу Y». Это не масштабируемый процесс при 3000 новых SKU в неделю.

Запрос звучал так: сделать так, чтобы поиск реально понимал, что имеет в виду покупатель, оставаться быстрым по мере роста каталога, и дать мерчандайзингу возможность настраивать релевантность без заведения тикета в разработку каждый раз. Именно последний пункт — нетехнической команде нужен контроль без деплоев — определил решение по фронтенду не меньше, чем любое решение по бэкенду.

Нефункциональные требования, которые реально сформировали стек

ТребованиеПочему это важноКакое решение оно определило
Субсекундная задержка запроса, в идеале до 300 мс, даже по мере роста каталогаПоиск-по-мере-набора ощущается сломанным после ~300-400 мс; медленный поиск бросают ещё до отрисовки результатовАрхитектура поиска (§1)
Результаты поиска должны отражать актуальные цену/наличие, а не устаревший снапшотПервый в выдаче товар не в наличии убивает доверие ко всей фичеВекторное хранилище и синхронизация (§2)
Каталог большой (120 тыс. SKU) и постоянно растёт через фид поставщиковМодель или индекс, работающие только «на масштабе демо», ломаются на второй месяцМодель эмбеддингов (§3)
Топ результатов должен быть по-настоящему релевантным, а не просто тематически похожимОдно только векторное сходство выдаёт «примерно подходящие» совпадения, которые не конвертируютсяСлой реранкинга (§4)
Мерчандайзинг должен настраивать релевантность (поднимать, опускать, фильтровать) без деплоя от разработкиНетехническая команда из 4 человек не может ждать спринт, чтобы исправить плохое ранжированиеФронтенд-доставка (§5)
Окно разработки — 8 недель до осеннего релонча каталога клиентаОтсекает всё, что требует поднимать новые категории инфраструктуры с нуляВсе решения выше
§1

Архитектура поиска — свой сервис vs. готовый managed-поиск

Эскизы
  • Algolia — лидер категории managed search-as-a-service. Быстрая интеграция, вся инфраструктура индексации и ранжирования уже готова.
  • Elasticsearch / OpenSearch (self-hosted или managed) — традиционный гибридный лексический+векторный поисковый движок, полный контроль, больше операционных расходов.
  • Собственный сервис поиска на FastAPI поверх pgvector — строим пайплайн сами поверх инфраструктуры, которую уже эксплуатируем.

Почему мы не купили просто Algolia: собственный опубликованный прайсинг Algolia тарифицирует по двум измерениям — поисковые запросы и записи — от $0.50 за 1000 дополнительных запросов и $0.40 за 1000 дополнительных записей сверх бесплатного тарифа, а их тариф с AI-функциями (NeuralSearch, персонализация) прыгает до $1.75 за 1000 запросов — рост в 3.5 раза относительно стандартного поиска именно за ту возможность, которая была нужна в этом проекте. При 120 тыс. SKU с еженедельной ротацией каталога и заметном объёме запросов это прогнозируемая, но реальная постоянная стоимость, которая растёт вместе с бизнесом, а не выходит на плато. Это не аргумент против Algolia в целом — для меньшего каталога или команды, которая хочет вообще не владеть поисковой инфраструктурой, это обычно правильный выбор. Это аргумент про соответствие именно этому размеру каталога и траектории его роста.

Почему «генеративные рекомендации», а не просто «ранжированные результаты», исключили готовый ретривер сам по себе: ни Algolia, ни Elasticsearch нативно не генерируют обоснованные рекомендации на естественном языке — оба являются поисковыми движками, возвращающими ранжированные структурированные результаты. Собственные материалы Algolia позиционируют генеративные ответы как отдельный надстроечный слой («Agent Studio»), которому всё равно нужно вызывать внешнюю LLM; документация Elastic описывает тот же паттерн как «подключите нас к своему RAG-пайплайну». Реальное отличие не в том, можно ли подключить Algolia к LLM — а в том, что владение цепочкой retrieval→rerank→generation самостоятельно даёт полный контроль над prompt-инжинирингом, стоимостью за вызов генерации и логикой ранжирования, вместо оплаты чёрного ящика той же идеи через чужой AI-тариф.

Почему не Elasticsearch тоже: векторный поиск Elasticsearch способный, но это вторая система, которую нужно эксплуатировать, патчить и масштабировать независимо от Postgres — для каталога такого размера это операционные накладные расходы за возможности, которые pgvector уже покрывает на этом масштабе (см. §2 ниже). Мы пересмотрим это решение в момент, когда объём запросов или количество векторов перерастёт то, что комфортно тянет один инстанс Postgres.

Финальный выбор

Собственный сервис поиска на FastAPI. Не потому что managed-поиск плох — а потому что при 120 тыс. SKU и окне в 4 недели, за которое нужно ещё поставить реранкинг и консоль мерчандайзинга, владение пайплайном обошлось дешевле по совокупной стоимости и дало генеративный слой, который клиент реально просил, а не просто лучше ранжированные результаты по ключевым словам.

§2

Векторное хранилище — Postgres+pgvector vs. выделенная векторная база

Эскизы
  • Pinecone / Weaviate / Qdrant — специализированные векторные базы данных, managed-масштабирование, отделены от транзакционной базы каталога.
  • Postgres с расширением pgvector — хранить эмбеддинги товаров в той же базе данных, что и сам каталог.

Почему pgvector справился на этом масштабе: опубликованный бенчмарк HNSW от Supabase (сентябрь 2023) тестировал pgvector на эмбеддингах размерности OpenAI (1536), от ~224 тыс. до 1 миллиона векторов, и показал, что индексация HNSW даёт примерно в 3 раза больше запросов в секунду по сравнению со старым индексом IVFFlat при равной или лучшей точности на этом уровне железа. Наш каталог — 120 тыс. SKU сегодня, растущий, возможно, до 300-400 тыс. в течение пары лет — комфортно укладывается в диапазон, который этот бенчмарк реально тестировал, а не выходит за его пределы.

Почему хранение векторов в том же экземпляре Postgres важнее сырой скорости поиска: реальный риск здесь был не в задержке поиска, а в устаревании данных — векторный индекс, рассинхронизированный с живым каталогом, уверенно поднимет в топ товар, который распродался час назад. Когда эмбеддинги и строки каталога живут в одной базе данных, изменения цены/наличия и семантический ре-эмбеддинг можно рассуждать как одну систему, а не как две системы, которые могут незаметно разойтись. Мы используем паттерн асинхронного триггера с очередью, а не синхронный ре-эмбеддинг на каждой записи, который иначе блокировал бы обновления каталога на время внешнего API-вызова — реальный риск для таблицы, которую фиды поставщиков трогают тысячи раз в неделю.

Деталь, которая реально сэкономила инженерное время: поскольку изменения цены и наличия срабатывают тот же триггер записи, что и настоящее изменение контента (название, описание, категория), мы фильтруем на уровне триггера — ре-эмбеддинг запускается только при изменении семантических полей, а не на каждое колебание цены. Полноценные CDC-инструменты вроде Debezium существуют для команд, которым нужны развязанные, воспроизводимые потоки событий для нескольких потребителей, но одноинстансовый Postgres с фильтрующими триггерами покрывает реальную потребность этого каталога без второй единицы инфраструктуры в эксплуатации.

Финальный выбор

Postgres с pgvector, синхронизация через паттерн асинхронного триггера с очередью, отфильтрованный только на изменения семантических полей. Отслеживаем два триггера для пересмотра: приближение размера каталога к диапазону многих миллионов векторов, или реальная потребность в CDC-уровне воспроизводимых потоков событий для более чем одной потребляющей системы.

§3

Модель эмбеддингов — OpenAI text-embedding-3 vs. открытые альтернативы

Эскизы
  • Self-hosted открытые эмбеддинги (например, BGE-large) — без оплаты за вызов, полный контроль над данными, требует владения GPU-инференсом.
  • Cohere embed — сопоставимая managed-альтернатива, похожая модель ценообразования на OpenAI.
  • OpenAI text-embedding-3 (small или large) — managed, оплата за токен, плотно интегрируется с остальным нашим стеком на базе OpenAI.

Почему мы выбрали именно текущее поколение модели эмбеддингов OpenAI, а не предыдущее: собственный анонс OpenAI от января 2024 даёт реальные, датированные цифры — text-embedding-3-large набирает 64.6% по среднему бенчмарка MTEB против 61.0% у предыдущей модели ada-002, скромный, но реальный прирост на англоязычном поиске. Более решающая цифра для каталога с разноязычными данными от нескольких поставщиков — MIRACL (мультиязычный retrieval): text-embedding-3-large прыгает до 54.9% против 31.4% у ada-002 — по-настоящему большое улучшение, не маргинальное. Поскольку этот каталог собирается от нескольких поставщиков с непоследовательными (иногда неанглоязычными) описаниями товаров, именно мультиязычная цифра реально повлияла на наше решение.

Почему не self-hosted открытые эмбеддинги: в текущем лидерборде MTEB топовые открытые модели (например, BGE-M3) набирают достаточно близко к моделям OpenAI и Cohere (примерно 63 против 64-65), что сырое качество не является решающим фактором — они реально конкурентоспособны. Реальный компромисс операционный: self-hosting означает владение мощностями GPU-инференса и их масштабированием, мониторингом и отказоустойчивостью; managed API эмбеддингов означает оплату за токен без какой-либо инфраструктуры в эксплуатации. Для окна разработки в 8 недель без выделенной MLOps-функции на стороне клиента именно этот операционный компромисс — а не качество модели — исключил self-hosting здесь.

Финальный выбор

text-embedding-3-small, не -large — разрыв по MTEB между small и large меньше 3 пунктов, а small стоит примерно в пять раз дешевле large за токен. На масштабе каталога с непрерывным ре-эмбеддингом при обновлениях от поставщиков эта разница в стоимости накапливается; разница в качестве на нашем масштабе не оправдывает её оплату.

§4

Реранкинг — Cohere Rerank vs. поиск только по эмбеддингам

Эскизы
  • Поиск только по эмбеддингам (bi-encoder) — ранжирование чисто по векторному сходству, без второго прохода.
  • Cohere Rerank — второй проход кросс-энкодером, переоценивающий топ-кандидатов из векторного поиска перед выдачей финальных результатов.

Почему одного векторного сходства было недостаточно: bi-encoder ранжирует по тому, насколько близки два эмбеддинга в векторном пространстве — быстро, но это приближение релевантности, а не прямая её оценка. Бенчмарки реранкинга в независимых сравнениях на разных датасетах стабильно показывают улучшение nDCG@10 кросс-энкодерным реранкингом примерно на 5-15 пунктов по сравнению с поиском только по bi-encoder — реальный, достаточно воспроизводимый диапазон, который мы считаем защитимым базовым ожиданием, в отличие от собственного маркетингового заявления Cohere «до 25% лучше на сложных задачах retrieval», которое мы цитируем как вендорский потолок для лучшего случая, а не как среднее, которое можно обещать клиенту.

Почему добавленная задержка того стоила именно здесь: реранкинг означает второй сетевой round-trip с оценкой топ-50-200 кандидатов из векторного поиска перед выдачей финального топ-10 — реальная добавленная задержка, обычно от десятков до пары сотен миллисекунд. Для низкорискового внутреннего инструмента поиска эти накладные расходы часто того не стоят. Для поиска по каталогу e-commerce, где релевантность результатов напрямую влияет на конверсию, мы сделали противоположный выбор: дёшево получить широкий-но-неточный набор векторным поиском, а затем потратить дополнительную задержку на сужение до точного топ-10 — потому что покупатель, не нашедший нужный товар на первом экране результатов, не листает дальше, а уходит.

Финальный выбор

Cohere Rerank поверх топ-N кандидатов векторного поиска перед выдачей результатов. Это слой, наиболее напрямую ответственный за опубликованный прирост конверсии — retrieval находит правдоподобных кандидатов, а реранкинг делает так, что именно топ результатов оказывается правильным.

§5

Фронтенд-доставка — поисковый виджет и консоль мерчандайзинга

Эскизы
  • Задача А: строка поиска/автокомплита, встроенная по всей витрине — на страницах категорий, странице результатов поиска и листингах товаров. Сторонний embed на конверсионно-критичных страницах, то же ограничение, что мы применяем к любому виджету на витрине: каждый лишний килобайт JavaScript — это издержка, которую платит собственная конверсия клиента.
  • Задача Б: консоль релевантности мерчандайзинга, которой команда клиента из 4 человек пользуется ежедневно — поднимает сезонные товары, опускает отсутствующие или снятые с продажи, просматривает аналитику поиска — всё без деплоя от разработки. Никаких ограничений на встраивание, авторизованный внутренний инструмент.

Одна этикетка («поиск»), две разные фронтенд-задачи. Виджет, построенный на полноценном фреймворк-рантайме, — неверная форма для стороннего embed независимо от того, какой это фреймворк; каждый виджет на витрине, который мы поставляем, — маленький, предсказуемый, фреймворк-агностичный бандл именно по этой причине.

React зарабатывает своё место на консоли мерчандайзинга по той же причине, что и на любом внутреннем операционном инструменте, который мы строим: зрелые паттерны для плотных по данным дашбордов, никаких ограничений по размеру бандла, и команда, которая, скорее всего, в итоге будет поддерживать этот инструмент, нанимает из самого широкого доступного пула специалистов. Сделать «без деплоя» реально верным утверждением означало представить правила релевантности как данные, которые команда мерчандайзинга редактирует напрямую, а не код, который выходит через спринт.

Финальный выбор

Скомпилированный, фреймворк-агностичный Web Component для поискового виджета на витрине + React для внутренней консоли релевантности мерчандайзинга.

Финальная архитектура

Витрина: поиск и листинги — страницы категорий/поиска
Поиск/автокомплит (скомпилированный Web Component)
запрос (с debounce)
FastAPI (ASGI)
Эмбеддинг запроса (text-embedding-3-small)
Векторный поиск (pgvector, top-N)
Реранкинг (Cohere Rerank) top-N → top-K
Postgres
каталог (цена / наличие)
pgvector — эмбеддинги товаров
правила релевантности · аналитика поиска
Консоль мерчандайзинга
React · внутренняя · поднять/опустить · правила без деплоя

Как цифры связаны с решениями

Ни одна из трёх опубликованных цифр не случайна — каждая является следствием конкретной пары решений выше:

  • +25% конверсии в поиске — следствие §3 и §4 вместе: лучшая модель эмбеддингов находит по-настоящему релевантных кандидатов, а реранкинг превращает «правдоподобные совпадения» в «правильный топ-10». Ни то, ни другое по отдельности не даёт этого результата.
  • −35% задержки запроса на первый взгляд выглядит нелогично — мы же добавили дополнительный round-trip реранкинга, это должно замедлять, а не ускорять. Объяснение — в том, что мы заменили: старая система была неиндексированным сканированием ILIKE по таблице из 120 тыс. строк, которое сильно деградирует на этом размере. Правильно проиндексированный векторный поиск HNSW плюс реранкинг над отфильтрованным топ-N кандидатов всё равно быстрее полного сканирования таблицы по паттерну, даже с дополнительным сетевым переходом.
  • Покрытие каталога 100k+ SKU — прямой результат §2 и §3 вместе: pgvector комфортно справляется с этим масштабом согласно бенчмарку Supabase, а отфильтрованный асинхронный пайплайн ре-эмбеддинга означает, что новые и обновлённые SKU из еженедельного фида поставщиков реально попадают в индекс поиска, а не устаревают позади медленной batch-задачи.

Опубликованные, именованные кейсы вендоров в этой сфере (кейс Algolia про ManoMano: +20% конверсии; Lacoste: +37%; Gémo: коэффициент конверсии ×2.3) ставят +25% нашего клиента ровно в достоверный, ничем не примечательный диапазон результатов «перешли на AI-поиск» — мы не заявляем аномальный результат, мы объясняем, почему попадание в середину этого диапазона потребовало всех пяти решений выше, а не просто «добавить векторную базу».

Где эта архитектура перестаёт быть правильной

Стоит сказать прямо, потому что ни одна архитектура не вечна:

Каталог вырастает в диапазон многих миллионов векторов, или объём запросов перерастает один инстанс PostgresПересмотреть выделенную векторную базу данных (§2)
Каталог заметно расширяется на неанглоязычные рынкиПересмотреть -large или выделенную мультиязычную модель эмбеддингов (§3)
Объём ре-эмбеддинга перерастает один воркер триггера с очередью, или второй системе нужны те же события измененийПересмотреть CDC-инструменты вроде Debezium (§2)
Клиент перерастает мерчандайзинг на основе правил и нуждается в реальной персонализации на уровне каждого покупателя в масштабеЭто другая система (архитектура рекомендаций/feature store), а не расширение консоли (§5)

Ни один из этих пунктов не является провалом исходного решения — это условия, при которых тот же самый процесс рассуждения, запущенный заново, дал бы другой ответ.

Источники и уровень доверия

Прайсинг Algolia: аддитивно запросы + записи, AI-тариф в 3.5 раза дороже стандартного
Официальная страница цен AlgoliaВысокий — прямо со страницы цен Algolia
Ни Algolia, ни Elasticsearch нативно не генерируют обоснованные LLM-рекомендации
Блог Algolia + документация Elastic по RAGВысокий — так оба вендора сами это описывают
pgvector HNSW: ~3x QPS над IVFFlat при равной/лучшей точности, протестировано до 1М векторов размерности 1536
Инженерный блог Supabase, «pgvector v0.5.0», сент. 2023Высокий — Supabase опубликовали полную методологию, цифры можно перепроверить напрямую
Асинхронный триггер с очередью — стандартный паттерн синхронизации pgvector с изменяющейся таблицей; CDC/Debezium — для мульти-потребительских сценариев
Документация Supabase «Automatic Embeddings»Высокий — это задокументированный паттерн от самого Supabase
OpenAI text-embedding-3-large: MTEB 64.6% против 61.0% у ada-002; MIRACL 54.9% против 31.4%
Блог OpenAI, «New embedding models and API updates», янв. 2024Высокий — собственный анонс OpenAI, с датой и конкретными цифрами
Текущая расстановка MTEB: Cohere embed-v4 ≈65.2, OpenAI-3-large ≈64.6, BGE-M3 ≈63.0
Лидерборд MTEB на Hugging FaceСредний — лидерборд живой, расстановка наверняка уже сдвинулась
Реранкинг улучшает nDCG@10 примерно на 5-15 пунктов по сравнению с bi-encoder-only
Агрегированные бенчмарки реранкеров на разных датасетахСредний — несколько независимых источников сходятся в этом диапазоне, но это не одно чистое исследование
Cohere Rerank 3.5: «до 25% лучше на сложных задачах retrieval»
Cohere / AWS Big Data BlogСредний — это собственная цифра Cohere для лучшего случая, а не среднее
Именованные кейсы: ManoMano +20% конверсии, Lacoste +37%, Gémo коэффициент ×2.3
Кейсы клиентов AlgoliaСредний — опубликовано вендором, но это реальные названные клиенты, а не обобщённая статистика

Мы публикуем эту таблицу доверия намеренно. Клиенту полезнее документ «вот в чём мы уверены, а что мы бы перепроверили», чем документ, который выглядит чисто только потому, что неопределённость из него тихо вычистили.