Производительность: добавьте уровни запасов в списки продуктов list.phtml всех типов продуктов

12

TL; DR. Требуется , чтобы уровень запасов был отображен на странице с перечнем продуктов категории с минимальными дополнительными запросами / памятью, учитывая производительность, соответствующую структуре Magento.


После прочтения статьи Винай Копп о предварительной загрузке для масштабируемости .

Каков наилучший способ включить уровни запасов на страницах со списком продуктов категории ( list.phtml ) с минимальным количеством дополнительных запросов / загрузок для повышения производительности?

Мне известны несколько подходов:

afterLoad (), кажется, хорошо работает сmedia_galleryвключением без дополнительных запросов, однако я не смог реализовать тот же подход с инвентарем.

$attributes = $_product->getTypeInstance(true)->getSetAttributes($_product);
$media_gallery = $attributes['media_gallery'];
$backend = $media_gallery->getBackend();
$backend->afterLoad($_product);

Прямой SQL для сбора необходимых данных параллельно с коллекцией с product_idключом, например. Но ищу больше средств через рамки.

В настоящее время я просто загружаю stock_itemобъект через:

$_product->load('stock_item')->getTotalQty(); Что работает, но я замечаю добавление дополнительных запросов, чтобы получить итоговые данные инвентаризации всех продуктов в коллекции.

...

__EAV_LOAD_MODEL__ (Mage_Catalog_Model_Product, id: stock_item, attributes: NULL)
__EAV_LOAD_MODEL__ (Mage_Catalog_Model_Product, id: stock_item, attributes: NULL)
__EAV_LOAD_MODEL__ (Mage_Catalog_Model_Product, id: stock_item, attributes: NULL)

...

странно, это работает. Волшебство происходит при загрузке Mage_Eav_Model_Entity_Abstract-> ($ object, $ entityId, $ attribute). Если $ attribute пуст, он вызовет loadAllAttribute ($ object). Таким образом, $ product-> load ('blah') загрузит все недостающие атрибуты, включая 'media_gallery' - Уильям Тран 19 ноября '14 в 4:45

Добавьте необходимые значения в уже загруженную коллекцию.

Очевидный простой подход добавления необходимых данных к производственному набору верхнего уровня в слое / фильтре, казалось бы, лучший подход.

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

<events>
    <catalog_product_collection_load_after>
        <observers>
            <inventory>
                <class>cataloginventory/observer</class>
                <method>addInventoryDataToCollection</method>
            </inventory>
        </observers>
    </catalog_product_collection_load_after>
</events>

Что приводит к:

Предупреждение: неверный аргумент указан для foreach () в /app/code/core/Mage/CatalogInventory/Model/Resource/Stock/Item/Collection.php в строке 71

B00MER
источник
1
Хороший вопрос, Бумер
Амит Бера

Ответы:

4

Реальная проблема здесь не в предварительной загрузке, а в точности. Относительно легко получить сумму запаса для коллекции продуктов:

$products = Mage::getModel('catalog/product')->getCollection()
    ->addCategoryFilter($_category);
$stockCollection = Mage::getModel('cataloginventory/stock_item')
    ->getCollection()
    ->addProductsFilter($products);

Теперь с двумя запросами у вас есть вся необходимая информация. Их просто трудно связать друг с другом, что можно исправить с помощью ассоциативного массива 'product_id' => 'stock'и написания геттера. Кроме того, addProductsFilter может быть оптимизирован:

public function addProductIdsFilter(array $productIds)
{
    if(empty($productIds) {
        $this->_setIsLoaded(true);
    }
    $this->addFieldToFilter('main_table.product_id', array('in' => $productIds));
    return $this;
}

Это избавит вас от проверки типа и клонирования массива.

Проблема сейчас в блоке HTML кеша. Эта страница категории должна быть очищена, когда любой запас обновляется на продукт, содержащийся в нем. Насколько я знаю, это не является стандартным, поскольку только изменение состояния запаса очищает страницу категории, содержащую продукт (или, точнее, изменение видимости). Таким образом, вы должны будете наблюдать хотя бы cataloginventory_stock_item_before_saveи, возможно, несколько других и очищать html-кэш блока (и кэш FPC) для этой страницы категории.

Мелвин
источник
Ваш последний пункт - причина, по которой мы пошли на дополнительный запрос для получения данных о запасах. Если у вас есть категории с быстродвижущимися продуктами, вы кешируете большую часть своего времени, будучи очищенными / аннулированными. единственный раз, когда нам нужно очистить кэш страницы категории, это если продукт удален из этой категории. Не существует единственного волшебного решения, которое вам нужно было бы разработать наиболее эффективную реализацию в каждом конкретном случае. Если вы хотите, вы можете кэшировать данные о запасах, так что только их нужно восстановить после очистки, а не всю страницу.
Джон-Джи
Если вы реализуете данные о запасах таким же образом, как и сейчас, используя данные JavaScript для обновления DOM, вы можете записать их, используя блок с собственным ключом кэша, и аннулировать только данные о запасах. Вот как я бы сделал это для вашей ситуации и использовал бы не FPC на основе ESI, а тот, который может пробивать блокировку в процессоре запросов. Не только для бита маршрутизатора, но и для того, чтобы требовать только одного интерпретатора php на страницу. Когда машина по какой-либо причине оказывается под давлением, ваш интерпретатор php является наиболее ресурсоемким ресурсом. Это начинает звучать все больше и больше как хороший проект выходного дня ;-)
Melvyn
3
Это тот тип вещей, который делает работу по-настоящему интересной, когда вы действительно должны думать о реализации, потенциальных проблемах и узких местах. Я вызывающе вижу, откуда вы идете с одним PHP-процессом, в данный момент это не проблема для нас, но я вижу, как это повлияет на масштабирование. Значения запасов, отображаемые на странице, не являются критически важной функциональностью, поэтому мы загружаем их как вторичный ресурс после загрузки, не влияя на пользователя. Это позор акции в списке продуктов, а не функция по умолчанию.
Джон-Дж
1
Да, я сейчас играю с идеей, чтобы получить это непосредственно от Redis и вырезать php. Здесь есть действительно интересная работа Ичунь Чжана на открытой Resty .
Мелвин
2

