Magento 1: оптимизация производительности для удаления объектов

10

В настоящее время я пытаюсь улучшить пару модулей в отношении производительности.

Некоторые из вас могут знать, как использовать walk()метод сбора, который очень полезен, чтобы не зацикливаться на товарах напрямую.

Кроме того, благодаря @Vinai можно также использовать delete()метод сбора данных .

Но я заметил, что собственные файлы Magento 1 не всегда используют любой из этих методов для удаления.

Один из худших кодов, который я видел, - это massDelete()метод, из app/code/core/Mage/Adminhtml/controllers/Catalog/ProductController.phpкоторого продукты загружаются в цикл перед удалением .

foreach ($productIds as $productId) {
    $product = Mage::getSingleton('catalog/product')->load($productId);
    Mage::dispatchEvent('catalog_controller_product_delete', array('product' => $product));
    $product->delete();
}

Поэтому я провел несколько тестов производительности, добавил несколько вызовов в журналы, чтобы проверить время и использование памяти для удаления 100 продуктов.

Тест 1: walkметод

Я заменил оригинальный код, вставленный выше, этим кодом:

$collection = Mage::getResourceModel('catalog/product_collection')
                        ->addAttributeToSelect('entity_id')
                        ->addIdFilter($productIds)
                        ->walk('delete');

И мои результаты следующие на моем crappy dev сервере (в среднем по 10 тестам):

  • Исходный код: 19,97 секунд, использовано 15,84 МБ
  • Пользовательский код: 17,12 секунд, используется 15,45 МБ

Таким образом, для удаления 100 продуктов мой пользовательский код на 3 секунды быстрее и использует на 0,4 МБ меньше.

Тест 2: Использование delete()метода сбора

Я заменил оригинальный код на этот:

$collection = Mage::getResourceModel('catalog/product_collection')
                        ->addAttributeToSelect('entity_id')
                        ->addIdFilter($productIds)
                        ->delete();

И умопомрачительные вот результаты:

  • Исходный код: 19,97 секунд, использовано 15,84 МБ
  • Пользовательский код: 1,24 секунды, 6,34 МБ используется

Поэтому при удалении 100 продуктов мой пользовательский код работает на 18 секунд быстрее и использует меньше 9 МБ.

Как указано в комментариях, похоже, что этот метод не вызывает события Magento (после загрузки, после удаления) и очистки индекса / кэша.

Вопрос

Итак, мой вопрос: есть ли причина, по которой основная команда Magento не использовала метод walk('delete')или событие лучше, чем delete()метод сбора вместо загрузки продуктов в цикле (что, как мы все знаем, очень и очень плохая практика)?

Главная цель - знать о таких ключевых моментах в случае разработки модуля: есть ли конкретные случаи, когда нельзя использовать метод walk/ collection delete()?

РЕДАКТИРОВАТЬ: причина определенно не в том, что catalog_controller_product_deleteсобытие отправляется, поскольку один и тот же код можно найти в нескольких местах (проверьте massDeleteметоды) в ядре Magento. Я использовал пример продуктов, чтобы подчеркнуть производительность, поскольку они, как правило, являются крупнейшими организациями

Рафаэль в цифровом пианизме
источник
3
Я думаю, это из-за события. Но я согласен с вами, это плохой стиль, особенно использование в getSingleton()качестве показателя производительности вместо очевидного использования коллекции. Да, и событие можно вызвать с помощью коллекции, но не с помощью walk()ярлыка.
Фабиан Шменглер
1
@fschmengler Да, я тоже думал об этом событии, но, как я уже сказал в своем редактировании, оно происходит во многих местах, где ни одно событие не отправляется.
Рафаэль на цифровом пианизме
3
Неудивительно. delete()делает запрос DELETE вместо загрузки коллекции и удаления каждого продукта. С этим вы действительно потеряете события.
Фабиан Шменглер
5
@fschmengler Удаление коллекции также выполняет удаление для каждого отдельного элемента, но оно обходит очистку кэша и запуск некоторых событий magento и indexer. Вот откуда разница.
Винай
2
@ Винай, ты прав. Желаемое за действительное на моей стороне
Фабиан Шменглер

Ответы:

4

Поэтому я сделал несколько тестов производительности, добавил несколько звонков в журнал, чтобы проверить время и использование памяти для удаления 100 продуктов.

Примечание, но вы должны посмотреть на использование Varien Profiler для этого!

мой пользовательский код на 2 секунды быстрее и использует 0,4 МБ меньше

Хотя я не сомневаюсь, что ваши изменения повысят производительность, было бы полезно предоставить результаты «до» для сравнения улучшений.

