Внешний поисковый движок - индекс, запрос, синхронизация
Переносим поиск по каталогу во внешний движок: разбираемся, что класть в индекс, как держать его в согласии с каталогом и что показывать, когда движок молчит.
Механика
Внешний движок - это отдельное хранилище, живущее рядом с сайтом. В нём лежат не товары, а документы: набор полей, по которым ищут и фильтруют. Каталог остаётся хозяином данных, а движок - только быстрым способом найти в нём нужные номера.
Отсюда главное следствие: выдача движка - это список номеров и порядок. Сами данные для показа берут из каталога обычной выборкой, потому что цена, наличие и права меняются чаще, чем успевает обновляться индекс.
Синхронизация - основная работа в этой задаче. Товар изменился, подешевел, кончился на складе - документ в индексе обязан догнать это изменение. Полная переиндексация годится на старте, а дальше живут точечные обновления по событиям и ночная сверка.
Отдельно решается вопрос отказа. Внешний движок - это ещё один сервис, который однажды не ответит, и поиск на витрине не должен превращаться в белый экран. Правильное поведение - откат на штатный поиск или на простую выборку по названию.
Стоит трезво решить, нужен ли он вообще. Опечатки, синонимы, подсказки при вводе и ранжирование по продажам - вот его обычные причины. Если ничего из этого не нужно, штатный поиск с морфологией дешевле в поддержке на порядок.
Наконец, у движка есть цена владения. Это ещё один сервис на сервере, ещё один источник сбоев и ещё одно место, куда смотреть при разборе. Магазины, которые завели его ради «поиска получше» и не заложили сопровождение, через год живут с индексом полугодовой давности и выдачей хуже штатной.
Шаги
- Сформулировать, чего не хватает: опечатки, синонимы, подсказки, своё ранжирование.
- Описать состав документа: по чему ищем, по чему фильтруем, что показываем.
- Собрать первичный индекс порциями и замерить время полного прохода.
- Обновлять документы на событиях изменения товара, цены и остатка.
- Собирать выдачу как номера и доставать данные из каталога.
- Заложить откат на штатный поиск и ночную сверку индекса с каталогом.
Код
Описываем документ индекса:
$doc = [ 'id' => $item['ID'], 'name' => $item['NAME'], 'article' => $item['PROPERTY_ARTICLE_VALUE'], 'sections' => $sectionIds, // по чему фильтруем 'price' => (float)$price, // по чему сортируем 'available' => $available > 0 ? 1 : 0,];// в документ кладут только то, по чему ищут, фильтруют или сортируютВ индекс кладут то, по чему ищут и фильтруют, а не карточку целиком. Описание на две тысячи знаков в документе раздувает индекс и почти никогда не улучшает выдачу.
Собираем первичный индекс порциями:
$lastId = 0;while (true) { $rows = CIBlockElement::GetList(['ID' => 'ASC'], ['IBLOCK_ID' => 12, '>ID' => $lastId], false, ['nTopCount' => 500], ['ID', 'NAME', 'PROPERTY_ARTICLE']); $batch = []; while ($row = $rows->Fetch()) { $batch[] = self::toDoc($row); $lastId = $row['ID']; } if (!$batch) { break; } Engine::bulkIndex($batch); // отправляем пачкой, а не по одному документу}Обход идёт по номеру, а не по смещению. На каталоге в сотни тысяч позиций смещение замедляется к концу прохода, а отбор «всё, что больше последнего номера» работает одинаково быстро на любой странице.
Обновляем документ при изменении товара:
EventManager::getInstance()->addEventHandler('iblock', 'OnAfterIBlockElementUpdate', function (&$fields) { if ((int)$fields['IBLOCK_ID'] === 12 && $fields['RESULT']) { SearchQueueTable::add(['ELEMENT_ID' => $fields['ID'], 'STATUS' => 'new']); } });// обработчик кладёт задание в очередь, а не ходит в движок прямо из событияИндекс обновляют через очередь, а не напрямую из обработчика. Обмен с учётной системой меняет тысячи товаров подряд, и обращение к движку на каждом из них превращает выгрузку в многочасовую.
Собираем выдачу из номеров:
$ids = Engine::search($query, ['section' => $sectionId, 'limit' => 40]);if (!$ids) { return []; }$res = CIBlockElement::GetList([], ['IBLOCK_ID' => 12, 'ID' => $ids, 'ACTIVE' => 'Y'], false, false, ['ID', 'NAME', 'DETAIL_PAGE_URL', 'PREVIEW_PICTURE']);// порядок из движка теряется при выборке: его восстанавливают по списку номеровПорядок задаёт движок, а данные - каталог. Выборка по списку номеров возвращает товары в своём порядке, поэтому результат раскладывают обратно по позициям исходного списка.
Переживаем отказ движка:
try { $ids = Engine::search($query, $params); // таймаут внутри - секунда, не больше} catch (\Throwable $e) { AddMessage2Log('поиск: ' . $e->getMessage(), 'vendor.shop'); return $this->fallbackSearch($query); // штатный поиск или выборка по названию}Недоступность движка не должна ронять каталог. Откат на более простой поиск покупатель почти не заметит, а пустая страница с ошибкой стоит магазину заказов в тот же час.
Сверяем индекс с каталогом ночью:
$inCatalog = CIBlockElement::GetList([], ['IBLOCK_ID' => 12, 'ACTIVE' => 'Y'], []);$inIndex = Engine::count(['iblock' => 12]);printf("в каталоге %s, в индексе %s\n", $inCatalog, $inIndex);// расхождение больше процента - повод посмотреть очередь обновленийСверка ловит то, что теряет очередь. Ошибка в обработчике, сбой сети или пропущенное событие не видны нигде, кроме разницы в числах, и смотреть на неё стоит ежедневно. Расхождение в один товар из ста тысяч терпимо, а в тысячу - уже признак того, что очередь не разбирается вовсе.
Подсказки при вводе собираем отдельно:
$hints = Engine::suggest($prefix, ['limit' => 8]);// подсказки берут из самого движка, а не из отдельной таблицы запросов// пустой ответ на частый префикс - повод посмотреть, что вообще в индексеПодсказки живут в том же движке, что и поиск. Отдельный список популярных запросов рядом с ним быстро расходится с каталогом: в подсказках остаются товары, которых уже нет в продаже.
Ограничения
Внешний движок - это отдельный сервис со своей установкой, обновлениями и резервными копиями. На простом хостинге его может быть просто негде запустить, и это первое, что выясняют до обещаний магазину.
Права на товары движок не знает. Если часть каталога скрыта от групп пользователей, фильтровать по правам приходится уже после получения номеров, и на большой выдаче это заметно.
Ранжирование придётся настраивать руками и не один раз. Движок из коробки не знает, что дорогой товар в наличии интереснее дешёвого под заказ, и вес полей подбирается замерами на настоящих запросах, а не один раз при установке.
Индекс всегда немного отстаёт от каталога. Товар, кончившийся минуту назад, ещё несколько минут находится поиском, и это нормальное поведение: наличие проверяют при показе, а не в индексе.
Замер выдачи стоит наладить до запуска движка на витрине. Доля запросов с пустым результатом и доля переходов из поиска в карточку - два числа, по которым видно, стало ли лучше; без них спор «раньше искало точнее» решается голосованием.
Типичные проблемы
Поиск находит товары, которых нет в наличии.
Наличие берётся из индекса, а не из каталога в момент показа. Индекс всегда немного отстаёт от реальных остатков.
Обмен с учётной системой стал многочасовым.
Каждое изменение товара идёт в движок прямо из обработчика события. Обновления складывают в очередь и отправляют пачками.
Выдача приходит в неправильном порядке.
Порядок из движка потерялся при выборке товаров из каталога. Результат раскладывают по позициям исходного списка номеров.
При недоступности движка падает вся страница.
Обращение к нему идёт без обработки ошибки и без отката. Отказ движка закрывают запасным поиском.
В индексе меньше товаров, чем в каталоге.
Часть обновлений потерялась из-за сбоя в очереди или в обработчике. Расхождение находит ежедневная сверка.
Частые вопросы
Когда внешний движок действительно нужен?
Когда важны опечатки, синонимы и подсказки на большом каталоге. Ради скорости одной его брать не стоит.
Что класть в документ индекса?
Поля для поиска, фильтрации и сортировки. Всё остальное берут из каталога при показе.
Как часто обновлять индекс?
Точечно на изменениях и полностью раз в сутки. Полная переиндексация в рабочее время мешает сайту.
Что показывать при отказе движка?
Результаты штатного поиска или простой выборки по названию. Пустая страница с ошибкой хуже неточной выдачи.
Кто следит за самим движком?
Тот же, кто следит за сервером: у него свои обновления и резервные копии. Это отдельная работа, а не разовая установка.
Смежное
- Поиск по сайту на практике - оглавление подтемы
- Настройка поиска по сайту: состав индекса, морфология, подсказки - что умеет штатный поиск
- Поиск не находит товары: индекс, переиндексация, выдача - разбор при штатном поиске
- Очередь обмена со сторонней системой: задания, повторы, сверка - чем обновлять индекс
- Модули и решения - устройство модулей целиком
- Sphinx как движок поиска: подключение, индекс, переключение - штатный бэкенд вместо своего индекса