Я вижу, что вы уже приняли и, без сомнения, что-то уже реализовали, однако я хотел бы указать, насколько вы были близки, addInventoryDataToCollection()но похоже, что вы неверно процитировали файл конфигурации или мы используем совершенно разные версии magento. Моя копия CatalogInventory/etc/config.xmlимеет другой метод дляcatalog_product_collection_load_after

 <catalog_product_collection_load_after>
    <observers>
        <inventory>
            <class>cataloginventory/observer</class>
            <method>addStockStatusToCollection</method>
        </inventory>
    </observers>
 </catalog_product_collection_load_after>

addInventoryDataToCollection() называется в <sales_quote_item_collection_products_after_load>

Источник для addStockStatusToCollection():

public function addStockStatusToCollection($observer)
{
    $productCollection = $observer->getEvent()->getCollection();
    if ($productCollection->hasFlag('require_stock_items')) {
        Mage::getModel('cataloginventory/stock')->addItemsToProducts($productCollection);
    } else {
        Mage::getModel('cataloginventory/stock_status')->addStockStatusToProducts($productCollection);
    }
    return $this;
}

Вы можете либо установить флаг require_stock_itemsдля коллекции перед ее загрузкой, возможно, это не так просто для блока за списком категорий, или вы можете вызвать Mage::getModel('cataloginventory/stock')->addItemsToProducts($productCollection)вручную коллекцию после того, как она уже загружена. addItemsToProducts()получает все StockItems для вас и прикрепляет их к вашей ProductCollection

public function addItemsToProducts($productCollection)
{
    $items = $this->getItemCollection()
        ->addProductsFilter($productCollection)
        ->joinStockStatus($productCollection->getStoreId())
        ->load();
    $stockItems = array();
    foreach ($items as $item) {
        $stockItems[$item->getProductId()] = $item;
    }
    foreach ($productCollection as $product) {
        if (isset($stockItems[$product->getId()])) {
            $stockItems[$product->getId()]->assignProduct($product);
        }
    }
    return $this;
}
Ричард
источник
1

Вы используете Varnish или FPC вообще или планируете в будущем?

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

Мы внедрили решение на одном веб-сайте, который использует запрос AJAX к пользовательскому контроллеру для получения данных о запасах продуктов, а javascript обрабатывает обновления DOM. Дополнительный запрос данных о запасе занимает ~ 100 мс, что никак не влияет на общее (видимое) время загрузки страницы. В сочетании с тем, что FPC снизит количество запросов страниц до 100 мс, у вас будет один быстрый сайт с низкими издержками производительности для отображения данных о запасах в списках продуктов.

Все, что вам нужно сделать по шаблону, это добавить productId в каждый html-упаковщик продуктов, чтобы ваш javascript знал, какие данные о запасах следует применять к каждому продукту.

Если вы посмотрите на дополнительные методы кэширования для фактических данных о запасах, вы можете опустить их намного ниже 100 мс, если бы не нужно было инициировать Mage / попадать в базу данных при каждом запросе.

Извините, если это не соответствует тому, что вы ищете, но мы нашли, что это лучший подход для масштабируемости и производительности в соответствии с нашими требованиями.

джон-JH
источник
2
Разверните обезьяну хаоса и посмотрите, что произойдет, когда ваша кеш-память упадет. Вопрос, в частности, упоминает не полагаться на кэш. Именно такие ответы заставляют нас убеждать клиента в том, что FPC не будет намного сложнее исправлять свой шаблон BuiltIn / MomsBasement.
Мелвин
Вы действительно неправильно понимаете мой ответ, если считаете, что я предлагаю использовать FPC / Varnish для повышения производительности и в качестве решения. Я заявил, что если они используют или рассматривают FPC / Vanish, им следует изучить этот подход, чтобы уменьшить число пробивок / запросов ESI. Мы получаем данные о запасах менее чем за 100 мс, не оставляя кеш-памяти.
Джон-Дж.
И вы получите те же данные в то же время, используя ESI. Ваш подход к ESI просто принципиально другой. И так, без какого-либо кэша вы можете получить те же данные за меньшее время. Вместо того, чтобы проходить через еще один маршрутизатор для отправки обратно JSON, вы пишете стандартный массив на JavaScript, который обновляет DOM и т. Д. Черт, поместите его в JSON, если хотите, просто отключите маршрутизатор.
Мелвин
1
Будет использоваться кэширование, но вопрос скорее в том, что касается оптимизации производительности. Немного предыстории: magento.stackexchange.com/questions/13957/… (без кеша / почти без кеша) Спасибо за ответ, тем не менее, благодарю за вклад!
B00MER
Не уверен, что в M2 есть способ «наилучшей практики», но ваше решение состоит в том, как мы всегда делали это начиная с Magento 1.x.
thdoan