Перейти к основному содержимому

Поиск и обнаружение — поисковая проекция, ранжирование, фасеты, экономика запросов

Версия: 1.0 Дата: 26.04.2026 Статус: Готов к обсуждению

Назначение документа

Документ определяет доменный контур поиска и обнаружения (search and discovery contour) платформы Vitiana — поисковую проекцию, движок поиска, модель ранжирования, фасеты, гео-запросы, многоязычный поиск, экономику запросов как метрику тарификации, защиту от нагрузки, наблюдаемость качества, фазы развёртывания.

Документ читается после:

В корневых документах зафиксировано что контур делает и где он живёт. Этот документ описывает как — поисковую проекцию, движок, ранжирование, экономику и наблюдаемость.

Поиск и обнаружение — главный пользовательский путь платформы. Через него проходит подавляющее большинство запросов на партнёрской поверхности, на потребительской витрине и на поверхности агентств. Качество, скорость и стоимость поиска прямо определяют конкурентоспособность платформы как маркетплейса программных интерфейсов.

Главное решение

Поиск — это отдельный доменный контур со своей каноничной проекцией, не выходное окно над хранилищем предложений.

Это означает:

  • Результат поиска — это SearchProjection, специальная поисковая проекция, не Offer напрямую. Поисковая проекция кэшируется, имеет короткий срок жизни, пересобирается при изменении исходных данных. Это закреплено в Каноничная доменная ось: «результат поиска — не offer-объект напрямую, а проекция».
  • Движок поиска — отдельный сервис (OpenSearch на фазе 1–2; ML-ranking как надстройка с фазы 3) с собственным жизненным циклом, отдельной упаковкой вычислений (compute packaging) и собственной политикой масштабирования.
  • Ранжирование — каноничная функция платформы, не внешняя зависимость. Платформа сама определяет, как сортировать результаты для каждого тенанта и каждой поверхности взаимодействия.
  • Фасеты, гео-запросы, многоязычный поиск — первоклассные возможности с фазы 1, не «фичи на потом».
  • Экономика запросов (стоимость поиска как метрика тарификации) встроена в контур с самого начала. Сложный поиск стоит партнёру дороже простого; партнёр, нагружающий «дорогих» поставщиков, платит больше.
  • Защита от нагрузки — многоуровневая (адаптивная, тенантная, глобальная) с самого начала, не «когда упадём».
  • Наблюдаемость качества — поиск без метрик качества (релевантность, конверсия, отказы) — это слепой поиск, недопустимый для маркетплейса.

Правило 00000 (платформа главенствует над поставщиками) применяется здесь напрямую: контур поиска не подгоняется под структуру поисковых ответов конкретного поставщика. Платформа задаёт каноничную поисковую проекцию; поставщики предоставляют сырые данные, которые слой приёма данных нормализует.

Каноничная модель поискового контура

Главные сущности

SearchSession — поисковая сессия

Сущность, представляющая серию связанных поисковых запросов одного пользователя в рамках одного намерения (например, «отель в Праге на даты 15–20 июня для двух взрослых»). Сессия объединяет начальный запрос, последующие уточнения (применение фасетов, изменение фильтров, переходы между страницами), просмотры предложений.

Поля:

  • session_id — каноничный идентификатор сессии.
  • tenant_id — какой тенант инициировал сессию.
  • surface_id — какая поверхность взаимодействия (Partner / B2C / Agency / Tour Builder).
  • actor_context — контекст актёра (агент агентства, конечный клиент, программный клиент партнёра).
  • client_locale — язык интерфейса клиента (для многоязычного ранжирования и фасетов).
  • client_currency — валюта отображения цен.
  • applied_filters — текущий набор примененных фильтров.
  • applied_facets — текущий набор примененных фасетов.
  • result_strategy — стратегия ранжирования, выбранная для этой сессии (см. ниже).
  • created_at, last_active_at — временные метки.
  • expires_at — момент истечения сессии (типично 30 минут неактивности).

SearchSession нужна для:

  • Контекстной аналитики (последовательность запросов одного намерения).
  • Кэширования результатов между уточнениями фасетов (повторное использование подмножества кандидатов).
  • Привязки коммерческой фиксации (Quote) к исходной поисковой сессии для отслеживания пути к бронированию.

SearchQuery — конкретный запрос

