- PVSM.RU - https://www.pvsm.ru -
…или правильная работа с коллекциями.
Хочу рассказать вам об ошибках, которые видел практически на каждом проекте на Magento у которого были проблемы с производительностью. Работая с Magento, мне иногда приходится проводить аудит чужого кода. Поэтому я бы хотел поделиться с вами опытом, который поможет улучшить производительность ваших сайтов и избежать ошибок в дальнейшем.
В этой статье рассказано о Magento 1.*, но описанное так же подходит и для Magento 2.*.
Практически на каждом проекте, где есть проблемы с производительностью, можно встретить что-то вроде такого:
$temp = array();
$collection = Mage::getModel('catalog/product')->getCollection()->addAttributeToSelect('*');
foreach ($collection as $product) {
$product = $product->load($product->getId());
$temp[] = $product->getSku();
}
Неправильно
вместо
$temp = array();
$collection = Mage::getModel('catalog/product')->getCollection()->addAttributeToSelect('sku');
foreach ($collection as $product) {
$temp[] = $product->getSku();
}
Правильно
Причины такого очень просты:
Для понимания, что же тут не так и что мы можем сделать с производительностью, я предлагаю сконцентрироваться на работе с коллекциями:
EAV – это такой подход хранения данных, когда сущность к которой относится атрибут, сам атрибут и его значение разнесены в разные таблицы.
В Magento к EAV сущностям относятся: продукты, категории, кастомеры и кастомер адреса. Сами же атрибуты хранятся в eav_attribute таблице.
Всего типов значений атрибутов в Magento 5: text, varchar, int, decimal и datetime. Есть еще 1 тип – static, он отличается от остальных 5ти тем, что находится в таблице с сущностью.
В таблице атрибутов указано в какой таблице или какого типа является тот или иной атрибут и Magento уже знает куда его писать и откуда читать.
Такое хранение значений позволяет иметь достаточно просто реализуемые атрибут сеты ( когда каждая сущность может иметь свой атрибут или не иметь его вовсе), добавление нового атрибута это всего лишь еще 1 строчка в БД. Добавил новое значение для 1 атрибута для другого стора – новая строчка в таблице значений этого атрибута.
Attribute:
eav_attribute
catalog_eav_attribute
customer_eav_attribute
Value:
*_text
*_varchar
*_int
*_decimal
*_datetime
Flat — это привычный нам всем подход, где все лежит в 1 месте и никакие дополнительные таблицы нам не нужны, чтобы получить товар и все его атрибуты без лишней работы – SELECT * FROM табличка WHERE id = какой то ид и все.
Из EAV сущностей, Flat представление можно использовать только для категорий и для товаров.
Magento добавит атрибут во Flat таблицу если у атрибута выставлено 1 из ниже указанных значений.
В админке System > Configuration > Catalog
Magento будет использовать Flat таблицы для сущностей указанных ниже.
Обратите внимание на следующие факты:
Flat:
+ Производительность
+ В выборку/фильтрацию могут быть применены только существующие атрибуты, которые добавлены во Flat таблицу
— Ограничение на размер строки (до 65,535 байт, т.е. 85 varchar 255) и количеству столбцов (InnoDB до 1000, некоторые до 4096)
— Используется только при работе с коллекциям (при загрузке всегда используется EAV)
— Результат отличается от выдачи запроса при EAV (отсутствуют статик атрибуты)
— После включения требуется реиндексация, в противном случае будут использованы EAV таблицы
— При добавлении нового атрибута необходимо реиндексировать Flat таблицы
Конечно каждый из вас может мне сказать, что зачем нам разбираться как ускорить запросы в БД и вообще как работают коллекции если кэш нас спасет и все будет закэшировано. Отвечу коротко – кэш вас не спасет. Ни 1 из кэшей представленных в Magento либо не кэширует коллекции автоматически либо не работает в ваших кастомных контроллерах и моделях, которые вы используете, скажем, при импорте данных или подсчете чего-то. Да и к тому же до того, как оно попадет в кэш, ведь надо это как-то туда положить и быстренько показать пользователю.
Типы кэшей в Magento 1.*:
…и ни 1 не кэширует коллекции автоматически.
Для того, чтобы показать более наглядно почему что-то надо делать иначе чем многие привыкли, я решил привести некоторые тесты производительности разных подходов. Начнем пожалуй с тестового стенда. Для тестирования я использовал:
Тестовый стенд:
OS X 10.10
3.1 GHz Intel Core i5 (4 cores)
8GB
Magento конфигурация:
Magento EE 1.14.0
MySQL 5.5.38
PHP 5.6.2
Контент:
3 Categories
2000 Products
2000 CMS pages
Процесс:
Для тестов был создан экстеншен с 1 контроллером и 1 экшеном, каждый тест проводился 5 раз, потом считалось среднее время. Все результаты указаны в секундах.
class Test_Test_IndexController extends Mage_Core_Controller_Front_Action
{
public function indexAction()
{
$temp = array();
$start = microtime(true);
Init values
Loop start
$temp[] = $product->getSku();
Loop end
Or
Some code snippet
$stop = microtime(true);
echo $stop - $start;
}
}
Псевдо код
Цикл по коллекции. С load (перезагрузка) моделей внутри цикла:
$temp = array();
$collection = Mage::getModel('catalog/product')->getCollection()->addAttributeToSelect(...);
foreach ($collection as $product) {
$product = $product->load($product->getId());
$temp[] = $product->getSku();
}
Цикл по коллекции. Без load моделей внутри:
$temp = array();
$collection = Mage::getModel('catalog/product')->getCollection()->addAttributeToSelect(...);
foreach ($collection as $product) {
$temp[] = $product->getSku();
}
3 вида выборки данных:
Как вы видимо заметили, время без перезагрузки моделей В РАЗЫ меньше, чем когда вы перезагружаете модельки. Так же время еще меньше, когда Flat таблицы включены (т.е. нет лишних джойнов и юнионов) и мы выбираем только необходимые атрибуты.
В 1ом случае мы выполняем загрузку с кучей джойнов… а потом делаем это снова, но для модельки и так 2000 раз.
2ой раз мы делаем это для статик атрибута (он находится в той же табличке, что и сам продукт) и Magento не надо делать джойны. Поэтому время меньше.
3ий раз Magento нужно приджойнить еще табличку где хранится этот атрибут.
С Flat таблицами все аналогично, а в 2ух случаях вcе идентично – это потому что оба атрибута находятся в 1 таблице, отсюда и время идентичное.
Думаю цифры говорят сами за себя.
Без кэша:
$collection = Mage::getModel('catalog/product')->getCollection()
->addAttributeToSelect('*');
Используя метод initCache:
$collection = Mage::getModel('catalog/product')->getCollection()
->addAttributeToSelect('*')
->initCache(Mage::app()->getCache(),'our_data',array('SOME_TAGS'));
Кастомная реализация кэширования:
$cache = Mage::app()->getCache();
$collection = $cache->load('our_data');
if(!collection) {
$collection = Mage::getModel('collection/product')->getCollection()->addAttributeToSelect('*')->getItems();
$cache->save(serialize($collection),'our_data',array(Mage_Core_Model_Resource_Db_Collection_Abstract::CACHE_TAG));
} else {
$collection = unserialize($collection);
}
Рассмотрим выборку без использования кэша, с использованием метода, который нам предлагает Magento и с костылем, который я нигде не видел… сам сваял, основанный на методах модельки кэша. Обратите внимание, что для всех тестов после составления запроса я производил загрузку данных и преобразование коллекции к массиву объектов.
Без кэша собственно ничего удивительного…все как обычно.
А вот используя маджентовский кэш я лично удивился, когда увидел, что время стало больше. А про EAV кэширование вообще глупой затеей, потому что EAV коллекция грузит сначала сущности из таблицы продуктов (именно вот это и кэшируется), а потом отдельным запросом выбирает значения атрибутов и заполняет объекты. Во Flat там все из 1 таблицы гонится. Но тем не менее время больше уходится на работу с кэшем чем с БД (тестировал я причем как с файловой системой, так и с redis – отличия 4ая цифра после запятой…т.е. на 2к сущностях ее нет). Суть InitCache метода заключается в том, что он сначала соберет все данные в коллекцию сам (пагинация, фильтры, events и так далее), создаст хеш из sql запроса и его будет искать в кэше, а если там что-то есть, то он это ансерелизует, а потом происходит запуск всех events и последующих методов. Это самая медленная процедура во всем процессе, именно вот тут выходит что кэш медленнее чем простой запрос в БД. Но зато не шлет запрос в БД… что не так и страшно уже.
Отдельно стоит пример с кэшем, написанным мной на коленке, там мы кэшируем конечный результат коллекции, причем минуя все events и дозагрузку атрибутов. Это работает для EAV и для Flat коллекций.
getSize()
$size = Mage::getModel('catalog/product')->getCollection()
->addAttributeToSelect('*')
->getSize();
count()
$size = Mage::getModel('catalog/product')->getCollection()
->addAttributeToSelect('*')
->count();
Разница методов заключается в том, что count() производит загрузку всех объектов коллекции, а потом обычным пхпшным count’ом подсчитывает количество объектов и возвращает нам число. getSize же не производит загрузку коллекции, а генерирует еще 1 запрос в БД, где нет лимитов, ордеров и списка выбираемых атрибутов, есть только COUNT(*).
Пример использования обоих методов такой:
Если вам надо знать, есть ли вообще значения в БД или сколько их – используйте getSize, если же вам в любом случае коллекция нужна загруженная, или уже загрузилась то используйте count() – он вернет вам число элементов, загруженных в коллекцию.
getFirstItem()
$product = Mage::getModel('catalog/product')->getCollection()
->getFirstItem();
setPage(1,1)
$product = Mage::getModel('catalog/product')->getCollection()
->setPage(1,1)
->getFirstItem();
load()
$product = Mage::getModel('catalog/product')->load(22);
Проблема getFirstItem в том, что он загружает всю коллекцию целиком, а потом просто в foreach возвращает первый элемент, а если его нет то возвращает пустой объект.
setPage (он же $this->setCurPage($pageNum)->setPageSize($pageSize)) же ограничивает выборку ровно 1 записью, что как вы видите значительно ускоряет загрузку результата.
Даже load быстрее getFirstItem, но обратите внимание, что load медленнее оказался чем выборка из коллекции 1 элемента. Это связано с тем, что load всегда работает с EAV таблицами.
Подводя итог всему выше написанному, хочу посоветовать всем людям, работающим с Magento:
Автор: Oxidant
Источник [9]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/php-2/118598
Ссылки в тексте:
[1] Eav/Flat таблицы: #eav_flat
[2] Cache: #cache
[3] Правильная работа с коллекциями: #performance
[4] И конечно же выводы.: #results
[5] EAV/Flat с перезагрузкой моделей и без: #test_eav_flat
[6] Кэширование коллекций: #test_cache
[7] Правильное использование count() и getSize(): #test_count_getsize
[8] Правильное использование getFirstItem и setPage(1,1): #test_getfirstitem
[9] Источник: https://habrahabr.ru/post/282025/
Нажмите здесь для печати.