Ускоряем массовое обновление ресурсов в 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 минут обновлялись.