Ускоряем массовое обновление ресурсов в 3 раза
Массовая выгрузка ресурсов, не такая уж простая задача, как может показаться на первый взгляд. Много подводных камней, касающихся улучшения производительности скрипта. Особенно неприятно, когда тормозит скорость работы из-за функционала ядра MODX.
Так, например, я столкнулся с куском кода в ядре, который увеличивает время выгрузки большого кол-ва товаров в ~3 раза, выполняя одно и то же ресурсоёмкое действие каждый раз, когда скрипт запускает обновление ресурса через процессор modResourceUpdateProcessor.
Сразу оговорю тот момент, что я не считаю этот кусок кода в ядре лишним или вредным, нет! Я полагаю, что этот код при обновлении большого кол-ва ресурсов можно выполнить самостоятельно по окончании работы скрипта выгрузки.
Я говорю вот об этой строчке кода. Давайте попробуем разобраться, что же с ней не так!
А всё дело в том, что запускается там метод modX::reloadContext(), который в свою очередь вызывает метод modContext::prepare(), а он уже запускает метод modCacheManager::generateContext(). Вот тут и начинается самое интересное!
Незамысловатый дебаг показал, что в методе modCacheManager::generateContext() вот здесь и здесь основной затык по времени. Там MODX получает и генерирует заново всю карту ресурсов. Представьте, если у нас 100k товаров на сайте, сколько времени потребуется, чтобы собрать всю эту карту из базы? На нормальном сервере карта из 100k ресурсов генерируется в течение 0,3-0,5 сек. А теперь подумайте, надо ли это делать при каждом обновлении ресурса во время массовой выгрузки?
У нас на выгрузке ежедневно 50k ресурсов, допустим, все их нужно обновить. Обновление 1 ресурса с запуском метода modX::reloadContext() в этом случае занимает ~0,6 сек. А 50k ресурсов будет обновляться ~8 часов.
Это если производить выгрузку, что называется, в вакууме, не учитывая скачивание изображений и загрузку их для каждого товара в miniShop2 галерею.
Теперь давайте закомментируем этот метод в ядре и снова замерим скорость работы. Обновление ресурса без метода modX::reloadContext() происходит за 0,2 сек. При 50k товарах выгрузка «в вакууме» займёт ~3 часа. После чего, мы спокойно можем выполнить один раз тот-же самый $modx->reloadContext() у себя в скрипте.
Для себя я обошёл это, вставив в процессор msProductUpdateProcessor компонента miniShop2, в самый конец класса такой код:
Главное, теперь после обновления miniShop2 нужно заново накатывать этот код!
Так, например, я столкнулся с куском кода в ядре, который увеличивает время выгрузки большого кол-ва товаров в ~3 раза, выполняя одно и то же ресурсоёмкое действие каждый раз, когда скрипт запускает обновление ресурса через процессор modResourceUpdateProcessor.
Сразу оговорю тот момент, что я не считаю этот кусок кода в ядре лишним или вредным, нет! Я полагаю, что этот код при обновлении большого кол-ва ресурсов можно выполнить самостоятельно по окончании работы скрипта выгрузки.
Я говорю вот об этой строчке кода. Давайте попробуем разобраться, что же с ней не так!
Детали проблемы
А всё дело в том, что запускается там метод modX::reloadContext(), который в свою очередь вызывает метод modContext::prepare(), а он уже запускает метод modCacheManager::generateContext(). Вот тут и начинается самое интересное!
Незамысловатый дебаг показал, что в методе modCacheManager::generateContext() вот здесь и здесь основной затык по времени. Там MODX получает и генерирует заново всю карту ресурсов. Представьте, если у нас 100k товаров на сайте, сколько времени потребуется, чтобы собрать всю эту карту из базы? На нормальном сервере карта из 100k ресурсов генерируется в течение 0,3-0,5 сек. А теперь подумайте, надо ли это делать при каждом обновлении ресурса во время массовой выгрузки?
Приведу пример
У нас на выгрузке ежедневно 50k ресурсов, допустим, все их нужно обновить. Обновление 1 ресурса с запуском метода modX::reloadContext() в этом случае занимает ~0,6 сек. А 50k ресурсов будет обновляться ~8 часов.
Это если производить выгрузку, что называется, в вакууме, не учитывая скачивание изображений и загрузку их для каждого товара в miniShop2 галерею.
Теперь давайте закомментируем этот метод в ядре и снова замерим скорость работы. Обновление ресурса без метода modX::reloadContext() происходит за 0,2 сек. При 50k товарах выгрузка «в вакууме» займёт ~3 часа. После чего, мы спокойно можем выполнить один раз тот-же самый $modx->reloadContext() у себя в скрипте.
Как решить?
Для себя я обошёл это, вставив в процессор msProductUpdateProcessor компонента miniShop2, в самый конец класса такой код:
public function cleanup()
{
$this->object->removeLock();
$this->clearCache();
$returnArray = $this->object->get(array_diff(array_keys($this->object->_fields), array('content','ta','introtext','description','link_attributes','pagetitle','longtitle','menutitle','properties')));
foreach ($returnArray as $k => $v) {
if (strpos($k,'tv') === 0) {
unset($returnArray[$k]);
}
}
$returnArray['class_key'] = $this->object->get('class_key');
$this->workingContext->prepare(false);
if ($this->getProperty('_reloadContext', true)) {
$this->modx->reloadContext($this->workingContext->key);
}
$returnArray['preview_url'] = '';
if (!$this->object->get('deleted')) {
$returnArray['preview_url'] = $this->modx->makeUrl($this->object->get('id'), $this->object->get('context_key'), '', 'full');
}
return $this->success('',$returnArray);
}
Т.к. он расширяет процессор modResourceUpdateProcessor, я переназначил метод cleanup(), в котором вызывается modX::reloadContext(). Теперь мы можем при обновлении через процессор передать параметр _reloadContext = false и метод, тормозящий массовую выгрузку в 3 раза, запускаться не будет.Главное, теперь после обновления miniShop2 нужно заново накатывать этот код!
Поблагодарить автора
Отправить деньги
Комментарии: 5
Вот почему оказывается такие тормоза при сохранении ресурса
Спасибо огромное за анализ проблемы. На одном проекте тоже возникала проблема с тормозами, все никак руки не доходили разобраться в причине, думал что какая то карта генерируется при каждом обновлении, поскольку тормозить начинало с увеличением количества товаров.
Мне при схожей задаче пришлось из-за этого уйти от обновления ресурсов через процессоры, т.к. очень долго.
Все через ручную работу с объектами делал и каждый раз вспоминал, не забыл ли чего…
Зато 15 тыс объектов за 5 минут обновлялись.
Все через ручную работу с объектами делал и каждый раз вспоминал, не забыл ли чего…
Зато 15 тыс объектов за 5 минут обновлялись.
К сожалению, не универсально… накатил какой-то пакет, который использует события обновления ресурса и всё по швам пошло.
Именно… приходится все помнить и держать в голове постоянно эту вероятность.
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.