Каноничная структура поискового запроса:

  • query_id — каноничный идентификатор.
  • session_id — ссылка на сессию.
  • intent_type — тип намерения (accommodation / tour_package / transfer / excursion / mixed).
  • geo_filter — географический фильтр (точка + радиус, регион, область, маршрут с несколькими точками).
  • date_filter — даты заезда и выезда, длительность, гибкие даты.
  • occupancy_filter — состав путешественников (взрослые, дети по возрастам, пожилые, домашние животные).
  • attribute_filters — фильтры атрибутов (звёздность, удобства, тип проживания).
  • price_filter — диапазон цены, валюта, режим (с налогами / без).
  • text_query — свободный текстовый запрос (опционально).
  • excluded_supplier_ids — список поставщиков, исключённых из результата (по политике тенанта или по операционным причинам).
  • complexity_score — рассчитанная сложность запроса (см. секцию «Экономика запросов»).
  • created_at — временная метка.

SearchProjection — поисковая проекция

Главная сущность поискового контура — материализованная проекция предложений, оптимизированная для поиска.

Свойства, фиксированные в Каноничная доменная ось и в Хранение:

  • Кэшируется в движке поиска (search engine).
  • Имеет короткий срок жизни (типично от десятков секунд до нескольких минут — зависит от свежести цены).
  • Пересобирается при изменении исходных данных через канал событий job.offer.projection.rebuild (см. Каталог каналов событий).
  • Не хранит полную audit-grade историю — только текущий снимок.
  • Не используется для коммерческой фиксации напрямую. Цена в проекции — это индикативная цена (indicative price), не зафиксированное коммерческое обещание.

Поля проекции:

  • projection_id — каноничный идентификатор.
  • property_id — ссылка на каноничную сущность Property.
  • supplier_id — поставщик, предоставивший предложение для этой проекции.
  • display_name — отображаемое название (с учётом языка тенанта).
  • geo_point — координаты для гео-запросов.
  • region_path — иерархия регионов (страна → регион → город → район).
  • attribute_facets — атрибуты для фасетного поиска (звёздность, тип, удобства, возрастные ограничения).
  • availability_window — временное окно, в котором проекция актуальна.
  • indicative_price — индикативная цена в базовой валюте.
  • indicative_currency — базовая валюта проекции (платформа конвертирует в валюту тенанта при выдаче).
  • language_payload — мультиязычный набор отображаемых полей (название, краткое описание).
  • media_summary — миниатюра и базовый набор изображений (полная медиа-выдача — отдельным запросом).
  • ranking_signals — набор сигналов для ранжирования (см. секцию «Ранжирование»).
  • freshness_class — класс свежести (live / cached / stale).
  • last_refreshed_at — момент последней пересборки.
  • expires_at — момент истечения проекции.

DiscoveryFacet — фасет

Сущность, представляющая фасетную ось для уточнения поиска. Фасеты вычисляются на основе текущего множества кандидатов.

Главные типы фасетов:

  • Числовые — диапазон цены, рейтинг, год постройки.
  • Категориальные — звёздность, тип проживания, удобства, бренд.
  • Иерархические — регионы (страна → регион → город), категории удобств (с группировкой).
  • Гео — расстояние от точки, кластеры на карте.

Поля:

  • facet_id — каноничный идентификатор фасета.
  • facet_type — тип.
  • axis — атрибут поисковой проекции, по которому строится фасет.
  • values — массив значений с количеством совпадений (для отображения «Wi-Fi (1234)» и аналогично).
  • applicable_locales — для каких языков отображается этот фасет (некоторые фасеты культурно-специфичны).

RankingPolicy — политика ранжирования

Каноничная политика, определяющая стратегию ранжирования для конкретной комбинации поверхности и тенанта.

Поля:

  • policy_id — каноничный идентификатор.
  • applicable_surfaces — на каких поверхностях применяется.
  • applicable_tenants — для каких тенантов применима (с возможностью дефолта).
  • signals — список сигналов ранжирования с весами.
  • tie_breaker_strategy — как разрешается равенство.
  • diversification_rules — правила разнообразия (например, не более N предложений одного поставщика подряд).
  • boost_rules — правила повышения определённых классов предложений.
  • experiment_id — ссылка на эксперимент A/B-тестирования платформы (см. Платформа A/B-тестирования, фаза 4).

Связь с другими каноничными сущностями

Поток поискового запроса

Запрос с поверхности взаимодействия

Валидация запроса и нормализация

Расчёт сложности запроса (complexity_score)

Применение тенантной политики и квоты

├── В рамках квоты — продолжение
└── Сверх квоты — отказ с retry-after

Привязка к SearchSession (создание или продолжение)

