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

Внешний поисковый движок - индекс, запрос, синхронизация

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

Механика

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

Отсюда главное следствие: выдача движка - это список номеров и порядок. Сами данные для показа берут из каталога обычной выборкой, потому что цена, наличие и права меняются чаще, чем успевает обновляться индекс.

Синхронизация - основная работа в этой задаче. Товар изменился, подешевел, кончился на складе - документ в индексе обязан догнать это изменение. Полная переиндексация годится на старте, а дальше живут точечные обновления по событиям и ночная сверка.

Отдельно решается вопрос отказа. Внешний движок - это ещё один сервис, который однажды не ответит, и поиск на витрине не должен превращаться в белый экран. Правильное поведение - откат на штатный поиск или на простую выборку по названию.

Стоит трезво решить, нужен ли он вообще. Опечатки, синонимы, подсказки при вводе и ранжирование по продажам - вот его обычные причины. Если ничего из этого не нужно, штатный поиск с морфологией дешевле в поддержке на порядок.

Наконец, у движка есть цена владения. Это ещё один сервис на сервере, ещё один источник сбоев и ещё одно место, куда смотреть при разборе. Магазины, которые завели его ради «поиска получше» и не заложили сопровождение, через год живут с индексом полугодовой давности и выдачей хуже штатной.

Шаги

  1. Сформулировать, чего не хватает: опечатки, синонимы, подсказки, своё ранжирование.
  2. Описать состав документа: по чему ищем, по чему фильтруем, что показываем.
  3. Собрать первичный индекс порциями и замерить время полного прохода.
  4. Обновлять документы на событиях изменения товара, цены и остатка.
  5. Собирать выдачу как номера и доставать данные из каталога.
  6. Заложить откат на штатный поиск и ночную сверку индекса с каталогом.

Код

Описываем документ индекса:

$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]);
// подсказки берут из самого движка, а не из отдельной таблицы запросов
// пустой ответ на частый префикс - повод посмотреть, что вообще в индексе

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

Ограничения

Внешний движок - это отдельный сервис со своей установкой, обновлениями и резервными копиями. На простом хостинге его может быть просто негде запустить, и это первое, что выясняют до обещаний магазину.

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

Ранжирование придётся настраивать руками и не один раз. Движок из коробки не знает, что дорогой товар в наличии интереснее дешёвого под заказ, и вес полей подбирается замерами на настоящих запросах, а не один раз при установке.

Индекс всегда немного отстаёт от каталога. Товар, кончившийся минуту назад, ещё несколько минут находится поиском, и это нормальное поведение: наличие проверяют при показе, а не в индексе.

Замер выдачи стоит наладить до запуска движка на витрине. Доля запросов с пустым результатом и доля переходов из поиска в карточку - два числа, по которым видно, стало ли лучше; без них спор «раньше искало точнее» решается голосованием.

Типичные проблемы

Поиск находит товары, которых нет в наличии.

Наличие берётся из индекса, а не из каталога в момент показа. Индекс всегда немного отстаёт от реальных остатков.

Обмен с учётной системой стал многочасовым.

Каждое изменение товара идёт в движок прямо из обработчика события. Обновления складывают в очередь и отправляют пачками.

Выдача приходит в неправильном порядке.

Порядок из движка потерялся при выборке товаров из каталога. Результат раскладывают по позициям исходного списка номеров.

При недоступности движка падает вся страница.

Обращение к нему идёт без обработки ошибки и без отката. Отказ движка закрывают запасным поиском.

В индексе меньше товаров, чем в каталоге.

Часть обновлений потерялась из-за сбоя в очереди или в обработчике. Расхождение находит ежедневная сверка.

Частые вопросы

Когда внешний движок действительно нужен?

Когда важны опечатки, синонимы и подсказки на большом каталоге. Ради скорости одной его брать не стоит.

Что класть в документ индекса?

Поля для поиска, фильтрации и сортировки. Всё остальное берут из каталога при показе.

Как часто обновлять индекс?

Точечно на изменениях и полностью раз в сутки. Полная переиндексация в рабочее время мешает сайту.

Что показывать при отказе движка?

Результаты штатного поиска или простой выборки по названию. Пустая страница с ошибкой хуже неточной выдачи.

Кто следит за самим движком?

Тот же, кто следит за сервером: у него свои обновления и резервные копии. Это отдельная работа, а не разовая установка.

Смежное

Первоисточник