Skip to content
mFilter
mFilter
Фасетная фильтрация для MODX 3 с поддержкой SEO URL
  • MODX 3
  • PHP 8.1
  • Vue 3
  1. Компоненты
  2. mFilter
  3. Рецепты
  4. Синхронизация индекса фасетов

Синхронизация индекса фасетов ​

Индекс фасетов — копия значений фильтров из таблиц товаров, опций и 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-задачи.

Свой импортёр: обновить точечно ​

Если импортёр знает, какие товары он изменил, пересоберите индекс только для них — это гораздо быстрее полной пересборки:

php
/** @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() сам сбрасывает кэш значений и счётчиков у страниц каталога, в дерево которых попадают товары. Не трогает он:

  • список товаров страницы;
  • категорию, из которой товар перенесли, — её он не знает;
  • посадочные страницы и страницу поиска — их список товаров задаёт не своё дерево.

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

php
foreach ($categoryIds as $categoryId) {
    $mfilter->invalidatePageCache($categoryId);
}

Так же сбрасывайте посадочные страницы и страницу поиска.

Полная пересборка сбрасывает значения и числа у всех страниц, но не списки товаров — после переноса товаров invalidatePageCache() нужен и с ней. См. Кэширование.

Когда идентификаторы неизвестны ​

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

php
// В фоне, задачей Scheduler
$mfilter->scheduleTaskOnce('mfl_rebuild_facet_index');

// Сразу, в своём CLI-скрипте
$mfilter->getFacetIndexBuilder()->buildAll();

Без Scheduler scheduleTaskOnce() ничего не ставит и возвращает false — тогда вызывайте buildAll().

Из админки — кнопки «Пересобрать сейчас» и «Через Scheduler» на вкладке Обслуживание.

Две пересборки одновременно не выполняются. Если другая уже идёт, buildAll() возвращает ['locked' => true] и ничего не делает. Проверьте этот признак, иначе скрипт решит, что индекс обновлён:

php
$stats = $mfilter->getFacetIndexBuilder()->buildAll();
if (!empty($stats['locked'])) {
    // Индекс занят: повторить позже или поставить задачу Scheduler
}

Тот же признак возвращают syncByEditedon(), indexAll() у индексатора TV и generateForAllConfigs() у менеджера алиасов.

Прямые запросы к базе ​

Задача синхронизации видит только изменения с новым editedon. Если импортёр пишет в базу напрямую, есть три пути:

  1. Обновлять editedon в своих запросах — тогда задача подхватит изменения за 5 минут. Колонка хранит время UNIX: пишите editedon = UNIX_TIMESTAMP(), а не NOW().
  2. Вызывать buildForProducts() или buildAll() в конце своего скрипта.
  3. Сделать 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() или полная пересборка.