Запрос к движку поиска с применением фильтров

Получение множества кандидатов (typically до 1000)

Применение RankingPolicy

├── Фаза 1-2: правила-based ранжирование с эвристиками
└── Фаза 3+: ML-модель ранжирования (re-ranker)

Применение правил разнообразия (diversification)

Подмножество для текущей страницы (typically 20-50 результатов)

Параллельный расчёт фасетов (DiscoveryFacet) на множестве кандидатов

Конвертация валют, локализация

Сборка ответа: проекции + фасеты + метаданные

Учёт потребления (metering)

Отправка наблюдаемых событий

Ответ на поверхность

Движок поиска

Технологический выбор по фазам

Конкретный технологический стек зафиксирован в Технологический фундамент реализации: на фазе 1–2 — OpenSearch (или Elasticsearch open-source) как движок поиска. На фазе 3 — добавление ML-слоя ранжирования поверх OpenSearch.

Здесь — архитектурные требования к движку поиска (независимо от конкретной реализации):

Требование 1. Свобода замены

Движок поиска должен быть заменяем без переделки каноничной модели. Это обеспечивается:

  • Каноничная модель SearchProjection не повторяет схему OpenSearch.
  • Слой адаптера движка поиска (search engine adapter) переводит каноничные запросы в специфические запросы движка.
  • Поддерживаются открытые стандарты (Lucene-based индексация, JSON-API), не проприетарные форматы.

Требование 2. Многоязычная индексация с самого начала

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

  • Каждое мультиязычное поле проекции индексируется на каждом языке отдельно.
  • Используются языковые анализаторы (language analyzers) — стемминг, токенизация, удаление стоп-слов специфичные для языка.
  • При поисковом запросе на одном языке движок ищет преимущественно на этом языке, с резервным fallback на английский.

Требование 3. Гео-запросы первоклассные

  • Поддержка гео-точки, гео-радиуса, гео-полигона, гео-маршрута.
  • Кластеризация для отображения карт.
  • Расчёт расстояния как сигнал ранжирования.
  • Поддержка иерархии регионов (страна → регион → город → район).

Требование 4. Фасеты с агрегациями

  • Числовые агрегации (мин, макс, гистограмма цен).
  • Категориальные агрегации (счётчики по значениям).
  • Иерархические агрегации (для регионов и категорий удобств).

Требование 5. Кэширование с инвалидацией по событиям

  • Поисковые проекции кэшируются на уровне движка.
  • Инвалидация — событийная (по job.offer.projection.rebuild), не временная.
  • При изменении предложения у поставщика — пересобирается только затронутая проекция, а не весь индекс.

Требование 6. Высокая параллельная нагрузка

  • Тысячи поисковых запросов в секунду на пиковой нагрузке.
  • Задержка на 95-м процентиле (p95) ниже 300 мс для типичного поиска (без сложных фасетов и широких гео-запросов).
  • Деградация под нагрузкой — управляемая (graceful degradation), не каскадные сбои.

Слой материализации проекций

Между каноничным хранилищем Offer и движком поиска работает слой материализации проекций (offer-to-search-projection materialization layer).

Поток:

Изменение Offer

Публикация события offer.published / offer.updated / offer.withdrawn

Слой материализации получает событие

Применение правил публикационной целостности (offer integrity rules)

Сборка SearchProjection из канонических данных (Property + Offer + Media)

Локализация на базовый набор языков

Индексация в движок поиска

Публикация события search.projection.indexed

Подробности — в Целостность предложения и контроль публикации. Здесь — операционная сторона:

  • Слой материализации работает как поток (streaming) с гарантией eventual consistency.
  • Целевая задержка от события offer.published до индексации в поиске — менее 5 секунд.
  • При массовой пересборке (например, при подключении нового поставщика) — отдельный пакетный поток с управляемой нагрузкой на движок поиска.

Ранжирование

Многоуровневая модель ранжирования

Ранжирование разделено на три уровня, каждый с собственной фазой развёртывания:

Уровень 1. Базовая релевантность (фаза 1, обязательно)

Простая модель, понятная и предсказуемая:

  • Соответствие гео-запросу (точное совпадение региона > близость к точке).
  • Соответствие датам и доступности.
  • Соответствие фильтрам атрибутов.
  • Цена в пределах фильтра пользователя.
  • Базовый рейтинг отеля (если есть).

Реализуется как скоринговая функция в OpenSearch без машинного обучения.

Уровень 2. Эвристическое ранжирование (фаза 2)

