mFilter2 и оптимизация скорости на 7k товарах
Всем, кто хоть раз пытался завести mFilter2 на большом кол-ве товаров и хотя-бы 10 опциях в фильтре, известно, что тормозов не избежать. Вот и мне попался на днях сайт, в котором, казалось бы, всего-то 7000 товаров и 10 опций на странице каталога. Однако сниппет фильтра отрабатывал за 3-4 секунды. Переписал на Fenom — стал отрабатывать за 2-3 секунды.
Не годится! Тем более, что у заказчика было требование: «Чтобы сайтом было комфортно пользоваться». Что-ж, взял задачу — надо решать!
Дебаг показал, что проблема в методе mse2FiltersHandler::getMsOptionValues, а именно в запросе (7k товаров!), который выглядит как-то так:
SELECT `product_id`, `key`, `value` FROM `modx_ms2_product_options` AS `msProductOption`
WHERE (`msProductOption`.`product_id` IN (3394,3408,еще 7 тысяч ID)
AND `msProductOption`.`key` IN ('atr_n1','atr_n6','atr_n5','atr_n12','atr_n43','atr_n46','atr_n29','atr_n11','atr_n44','atr_n2'))
Оговорочка
- В данной статье мы улучшаем только метод для работы с опциями miniShop2, которые хранятся в таблице modx_ms2_product_options. Фильтрацию по ТВ и прочее мы здесь не затрагиваем.
- Пока не исследовано, как данные правки поведут себя на небольшом каталоге, поэтому тестируйте — отписывайтесь!
Поиск решения
Честно скажу, пытался я по-всякому, и разделял запрос на несколько, по количеству элементов в массиве $keys, и даже думал создавать дополнительную таблицу, которая бы обладала удобным форматом хранения значений, но так и не придумал, что это за формат такой. :) В итоге, несколько часов раздумий и тестов привели меня к сумасшедшей идее, которую я, собственно, и предлагаю рассмотреть в данной статье. Реализуется она в пару шагов:
Шаг 1
Системной настройке mse2_filters_handler_class присваиваем значение:customFiltersHandler
Шаг 2
В директории core/components/msearch2/custom/filters/ создаём файл custom.class.php следующего содержания:<?php
class customFiltersHandler extends mse2FiltersHandler
{
/**
* @param array $keys
* @param array $ids
*
* @return array
*/
public function getMsOptionValues(array $keys, array $ids)
{
$filters = array();
$fields = $this->modx->getFields('msProductData');
$q = $this->modx->newQuery('msProductOption');
if (array_key_exists('sku_id', $fields)) {
$q->innerJoin('msProductData', 'Data', 'Data.sku_id = msProductOption.product_id');
$q->where(array('Data.id:IN' => $ids, 'key:IN' => $keys));
$q->select('Data.id as product_id, key, value');
} else {
$q->where(array('key:IN' => $keys));
$q->select('product_id, key, value');
}
$tstart = microtime(true);
if ($q->prepare() && $q->stmt->execute()) {
$this->modx->queryTime += microtime(true) - $tstart;
$this->modx->executedQueries++;
if ($rows = $q->stmt->fetchAll(PDO::FETCH_ASSOC)) {
$ids_flip = array_flip($ids);
foreach ($rows as $row) {
if (!isset($ids_flip[$row['product_id']])) {
continue;
}
$value = str_replace('"', '"', trim($row['value']));
$key = $row['key'];
// Get ready for the special options in "key==value" format
if (strpos($value, '==')) {
list($key, $value) = explode('==', $value);
$key = preg_replace('/\s+/', '_', $key);
}
if (isset($filters[$key][$value])) {
$filters[$key][$value][$row['product_id']] = $row['product_id'];
} else {
$filters[$key][$value] = array($row['product_id'] => $row['product_id']);
}
}
}
} else {
$this->modx->log(modX::LOG_LEVEL_ERROR, "[mSearch2] Error on get filter params.\nQuery: " . $q->toSQL() . "\nResponse: " . print_r($q->stmt->errorInfo(), 1));
}
return $filters;
}
}
Если вкратце, то
Мы исключаем из выборки параметр со списком ID товаров (так как это делает запрос слишком большим) и производим сравнение непосредственно в переборке результатов (на стороне PHP).
В нашем случае, данное решение оказалось довольно действенным, о чем говорят результаты:
Сниппет mFilter2 со сброшенным кешем отрабатывал на старом методе за 2-3 секунды, а на новом — за ~0.3-0.5. На мой взгляд, это существенно сказалось на восприятии удобства пользования сайтом. Заказчик, к слову, остался доволен.
Оптимизирую
Предоставляю услуги по оптимизации скорости работы сайтов на MODX Revolution.
Телеграм: t.me/pavelgvozdb
Скайп: pavelgvozdb
Поблагодарить автора
Отправить деньги
Комментарии: 11
Простите, но мое воображение меня порой сводит с ума… Я представил, как цикл прогоняет 7000 элементов за 0.3 сек… «Тра-та-та-та-та-та-та-та-та-пиууу-та-та-та-та-та-та-пиу-та-та-та-та-та...» Скорость, ух…
А разе
А разе
$q->where(array('Data.id:IN' => $ids, 'key:IN' => $keys));
не перечисляет все эти 7000 id так же, как и `msProductOption`.`product_id` IN (3394,3408,еще 7 тысяч ID,9775,9776)
Или я чего-то недопонял?
Перечисляет, но оно срабатывает только тогда, когда в списке полей таблицы modx_ms2_product_options есть sku_id. Не стал ни тестировать, ни править это, т.к. у меня нет такого поля в этой таблице.
Уже разобрался, но непонятно зачем это поле… Задаток на работу с остатками?
К Василию, я не разработчик компонента.
Это от отменённой версии 2.3.
Ни в miniShop2.2, ни в 2.4 этого поля нет.
Ни в miniShop2.2, ни в 2.4 этого поля нет.
Простите, но мое воображение меня порой сводит с ума… Я представил, как цикл прогоняет 7000 элементов за 0.3 сек… «Тра-та-та-та-та-та-та-та-та-пиууу-та-та-та-та-та-та-пиу-та-та-та-та-та...» Скорость, ух…К слову сказать, цикл гораздо больше, ибо только в таблице modx_ms2_product_options 62k записей.
А если вместо
$ids_flip = array_flip($ids);и
if (!isset($ids_flip[$row['product_id']])) {заюзать бинарный поиск? реализация, например такая:
function binarySearch($arr, $num)
{
$first = 0;
$last = sizeof($arr) - 1;
while ($first <= $last) {
$i = floor(($first + $last) / 2);
if ($arr[$i] == $num) {
return true;
} elseif ($arr[$i] > $num) {
$last = $i - 1;
} else {
$first = $i + 1;
}
}
return false;
}
Не будет есть меньше памяти и быстрее отрабатывать?
Разрешаю проверить и отписаться. :)
Проверил бы, да не на чем :)
modhost.pro ;)
А как оптимизировать для tv параметров?
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.