Блог
Оптимизация выборки при недостатке памяти в HostCMS
Работая с большими выборками в HostCMS можно столкнуться с проблемой недостатка памяти, которая будет проявляться, например, вот такой ошибкой:
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 32 bytes) in /modules/core/orm.php on line 730
Техническая поддержка HostCMS помогла нам с оптимизацией кода и решением этой проблемы.
Так, в нашем модуле SEO-фильтр мы столкнулись с проблемой нехватки памяти при генерации sitemap. У модуля есть генератор URL-ов*, который создает URL-ы для разных возможных комбинаций параметров(дополнительных свойств) в фильтре в клиентской части. И таких комбинаций может получится огромное множество. В одном из проектов их более 100 000. Но в данном случае у нас было всего 20 000 URL-ов и этого уже хватало, чтобы вызвать ошибку нехватки памяти. Все эти 20 000 получались одним запросом:
$oKadseofilters = Core_Entity::factory('kadseofilter');
$oKadseofilters->queryBuilder()
->join('shops', 'kadseofilters.shop_id', '=', 'shops.id')
->where('shops.site_id', '=', CURRENT_SITE); $aoKadseofilters = $oKadseofilters->findAll(FALSE);
А далее срабатывал код, который помещал нужные данные в карту сайта:
foreach ($aoKadseofilters as $oKadseofilter) { $object->addNode($oKadseofilter->href(), 2, 0.5); }
В данном случае очень важно учесть, что из всего этого большого объема данных, нам нужна реально только ссылка на URL*. А мы получаем все данные. Оптимизировать можно, получая эти данные по частям. Получаем часть, добавляем узлы, извлекая полезную информацию - url, получаем следующую часть и так далее до последнего. Таким образом используем меньший объем оперативной памяти. В итоге получаем код:
// Get max ID $oCore_QueryBuilder_Select = Core_QueryBuilder::select(array('MAX(kadseofilters.id)', 'max_id')); $oCore_QueryBuilder_Select ->from('kadseofilters') ->join('shops', 'kadseofilters.shop_id', '=', 'shops.id') ->where('shops.site_id', '=', CURRENT_SITE) ->where('kadseofilters.deleted', '=', 0); $aRow = $oCore_QueryBuilder_Select->execute()->asAssoc()->current(); $maxId = $aRow['max_id']; $iFrom = 0; $iOnStep = 1000; do { $oKadseofilters = Core_Entity::factory('kadseofilter'); $oKadseofilters->queryBuilder() ->join('shops', 'kadseofilters.shop_id', '=', 'shops.id') ->where('kadseofilters.id', 'BETWEEN', array($iFrom + 1, $iFrom + $iOnStep)) ->where('shops.site_id', '=', CURRENT_SITE); $aoKadseofilters = $oKadseofilters->findAll(); foreach ($aoKadseofilters as $oKadseofilter) { $object->addNode($oKadseofilter->href(), 2, 0.5); } $iFrom += $iOnStep; } while ($iFrom < $maxId);
Далее можно избавиться от кеширования данных, в данном случае эти данные получаются разово и нам не нужно их кэшировать. Для этого в метод findAll передаем параметр FALSE
$aoKadseofilters = $oKadseofilters->findAll(FALSE);
Таким образом мы значительно уменьшили необходимый для работы скрипта объем ОЗУ и все заработало.
Выражаю благодарность за помощь в данном вопросе и решение проблемы технической поддержке HostCMS и лично Борису Теряеву.
* здесь и далее URL - сущность модуля SEO-фильтр, url - путь к выборке