Поверх базовой релевантности накладываются эвристики:

  • Свежесть данных — предложения с актуализированной за последние N часов ценой ранжируются выше.
  • Профиль поставщика — предложения от поставщиков с высоким классом риска (high-trust) ранжируются выше.
  • Историческая конверсия по предложению — предложения с высокой исторической конверсией просмотра в бронирование ранжируются выше.
  • Свежесть медиа — предложения с актуальными изображениями ранжируются выше «голых» без медиа.
  • Разнообразие — правила, предотвращающие монополизацию выдачи одним поставщиком.

Эвристики — конфигурируемые (без релизов кода), привязаны к RankingPolicy.

Уровень 3. ML-ранжирование (фаза 3)

Поверх эвристик добавляется ML-ранжировщик (re-ranker) — нейросетевая или градиентного бустинга модель, обучаемая на исторических данных платформы. Подробности — в Платформа машинного обучения, фаза 3.

Сигналы для ML-модели:

  • Все сигналы уровня 1 и 2.
  • Поведенческие сигналы пользователя (просмотры, клики, конверсия).
  • Сигналы похожести (collaborative filtering — пользователи, похожие на текущего, предпочитали такие предложения).
  • Сигналы партнёра (профиль партнёра, сегмент его конечных клиентов).
  • Сигналы сезонности и пиковых дней.

ML-ранжирование разворачивается за уровнями 1–2: даже при отказе ML-модели поиск возвращает результаты с эвристическим ранжированием.

Стратегии ранжирования по поверхности

Каждая поверхность имеет свою каноничную стратегию, привязанную через RankingPolicy:

  • Потребительская витрина (B2C Storefront Surface) — баланс релевантности и коммерческого преимущества (комиссии). Высокий вес персонализации.
  • Партнёрская поверхность (Partner API Surface) — стабильное, предсказуемое ранжирование. Партнёр должен иметь возможность повторять результат при тех же входных данных. Запрет случайных сдвигов.
  • Поверхность для агентств (Agency Working Surface) — ранжирование с приоритетом скорости работы агента (популярные предложения первыми, известные бренды первыми).
  • Tour Builder Closed Surface — ранжирование внутри композиций тура с учётом совместимости компонентов.

Стабильность ранжирования для партнёрской поверхности

Партнёр, повторяющий поисковый запрос с теми же параметрами в течение окна стабильности (типично 5 минут), получает тот же порядок результатов. Это критическое свойство для:

  • Корректной работы пагинации партнёрского интерфейса.
  • Кэширования партнёром промежуточных результатов.
  • Стабильности коммерческой фиксации (Quote) — партнёр видит то предложение, которое выбрал.

Реализуется через детерминированные tie-breakers и ключ окна стабильности.

Запрет захватных правил ранжирования

  • ❌ Захватные правила в пользу конкретного поставщика без явного бизнес-обоснования.
  • ❌ Зашитое жёсткое правило «всегда выше Stuba» — запрещено правилом 00000.
  • ❌ Скрытые модификаторы ранжирования без отражения в RankingPolicy.

Фасеты

Каноничный набор фасетов

Базовый набор фасетов фазы 1, доступный на всех поверхностях:

  • Цена (range slider) — диапазон с гистограммой распределения.
  • Регион (hierarchical) — страна → регион → город → район.
  • Тип проживания (multi-select) — отель, апартаменты, вилла, хостел, гостевой дом.
  • Звёздность (multi-select) — 1, 2, 3, 4, 5.
  • Удобства (multi-select, hierarchical) — Wi-Fi, бассейн, парковка, питание, и т.д., с группировкой по категориям.
  • Расстояние от точки (range slider) — для гео-запросов.

Фасеты по поверхности

Дополнительные фасеты, доступные на конкретных поверхностях:

  • Партнёрская — фасет «класс риска поставщика» (для тенантов с собственным риск-профилем), фасет «профиль свежести данных».
  • Agency — фасет «брендированный поставщик» (для агентств, работающих преимущественно с известными брендами).
  • B2C — фасет «отзывы клиентов», «сервис ALL inclusive».

Расчёт фасетов

Фасеты рассчитываются на множестве кандидатов (typically до 1000) после применения текущих фильтров. Это ограничивает стоимость расчёта и обеспечивает релевантность фасетов текущему контексту поиска.

При применении фасета пользователем — фильтр добавляется в SearchQuery.attribute_filters, и поиск перестраивается. Фасеты пересчитываются на новом множестве.

