архитектурное решениеобновлено 2026-08-20

Поиск по событиям и рынкам: отдельный индекс чтения

Основной API события и поиск решают разные задачи. Разбираем отдельную поисковую проекцию для команд, турниров и рынков, её обновление дельтами и контроль свежести результатов.

Иллюстрация к материалу: Поиск по событиям и рынкам: зачем спортивной платформе отдельный read-index
Stephen Dawson / UnsplashUnsplash License

Поиск — другая форма чтения

API страницы события оптимизирован под известный event_id. Поиск начинается с текста и должен быстро сузить множество. Пытаться каждый раз обходить основной catalog API с фильтрами обычно означает смешать два разных read-pattern.

Проекция из нормализованного каталога

Indexer получает изменения события/рынка и собирает поисковый документ: нормализованное имя, aliases, tournament, sport, start_at, статус и routing id. UI получает компактный результат и затем открывает обычную страницу по internal id.

Свежесть против стоимости переиндексации

Не каждое изменение коэффициента должно менять search document. В индекс идут поля, влияющие на discoverability и availability. Live price остаётся в market API. Это отделяет высокочастотный поток от относительно спокойного каталога.

Нормализация текста отдельно от идентификаторов

Транслитерация, aliases и синонимы помогают поиску, но не являются системой сопоставления сущностей. Search может показать два похожих результата; identity resolver не имеет права объединять их только из-за совпавшего текста.

Нулевой результат из-за отставшего индекса

Если новое событие ещё не попало в индекс, навигация по турниру должна оставаться доступной. Поиск не должен быть единственной дверью в контент. Для критических страниц полезен direct route по internal id.

Что включать в поисковый документ

Поисковая проекция должна отвечать на пользовательский запрос, а не повторять всю доменную модель. Обычно достаточно стабильного identity, названия, алиасов, турнира, спорта, статуса доступности и нескольких полей для ранжирования. Тяжёлые подробности события можно дочитать по ID после выбора результата.

Так индекс остаётся компактным и быстрее обновляется. Отдельно стоит хранить версию каталога или timestamp проекции: если поиск сильно отстал, лучше показать ограниченный fallback или предупредить о неполных результатах, чем уверенно возвращать устаревшую выдачу.

  • Разделять нормализованный текст для поиска и исходное отображаемое название.
  • Измерять zero-result rate по языкам, видам спорта и типам сущностей.
  • Удаление и закрытие события должны попадать в индекс так же надёжно, как создание.

Поиск обязан уметь честно устаревать

Read-index почти неизбежно отстаёт от основного каталога хотя бы на короткое время. Поэтому интерфейс не должен считать поисковый документ источником окончательной доступности. Поиск находит candidate event или market, а переход на сущность сверяет актуальное состояние уже через продуктовый read path.

Хороший тест: удалить рынок или завершить событие, пока старый документ ещё существует в индексе. Пользователь может увидеть результат поиска, но после открытия должен получить корректный terminal/suspended state, а не 404 или возможность выполнить устаревшее действие. Такой разрыв особенно важен для live-каталога.

Внутренний CQRS read model полезен именно как оптимизированная проекция, а не копия источника истины. Метрики zero-result и stale-hit стоит смотреть отдельно по языкам, спорту и времени до старта — среднее значение часто скрывает локальную проблему.

Доля пустой выдачи в разрезе языка и спорта

Общий процент нулевых запросов скрывает проблему токенизации конкретного языка. Нужны breakdown по locale/sport и отдельный index freshness. Это быстрее приводит к причине, чем ручной просмотр популярных запросов.

Схема решения: Поиск по событиям и рынкам: зачем спортивной платформе отдельный read-index
Диаграмма к разбору: Поиск по событиям и рынкам: зачем спортивной платформе отдельный поисковый индекс.
Следующий слой

Связанные разборы по той же архитектурной цепочке

Интеграции данных

Нормализация спортивных данных: единая модель событий

Как переводить данные разных поставщиков во внутреннюю модель: исходный пакет, версии, неизвестные значения, сопоставление и диагностические сигналы.

Перейти к разбору →
Надёжность

Сессия пользователя и публичный кэш: где проходит граница

Как отделить публичную страницу события от данных аккаунта, сохранить эффективность кэша и не допустить персональные данные в общий ответ.

Перейти к разбору →
Надёжность

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

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

Перейти к разбору →
Букмекерская логика

Кнопка коэффициента: доступность, изменение цены и приостановка

Как проектировать кнопку коэффициента: изменение цены, приостановка, фокус, клавиатурное управление и понятные состояния после выбора пользователя.

Перейти к разбору →