Задача 7 из 10
Поиск по каталогу
Спроектируйте поиск по каталогу товаров: пользователь вводит запрос, подсказки появляются на лету, а по Enter открывается релевантная выдача с фильтрами.
Функциональные требования
- Поиск по названию и описанию, устойчивый к опечаткам
- Подсказки по мере ввода
- Фильтры (категория, цена, наличие) и сортировка
- Новый или изменённый товар быстро появляется в поиске
Нефункциональные требования
- 5 млн товаров, 3000 поисков/с в пик
- Подсказка отвечает за 50 мс, выдача — за 200 мс
- Изменение товара доезжает до поиска в течение минуты
- Индекс может отставать от БД, но не имеет права терять товары
Нарисуйте архитектуру
Не гонитесь за красотой: на собеседовании от схемы нужно, чтобы по ней было видно путь запроса и где лежат данные. Сначала нарисуйте сами — эталон и разбор ниже.
Блок из палитры — добавить. Тащите мышкой или пальцем. Двойной клик по названию — переименовать. «Связь» — щёлкнуть по одному блоку, потом по другому: получится стрелка.
Схема сохраняется в этом браузере — вкладку можно закрыть и вернуться позже.
1 С чего начать, если ступор
- Объясните, почему LIKE «%запрос%» по основной БД не работает на 5 млн товаров.
- Поисковый индекс — отдельное хранилище, значит появляется задача его наполнять.
- Синхронизация: писать в индекс из приложения или ловить изменения из БД (CDC / outbox)?
- Подсказки и полнотекстовый поиск — разные задачи с разными требованиями по латентности.
- Продумайте переиндексацию: что происходит, когда индекс нужно построить заново.
2 Эталонная схема
Поиск живёт в отдельном индексе, а не в основной БД. Изменения товаров доезжают до индекса через очередь, поэтому индекс всегда чуть отстаёт — по требованиям это допустимо. Карточку товара при этом отдаёт каталог из основной БД, где данные точные.
Это один из рабочих вариантов, а не единственно верный. Если у вас иначе, но вы можете объяснить почему — на собеседовании это ровно то, что нужно.
3 Разбор: что должно прозвучать
Почему не искать в основной БД
- LIKE «%...%» не использует обычный индекс — это полный скан по 5 млн строк
- Нет релевантности, морфологии и устойчивости к опечаткам
- Поисковая нагрузка конкурирует с транзакционной за одни и те же ресурсы
Наполнение индекса
- Двойная запись из приложения (в БД и в индекс) рассинхронизируется при любом сбое между ними
- Outbox: событие пишется в одной транзакции с товаром, отправляет его отдельный процесс
- CDC из журнала БД — вариант без изменения кода приложения
Отставание индекса
- Требование «в течение минуты» разрешает асинхронность — на это стоит опереться явно
- Сразу после сохранения товар может не находиться: продавцу это надо объяснить в интерфейсе
- Карточку товара читаем из БД, а не из индекса: там цена и наличие должны быть точными
Подсказки
- Отдельный лёгкий индекс под префиксы, а не полнотекстовый поиск
- Требование 50 мс жёстче, чем у выдачи, — здесь агрессивный кэш популярных префиксов
- Подсказки можно строить по логу запросов, а не только по названиям товаров
Фильтры и пагинация
- Фильтры и сортировка считаются в индексе — иначе придётся вытаскивать всё в приложение
- Глубокая пагинация в поисковых движках дорогая: обычно ограничивают глубину
- Наличие и цена меняются часто — иногда их держат отдельно и подмешивают при выдаче
Куда копать дальше, если спросят
- Переиндексация без даунтайма через алиасы: строим новый индекс, переключаем указатель
- Оценка релевантности и A/B-эксперименты над ранжированием
- Что делать, если индекс потерян целиком — сколько занимает полная переиндексация
- Персонализация и региональность выдачи
Следующая задача: Оплата заказа
Деньги нельзя ни потерять, ни списать дважды. Распределённой транзакции с банком не существует — отсюда идемпотентность, outbox и сага.