есть ли причина, по которой основная команда Magento не использовала walk('delete')вместо загрузки продуктов в цикле (что, как мы все знаем, очень и очень плохая практика)?

Ну, мы знаем из других вопросов на этом форуме следующее:

  • Кодовая база Magento развивалась и развивалась на протяжении многих лет.
  • Над этим работало много разработчиков
  • Процессы разработки ядра Magento значительно улучшились за время их работы на платформе, догоняя современные передовые практики и методы, и теперь Magento 2 демонстрирует многие ведущие современные методы проектирования приложений.

Поэтому я хотел бы предположить, что пример, который вы нашли, вероятно, является одним из потенциально многих драгоценных камней, скрытых в коде, который был написан давным-давно и / или менее опытным разработчиком. Как и большая часть основного кода (и кода сообщества!), Он был бы протестирован на небольшом наборе данных и не проверен в бою, поэтому производительность, возможно, не отслеживалась.

Является ли ваше улучшение полезным и более тесно связано с лучшими практиками, чем исходный код? Да. Однако вы, как разработчик сообщества Magento [1.x], не можете вносить предлагаемые улучшения, как вы делаете это с Magento 2, поэтому я бы предложил реализовать это в локальном модуле, если вам это требуется для производительности в одном из ваших магазинов. или не обращайте на это внимания, если это не влияет на вас, но вы заметили это во время исследования.

Как обновление вашего вопроса редактирования, я уверен, что вы знаете, что метод ходьбы в Varien_Data_Collection принимает произвольный обратный вызов, так что вы можете использовать его для всего, что вы, вероятно, хотели. Для отправки события в исходном примере вы можете сделать это с помощью функции ходьбы, а также удаления.

Единственная причина, по которой я мог представить, что загрузка продукта перед его удалением будет полезной, может заключаться в том, что наблюдателям, прикрепленным к этому событию, может понадобиться полный набор данных, недоступный без предварительной загрузки продукта. Если это так, то это объясняет, почему они используют синглтон, а не модель, чтобы, по крайней мере, минимизировать накладные расходы объекта.

Робби Аверилл
источник
Спасибо, я добавил до и после результатов к сообщению. Так вы думаете, что нет особой причины, кроме того, что это старый код?
Рафаэль на цифровом пианизме
2
Это было бы мое предположение, да. Загрузка продукта перед его удалением не принесет никакой пользы, кроме запуска событий загрузки, которые не имеют отношения к удалению. Обычно вы загружаете продукт, чтобы получить полный набор данных, который может потребоваться одному из наблюдателей, прикрепленных к событию - в этом случае будет объяснено, почему они используют синглтон вместо модели.
Робби Аверилл
1
Смотрите мое редактирование с большим количеством тестов, результаты еще более безумные
Рафаэль на Digital Pianism
0

Я думаю, что они делают это, чтобы запустить catalog_controller_product_deleteсобытие, которое используется Mage_Tag.

catalog_product_delete_beforeили catalog_product_delete_afterбудет означать, что это не нужно, хотя я бы подумал. Интересно, это конкретное событие также используется для регистрации действий администратора.

Дэниел Кенни
источник
Мысль об этом тоже, но это определенно не причина, как это происходит и с massDelete()действиемCustomerController.php
Рафаэля в Digital Pianism
Смотрите мое редактирование с большим количеством тестов, результаты еще более безумные
Рафаэль на Digital Pianism
0

Я думаю, что массовое удаление должно работать как удаление одного (полностью загруженного) продукта.

Ибо $collection->delete()ответ уже дан. Если вы не активируете deleter_before, delete_afterя мог бы сломать некоторые расширения и обойти некоторых наблюдателей, используемых в ядре.

$collection->walk('delete')возможно, сработает, но все же имеет тот недостаток, что данные о продукте не полны. Это также может нарушить работу пользовательских наблюдателей, если они полагаются на дополнительные данные, например, на объект товарной позиции.

Я думаю, если вы измените ->addAttributeToSelect('entity_id')к ->addAttributeToSelect('*')и добавить ->setFlag('require_stock_items', true)(добавить данные о запасах в продукты) не будет работать лучше , чем «петля удаления».

Выглядит как плохой стиль, но я думаю, что это правильно для обоих действий массового удаления.

Я использую walk()и delete()для пользовательских моделей тоже, но я знаю, что нет наблюдателей или entity_idдостаточно. Просто чтобы упомянуть, walk()будет работать со всеми событиями, используемыми в ядре, потому что они только используют $product->getId(), но вы не знаете о сторонних наблюдателей.

sv3n
источник