У магазина на Odoo с каталогом больше ста тысяч позиций появляется проблема, которой никогда не видно на демо-базе: поиск перестаёт быть мгновенным. У нас он занимал восемь–десять секунд с включёнными фильтрами. Ждал склад, ждали продавцы, и все привыкли открывать вторую вкладку, пока думает первая.
Что именно было медленным
Первое желание - обвинить сервер, и оно почти всегда ошибочно. Памяти хватало, диски не были загружены. Время уходило в один запрос, и план запроса объяснял почему.
Поиск по каталогу - это не один вопрос, а несколько сразу: найти строки, в тексте которых есть фрагмент, сузить их по нескольким атрибутам и посчитать, сколько товаров осталось за каждым значением фильтра, чтобы числа рядом с галочками не врали. На нескольких тысячах товаров это бесплатно. На ста десяти тысячах строк с длинными описаниями и несколькими присоединёнными таблицами атрибутов база вынуждена перебирать почти всё - на каждое нажатие клавиши и на каждого посетителя.
Индексы здесь не спасают. Поиск по фрагменту в середине слова обычным индексом воспользоваться не может в принципе, а счётчики у фильтров пересчитываются заново на каждый запрос по определению: они зависят от того, что посетитель уже выбрал.
И каталог был на той машине не один. Та же Odoo обслуживала ещё тридцать девять витрин из общего пула воркеров, так что каждый медленный поиск забирал воркер сразу у всех.
Сначала измерить
Что делать дальше, решают два числа: сколько времени уходит у базы на запрос и сколько у браузера на всё остальное. Окажись ответ в браузере - мы бы неделю оптимизировали не ту половину системы.
Браузер был ни при чём. Лог медленных запросов и план запроса показывали на одно и то же место, и это сделало решение простым.
Что изменилось
Поиск переехал из базы в Elasticsearch, который держит собственный индекс и создан ровно под этот вопрос: текст, фильтры и счётчики одновременно. Поле поиска на сайте осталось там же, и человек, который в нём печатает, не видит ничего нового - кроме того, что результаты уже здесь.
Важна здесь сторона стрелки. Odoo остаётся источником истины: товары, цены и остатки живут и правятся там. Индекс собирается из базы и пересобирается при изменении записей - и никогда наоборот. Если индекс потеряется, его можно выбросить и построить заново: ничего ценного в нём не хранится.
Из чего это собрано
Ничего здесь не писалось с нуля ради самого процесса. Модуль для Elasticsearch куплен и переработан, а поверх него - мост к теме магазина, чтобы поле поиска, страница каталога и фильтры разговаривали с индексом, а не с базой.
Между Odoo и хранилищами стоит небольшой сервис на Go. Он собирает запрос с фильтрами, спрашивает Elasticsearch, считает счётчики фасетов и держит тяжёлые куски страницы товара в Redis с таймаутом в десятки миллисекунд - если не ответил вовремя, его никто не ждёт.
Зачем вообще отдельный сервис: на хостинге Odoo рядом с ней свой демон не поднять, туда нет доступа. Поэтому сервис живёт снаружи и отвечает по HTTP. На своём сервере он переезжает в ту же подсеть, и этот переход почти ничего не стоит.
И магазин продолжает работать, когда сервис не работает. Та же логика фильтрации есть в Odoo на чистом Python: медленнее, но верно. Этот путь не рудимент - именно из-за него выкатка сервиса перестала быть событием, которое надо планировать.
Во что это обходится
Честный ответ включает цену, и это не только работа по переносу поиска. Появился второй сервис, который надо запускать, мониторить и бэкапить. Его индекс нужно пересобирать после массовых импортов. Кто-то должен замечать, когда он отстал, и система должна вести себя разумно в ту минуту, когда он недоступен.
Это и есть настоящий обмен: постоянный кусок инфраструктуры взамен поиска, который больше не заставляет людей ждать. Для каталога такого размера обмен очевидно выгоден. Для каталога в пять тысяч позиций - обычно нет.
Когда всё это не нужно
Если поиск медленный на небольшом каталоге, причина почти никогда не в объёме данных. Чаще это запрос, который присоединяет лишнее, отсутствующий индекс на поле, по которому фильтруют на каждой странице, картинка, блокирующая отрисовку, или сниппет, перерисовывающий весь список на каждое нажатие клавиши. Это стоит дня работы, а не нового сервиса в продакшене.
Поэтому первый шаг всегда один и тот же: сначала измерить, потом менять. Измерение занимает час и экономит неделю, потраченную на оптимизацию не той половины.
Ещё выбираете саму систему? Тогда арифметика идёт раньше архитектуры: во что обходится команда из десяти человек в Odoo, Salesforce, HubSpot и SAP.
Интересно, как те же системы ведут себя изо дня в день? Odoo в эксплуатации: что держит, что ломается, чего нет в документации.