Гео-запросы

Поддерживаемые операции

  • Точка с радиусом — «отели в 5 км от центра Праги».
  • Регион (полигон) — «отели в Чехии», «отели в Карловарском крае».
  • Иерархия регионов — «отели в Восточной Европе» (как объединение стран).
  • Маршрут — «отели вдоль маршрута Прага → Вена → Будапешт» (для Tour Builder, с указанием максимального отклонения от маршрута).
  • Множество точек — «отели около любого из этих 10 объектов» (для тематических поисков).

Многоуровневая иерархия регионов

Иерархия регионов фиксируется в каноничной геомодели платформы. Подробности — в reference/geo-domain.md (открытая развилка — отдельный документ может быть создан в фазе 5 при необходимости детализации). Базовая модель уже зафиксирована в существующей реализации home-to-go-api (см. GEO_DATABASE.md, GEO_CHAIN.md).

Уровни иерархии:

country (страна, ISO 3166)

region (регион — край, область, штат)

city (город)

district (район города)

poi (точка интереса — opcional)

Геокодирование запроса

Текстовый гео-запрос пользователя (например, «возле центра Праги») преобразуется в каноничный гео-запрос:

  • Распознавание именованной сущности (named entity recognition) — определение, что это «Прага» + «центр».
  • Геокодирование (geocoding) — конвертация имени в координаты.
  • Применение к канонической геомодели платформы.

На фазе 1 — простое геокодирование через каталог регионов; на фазе 3 — ML-улучшенное распознавание контекста запроса.

Многоязычный поиск

Базовый набор языков

С фазы 1: украинский (UK), русский (RU), чешский (CZ), польский (PL), английский (EN), казахский (KZ).

Стратегии многоязычного поиска

Стратегия А. Локализованная индексация (рекомендуется)

Каждое мультиязычное поле индексируется в отдельном поле для каждого языка с языковым анализатором:

  • display_name_ru, display_name_uk, display_name_cz, display_name_pl, display_name_en, display_name_kz.
  • При поиске на конкретном языке движок использует поле этого языка с приоритетом.
  • При отсутствии перевода — fallback на английский.

Стратегия Б. Кросс-язычный поиск

Запрос на одном языке может искать по полям другого языка через переводной словарь или кросс-язычные эмбеддинги (cross-lingual embeddings, доступно с фазы 3 при наличии ML-инфраструктуры).

Это критично для случаев, когда:

  • Партнёр работает на украинском, но в данных поставщика есть только английская версия.
  • Конечный клиент ищет «Прага» на русском, а название региона в данных — «Praha» на чешском.

Локализация фасетов

Значения фасетов также локализованы. Например, удобство «Wi-Fi» отображается как «Wi-Fi» на любом языке (бренд), но «Бесплатная парковка» — на каждом языке свой перевод.

Экономика запросов

Принцип

Поиск — самый частый и потенциально дорогостоящий вызов программного интерфейса. Партнёр, делающий 1 миллион поисковых запросов в день, объективно дороже партнёра, делающего 100 тысяч целевых запросов. Платформа отражает это в динамическом ценообразовании платформенных услуг (см. Архитектурный якорь и бизнес-модель и Учёт потребления и квоты).

Сложность запроса (complexity_score)

Каждый поисковый запрос получает сложностный балл (complexity_score), рассчитываемый из множителей:

МножительЧто увеличивает сложность
Широта гео-запросаБольшая страна > малая страна > регион > город > точка с малым радиусом
Длительность диапазона датГибкие даты «±3 дня» > точные даты
Количество фильтров атрибутовКаждый дополнительный фильтр
Количество фасетов в расчётеКаждый дополнительный фасет
Количество поставщиков в выборкеВсе поставщики > подмножество
Применение текстового запросаТекстовый запрос > только структурный
Кросс-язычный поискБольше языков в индексе для запроса

Сложностный балл — отдельный множитель к базовой стоимости поискового вызова. Базовый вызов = 1 балл; сложный вызов = 5–10 баллов.

Профиль нагрузки на поставщиков (supplier load profile)

Партнёр, использующий «дорогих» поставщиков (с низкими лимитами вызовов поставщика, с медленным API, с высокой долей failed-ответов) — потребляет больше ресурсов платформы. Это отражается в множителе профиля нагрузки на поставщиков (supplier load profile multiplier).

Защита от нагрузки

