
Синхронизация индекса фасетов
Индекс фасетов — копия значений фильтров из таблиц товаров, опций и TV. Если данные изменились, а индекс нет, фильтры покажут устаревшие значения и неверные числа товаров.
Задача
Понять, когда индекс обновляется сам, и обновить его вручную там, где автоматики не хватает: свой импортёр, прямые запросы к базе.
Что обновляет индекс само
| Что произошло | Чем обновляется | Когда |
|---|---|---|
| Товар сохранён или удалён в админке MODX или MiniShop3 | Плагин mFilter | Сразу |
Процессор resource/create, resource/update или процессор товара MiniShop3 — в том числе через runProcessor() | Плагин mFilter | Сразу |
| Публикация и снятие с публикации из контекстного меню | Задача Scheduler mfl_sync_facet_index | В течение 5 минут |
Прямой запрос к базе с editedon | Та же задача | В течение 5 минут |
| Импорт MiniShop3 из CSV | Плагин mFilter по событию msOnAfterImport | Полной пересборкой: со Scheduler — при ближайшем запуске, без него — сразу, импорт ждёт её окончания |
$resource->save(), setTVValue(), прямой запрос без editedon | Ничем — вызовите buildForProducts() | — |
| Товар опубликован по дате | Ничем — сохраните товар заново или пересоберите индекс | — |
| Товар восстановлен из корзины | Ничем — сохраните товар заново или вызовите buildForProducts() | — |
Задача сравнивает editedon товаров с временем прошлого запуска, а удалённые товары находит по deletedon и убирает из индекса. Поэтому важно не само изменение, а отметка времени: правка, которая её не обновила, для задачи не существует.
Подробности задач — Scheduler-задачи.
Свой импортёр: обновить точечно
Если импортёр знает, какие товары он изменил, пересоберите индекс только для них — это гораздо быстрее полной пересборки:
/** @var \MFilter\MFilter $mfilter */
$mfilter = $modx->services->get('mfilter');
$builder = $mfilter->getFacetIndexBuilder();
$updatedIds = [];
foreach ($rows as $row) {
$resource = $modx->getObject(\MODX\Revolution\modResource::class, $row['id']);
if (!$resource) {
continue;
}
$resource->set('pagetitle', $row['name']);
$resource->save();
$updatedIds[] = $resource->get('id');
}
// Один вызов после всего импорта
if ($updatedIds) {
$stats = $builder->buildForProducts($updatedIds);
$modx->log(modX::LOG_LEVEL_ERROR, "[Импорт] Индекс фасетов: {$stats['products']} товаров за {$stats['duration_ms']} мс");
}buildForProducts() возвращает products, text_rows, num_rows и duration_ms:
Array
(
[products] => 2
[text_rows] => 2
[num_rows] => 2
[duration_ms] => 182
)Если какой-то фильтр записать не удалось, остальные всё равно индексируются, а его ключ попадает в failed_keys. Причина — в журнале ошибок.
Если у импортёра есть своё событие окончания, тот же вызов можно повесить плагином на него — идентификаторы товаров плагин возьмёт из параметров события.
TV лежат ещё и в отдельном индексе. Если импортёр меняет TV, обновите и его: $mfilter->getTvIndexer()->indexResource($id).
Кэш страниц после импорта
buildForProducts() сам сбрасывает кэш значений и счётчиков у страниц каталога, в дерево которых попадают товары. Не трогает он:
- список товаров страницы;
- категорию, из которой товар перенесли, — её он не знает;
- посадочные страницы и страницу поиска — их список товаров задаёт не своё дерево.
Если импорт добавляет товары или переносит их между категориями, сбросьте кэш затронутых категорий целиком, вместе со списком товаров, — иначе прогретые страницы покажут прежний список:
foreach ($categoryIds as $categoryId) {
$mfilter->invalidatePageCache($categoryId);
}Так же сбрасывайте посадочные страницы и страницу поиска.
Полная пересборка сбрасывает значения и числа у всех страниц, но не списки товаров — после переноса товаров invalidatePageCache() нужен и с ней. См. Кэширование.
Когда идентификаторы неизвестны
Тогда остаётся полная пересборка. Она тяжелее точечной, поэтому на больших каталогах запускайте её в фоне. Из кода:
// В фоне, задачей Scheduler
$mfilter->scheduleTaskOnce('mfl_rebuild_facet_index');
// Сразу, в своём CLI-скрипте
$mfilter->getFacetIndexBuilder()->buildAll();Без Scheduler scheduleTaskOnce() ничего не ставит и возвращает false — тогда вызывайте buildAll().
Из админки — кнопки «Пересобрать сейчас» и «Через Scheduler» на вкладке Обслуживание.
Две пересборки одновременно не выполняются. Если другая уже идёт, buildAll() возвращает ['locked' => true] и ничего не делает. Проверьте этот признак, иначе скрипт решит, что индекс обновлён:
$stats = $mfilter->getFacetIndexBuilder()->buildAll();
if (!empty($stats['locked'])) {
// Индекс занят: повторить позже или поставить задачу Scheduler
}Тот же признак возвращают syncByEditedon(), indexAll() у индексатора TV и generateForAllConfigs() у менеджера алиасов.
Прямые запросы к базе
Задача синхронизации видит только изменения с новым editedon. Если импортёр пишет в базу напрямую, есть три пути:
- Обновлять
editedonв своих запросах — тогда задача подхватит изменения за 5 минут. Колонка хранит время UNIX: пишитеeditedon = UNIX_TIMESTAMP(), а неNOW(). - Вызывать
buildForProducts()илиbuildAll()в конце своего скрипта. - Сделать
mfl_rebuild_facet_indexповторяющейся задачей — например, раз в неделю ночью, как страховку. Повторы начинаются только после первого запуска: запустите задачу кнопкой «Через Scheduler» на вкладке «Обслуживание». Обновление mFilter снимает повторение — после обновления включите его снова.
Если индекс отстаёт
| Что проверить | Где |
|---|---|
| Время последней пересборки и число строк | Вкладка «Обслуживание», блок «Индекс фасетов» |
| Работает ли задача синхронизации | mfl_cache, ключ facet_index.last_sync_at. Время стоит на месте — Scheduler не запускается или синхронизация упирается в фильтр, который не записывается: в журнале — Sync kept its last position |
| Ошибки пересборки | Журнал ошибок MODX, строки с [mFilter] Facet index и [FacetIndexBuilder] |
Товар, у которого при записи не обновился editedon, задача не увидит, сколько бы раз ни отработала. Для него — buildForProducts() или полная пересборка.