Многоуровневая защита, описанная в Программный интерфейс как продукт (rate limiting):

  • Глобальный лимит на весь поисковый контур.
  • Тенантный лимит в соответствии с тарифом партнёра.
  • Адаптивная деградация — при перегрузке движка поиска временно ограничивается допустимая сложность запроса (например, отключаются широкие гео-запросы и текстовый поиск, остаются только точечные).
  • Защита от всплесков — короткие пики разрешены, длительные ограничиваются.

Запрет на «бесплатные тяжёлые запросы»

  • ❌ Поиск без фильтра региона на бесплатном тарифе (Free) — каждый такой вызов проходит через весь индекс.
  • ❌ Запросы с включённым текстовым поиском без фильтра региона на тарифах ниже Professional.
  • ❌ Гео-запросы с радиусом более 500 км без явной поддержки в тарифе.

Эти ограничения встроены в политику тарифов, не в код поискового движка.

Кэширование

Многоуровневое кэширование

Платформа применяет три уровня кэша для поисковых результатов:

Уровень 1. Кэш проекции (projection cache)

  • Кэш в самом движке поиска (OpenSearch caches).
  • Срок жизни — связан со свежестью данных (типично десятки секунд до нескольких минут).
  • Инвалидация — событийная (по job.offer.projection.rebuild).

Уровень 2. Кэш результата запроса (query result cache)

  • Хранит результат конкретного запроса (полный SearchQuery) с его множеством кандидатов.
  • Срок жизни — короткий (десятки секунд).
  • Кэш по ключу хеша запроса.
  • Используется для повторов того же запроса разными пользователями (типично — конкурирующие запросы на популярные направления).

Уровень 3. Кэш сессии (session cache)

  • Хранит множество кандидатов в рамках SearchSession.
  • Используется при применении фасетов — фасеты применяются на закэшированном множестве, без повторного запроса в движок.
  • Срок жизни — длительность сессии (типично 30 минут).

Запрет «вечного кэша»

  • ❌ Кэширование без явного срока жизни.
  • ❌ Кэширование без событийной инвалидации.
  • ❌ Кэширование цены — цена должна либо браться из живых данных проекции с актуальностью, либо явно помечаться как индикативная (indicative).

Наблюдаемость качества поиска

Метрики качества

Платформа собирает следующие метрики качества поиска (без них поиск — слепой):

Метрики релевантности

  • CTR на позиции (click-through rate by position) — какова доля переходов от просмотра выдачи к открытию предложения, разбитая по позиции в выдаче.
  • Глубина просмотра (browse depth) — на какой странице/позиции пользователь чаще всего находит то, что искал.
  • Доля отказов (zero-result rate) — какова доля запросов, возвращающих пустую выдачу.
  • Доля уточнений (refinement rate) — какова доля запросов, требующих применения фасетов.
  • Конверсия выдача → коммерческая фиксация — какова доля сессий поиска, заканчивающихся Quote.
  • Конверсия выдача → бронирование — какова доля сессий поиска, заканчивающихся подтверждённым бронированием.

Метрики производительности

  • Задержка p50, p95, p99 для поискового вызова.
  • Доля fallback на эвристическое ранжирование при недоступности ML-слоя (фаза 3+).
  • Доля деградаций при перегрузке (когда возвращается урезанная выдача).

Метрики экономики

  • Средняя сложность запроса по тенанту.
  • Распределение затрат на поиск по поставщикам, сложности, поверхностям.
  • Доля «дорогих» запросов в общем потоке.

Каналы передачи метрик

Все метрики поиска публикуются в каналы наблюдаемости:

Эксперименты A/B-тестирования

С фазы 4 — встроенная возможность экспериментов A/B-тестирования над поисковым ранжированием:

  • Сегментация трафика по experiment_id в RankingPolicy.
  • Параллельное измерение метрик качества для контрольной и тестовой группы.
  • Платформа экспериментов — отдельный домен (см. Платформа A/B-тестирования, фаза 4).

События поискового контура

Контур поиска и обнаружения публикует следующие каноничные события:

СобытиеКогда публикуетсяГлавные потребители
search.session.startedПри создании поисковой сессииКонтур аналитики, контур учёта потребления
search.query.executedПосле выполнения каждого запросаКонтур учёта потребления, контур аналитики
search.result.viewedПри просмотре пользователем выдачиКонтур аналитики, контур экспериментов
search.facet.appliedПри применении фасета пользователемКонтур аналитики, контур качества поиска
search.projection.indexedПосле материализации проекции в движкеКонтур наблюдаемости свежести данных
search.degradation.activatedПри активации деградации под нагрузкойКонтур наблюдаемости, контур инцидентов
search.experiment.exposureПри показе пользователю экспериментального ранжированияКонтур экспериментов, контур аналитики

Полная таксономия — в Первоначальная таксономия событий.

Фазы развёртывания контура поиска

Фаза Bootstrap (0–6 месяцев)

Что разворачивается:

  • Каноничная модель SearchSession, SearchQuery, SearchProjection, DiscoveryFacet, RankingPolicy в схеме хранения.
  • Базовый движок поиска (OpenSearch self-hosted в Docker на VPS-3) с одним индексом для прототипа.
  • Слой материализации проекций — потоковая обработка событий offer.published/offer.updated.
  • Базовое ранжирование (уровень 1) — простые скоринговые функции.
  • Базовый набор фасетов (цена, регион, тип, звёздность, удобства).
  • Гео-запросы базовые (точка-радиус, регион).
  • Многоязычная индексация для базового набора языков.
  • Расчёт сложности запроса (complexity_score) — простой подсчёт множителей.
  • Учёт потребления (metering) поисковых вызовов.
  • Базовая наблюдаемость (метрики p50/p95, доля отказов, конверсия).

Триггер выхода:

  • Готовность к боевому запуску первого тенанта (vitrip.store).
  • Стабильное ранжирование с предсказуемым порядком для партнёрской поверхности.

Фаза 2 — Production launch (6–12 месяцев)

Что разворачивается:

  • Управляемый OpenSearch (Managed OpenSearch в OVHcloud Public Cloud) — переход с self-hosted.
  • Эвристическое ранжирование (уровень 2) с конфигурируемыми правилами.
  • Расширенные фасеты, специфичные для каждой поверхности.
  • Правила разнообразия (diversification rules) в выдаче.
  • Многоуровневое кэширование (проекции + результат + сессия).
  • Адаптивная защита от нагрузки.
  • Наблюдаемость качества с пользовательскими дашбордами.

Триггер выхода:

  • Объём запросов превышает возможности одного OpenSearch-кластера.
  • Появляется потребность в персонализации ранжирования (B2C-витрина).
  • Появляются первые партнёры, требующие эксперименты A/B.

Фаза 3 — ML-ранжирование и Multi-region (12–24 месяца)

Что разворачивается:

  • ML-слой ранжирования (re-ranker) поверх эвристик — нейросетевая или GBDT-модель.
  • Многорегиональная репликация поискового индекса для глобальной задержки.
  • Кросс-язычный поиск через эмбеддинги.
  • ML-улучшенное распознавание контекста гео-запроса.
  • Платформа аналитики поиска для партнёров (тарифы Professional, Enterprise).

Фаза 4 — Эксперименты, виртуальные помощники, новые поверхности (24+ месяцев)

Что разворачивается:

  • Полная платформа A/B-экспериментов над ранжированием.
  • Виртуальные помощники для конечных клиентов (B2C-витрина) с ML-driven подсказками.
  • Голосовой поиск (опционально, по запросу партнёров).
  • Расширение на новые типы инвентаря (туры, экскурсии, трансферы как первоклассные сущности поиска).

Архитектурные решения с тезисным обоснованием

Решение 1. Поисковая проекция — отдельная сущность, не Offer напрямую

Цель: обеспечить высокую производительность поиска без compromise на источнике истины предложений.

Тезисы:

  1. Offer — это источник истины с полной audit-grade историей. Поиск по такой модели медленный (множество соединений, много полей).
  2. SearchProjection — это денормализованная проекция, оптимизированная для чтения. Она кэшируема, пересобираема, не несёт ответственности за audit.
  3. Современные лучшие практики поисковых платформ (Algolia, Elasticsearch enterprise) — все построены на разделении источника истины и поисковой проекции.
  4. Это закреплено в Каноничная доменная ось: «результат поиска — не offer-объект напрямую, а проекция».

Альтернатива: искать напрямую в каноничной таблице Offer. Отклонено: невозможно достичь требуемой задержки p95 ниже 300 мс на масштабе.

Решение 2. Движок поиска — open-source стандарт (OpenSearch/Elasticsearch), не проприетарный SaaS

Цель: избежать захватной интеграции с Algolia или подобным проприетарным сервисом.

Тезисы:

  1. Правило 00000 — платформа сама задаёт правила. Захватная интеграция с проприетарным движком поиска противоречит этому.
  2. OpenSearch имеет полнофункциональный набор возможностей для платформы первой волны (многоязычность, гео, фасеты, скоринг, ML).
  3. Стоимость управляемого OpenSearch у OVHcloud в фазе 2–3 ниже сопоставимой Algolia или ElasticCloud при наших объёмах.
  4. Возможность миграции на собственный кластер при достижении масштаба (фаза 4) сохраняется при использовании open-source.

Альтернатива: Algolia как первичный движок. Отклонено: захватная интеграция, ограниченные возможности гео и многоязычности под наши задачи, более высокая стоимость.

Решение 3. Многоуровневое ранжирование с фазированной реализацией

Цель: не блокировать запуск платформы ожиданием ML-инфраструктуры.

Тезисы:

  1. На фазе 1 базовое ранжирование (релевантность + цена + рейтинг) даёт результат, удовлетворительный для прототипа.
  2. ML-ранжирование — это отдельная инфраструктурная инвестиция (фаза 3), которая невозможна на фазе 1 без жертв в других контурах.
  3. Эвристическое ранжирование на фазе 2 закрывает основные пробелы базового, не требуя ML.
  4. Фазированная модель — это расширение, не миграция. ML-слой накладывается поверх эвристик, не заменяет их.

Решение 4. Стабильность ранжирования для партнёрской поверхности

Цель: обеспечить контракт с партнёрами, в котором повторяющийся запрос даёт повторяющийся результат.

Тезисы:

  1. Партнёрская поверхность — машинная интеграция. Случайные сдвиги в выдаче ломают пагинацию, кэширование и коммерческую фиксацию.
  2. Партнёр должен иметь возможность повторить запрос для отладки и получить тот же результат.
  3. Окно стабильности — компромисс между стабильностью и свежестью данных (5 минут — типичная индустриальная норма).

Альтернатива: случайное ранжирование с ML на каждый запрос. Отклонено для партнёрской поверхности (можно для потребительской).

Решение 5. Сложность запроса как метрика тарификации с самого начала

Цель: связать стоимость запроса с реальной нагрузкой на платформу.

Тезисы:

  1. Без сложностного балла платформа не отличает «дешёвый» партнёрский запрос от «дорогого».
  2. Партнёр, нагружающий платформу широкими запросами, должен платить больше — иначе его трафик субсидируется другими партнёрами.
  3. Сложностный балл — конфигурируемая политика, легко настраиваемая под текущие операционные расходы.

Открытые развилки

Развилка 1. Конкретный движок поиска — OpenSearch vs Elasticsearch open-source vs управляемый Elastic Cloud

Архитектурное требование — open-source основа. Конкретный выбор между OpenSearch (управляемый OVHcloud) и Elasticsearch (либо self-hosted, либо Elastic Cloud) — открытая развилка.

Эскалируется: при выходе с фазы Bootstrap в фазу 2 (когда нужен управляемый кластер).

Развилка 2. Стратегия ML-ранжирования — собственная модель vs стороннее решение

На фазе 3 ML-слой может быть собственной моделью (с командой ML и собственной инфраструктурой) или сторонним решением (например, Algolia AI для ранжирования поверх собственного индекса).

Эскалируется: при создании Платформа машинного обучения (новый документ, фаза 3).

Развилка 3. Глубина персонализации на потребительской витрине

Сколько персонализации применять для конечных клиентов на vitrip.store? Возможный диапазон от «нулевой персонализации» (чтобы сохранить консистентность с партнёрской поверхностью) до «полной персонализации с ML-моделями». Имеет коммерческие следствия (выручка) и регуляторные следствия (Закон о цифровых услугах требует прозрачности рекомендательных систем).

Эскалируется: при детальной проработке потребительской витрины (после стабилизации партнёрской поверхности).

Развилка 4. Поиск по новым типам инвентаря — туры, экскурсии, трансферы

Текущая модель поиска ориентирована на проживание (accommodation). Поддержка остальных типов инвентаря (tour_package, transfer, excursion) на фазах 1–2 — открытая развилка по приоритету.

Эскалируется: при создании отдельных доменных документов под каждый тип инвентаря.

Развилка 5. Голосовой и conversational-поиск

В фазе 4 рассматривается возможность conversational-интерфейса поиска (через ML/LLM). Это требует отдельной инфраструктуры и имеет существенные коммерческие следствия.

Эскалируется: при появлении партнёрских запросов на conversational-поиск.

Связанная документация

Корневые архитектурные документы

Связанные доменные документы

Документы развития

Операционная сторона

Архитектурные правила