Performances: ajouter des niveaux de stock d'inventaire sur les listes de produits list.phtml de tous les types de produits

12

TL; DR , L'exigence est d'avoir un niveau de stock d'inventaire affiché sur la page de liste des produits de la catégorie avec aussi peu de requêtes / mémoire supplémentaires avec des performances à l'esprit qui adhèrent au cadre de Magento.


Après avoir lu l'article de Vinai Kopp sur le préchargement pour l'évolutivité .

Quelle est la meilleure façon d'inclure les niveaux de stock dans les pages de liste des produits de la catégorie ( list.phtml ) avec aussi peu de requêtes / chargements supplémentaires pour des raisons de performances?

Je connais quelques approches:

afterLoad () semble bien fonctionner avec l'media_galleryinclusion sans requêtes supplémentaires, mais je n'ai pas réussi à implémenter la même approche avec l'inventaire.

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

Demandez à SQL de collecter les données requises en parallèle à la collecte avec une product_idclé par exemple. Mais à la recherche de plus de moyens dans le cadre.

Actuellement, je charge simplement l' stock_itemobjet via:

$_product->load('stock_item')->getTotalQty(); Ce qui fonctionne, mais je remarque l'ajout de plus de requêtes pour obtenir le total des stocks de tous les produits de la collection.

...

__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)

...

étrangement, cela fonctionne. La magie opère dans Mage_Eav_Model_Entity_Abstract-> load ($ object, $ entityId, $ attributes). Si $ attributes est vide, il appellera loadAllAttribute ($ object). Donc $ product-> load ('blah') chargera tous les attributs manquants, y compris 'media_gallery' - William Tran 19 novembre 14 à 4:45

Ajoutez les valeurs nécessaires à la collection déjà chargée.

L'approche simple évidente consistant à ajouter les données nécessaires à la collection de production de niveau supérieur dans la couche / le filtre semble être la meilleure approche.

J'ai remarqué un observateur addInventoryDataToCollection () dans Mage_CatalogInventory_Model_Observer qui semble pouvoir y arriver, mais l'ajout de la méthode à un observateur de modules personnalisés ne semble pas compatible.

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

Ce qui se traduit par:

Avertissement: argument non valide fourni pour foreach () dans /app/code/core/Mage/CatalogInventory/Model/Resource/Stock/Item/Collection.php sur la ligne 71

B00MER
la source
1
Bonne question boomer
Amit Bera

Réponses:

4

Le vrai problème ici n'est pas le préchargement, c'est la précision. Il est relativement facile d'obtenir le montant du stock pour une collection de produits:

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

Maintenant, avec deux requêtes, vous avez toutes les informations dont vous avez besoin. Ils sont juste difficiles à relier les uns aux autres, ce qui peut être corrigé en utilisant un tableau associatif 'product_id' => 'stock'et en écrivant un getter. De plus, addProductsFilter peut être optimisé:

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

Cela vous évite la vérification de type et le clonage de tableau.

Le problème est maintenant de bloquer le cache HTML. Cette page de catégorie doit être purgée lorsqu'un stock est mis à jour sur un produit qu'il contient. Pour autant que je sache, ce n'est pas standard, car seul un changement d'état des stocks purge une page de catégorie contenant un produit (ou plus précisément, un changement de visibilité). Vous devrez donc observer au moins cataloginventory_stock_item_before_saveet peut-être quelques autres et purger le cache html du bloc (et le cache FPC) pour cette page de catégorie.

Melvyn
la source
Votre dernier point est la raison pour laquelle nous sommes allés pour la demande supplémentaire de récupérer les données de stock. Si vous avez des catégories avec des produits à évolution rapide, votre cache passera la plupart de son temps à être vidé / invalidé. la seule fois où nous devons purger un cache de page de catégorie est si un produit est supprimé de cette catégorie. Il n'y a pas de solution magique unique pour trouver la mise en œuvre la plus efficace au cas par cas. Si vous le souhaitez, vous pouvez mettre en cache les données de stock afin que seules celles-ci soient reconstruites après le vidage plutôt que la page entière.
john-jh
Si vous implémentez les données de stock de la même manière que vous le faites maintenant, en utilisant les données JavaScript pour mettre à jour le DOM, vous pouvez écrire cela en utilisant un bloc avec sa propre clé de cache et invalider uniquement les données de stock. C'est ainsi que je le ferais pour votre situation et n'utiliserais pas un FPC basé sur ESI, mais un qui peut percer des blocs de perforation dans le processeur de demande. Pas seulement pour le bit routeur, mais aussi pour ne nécessiter qu'un seul interprète php par page. Lorsqu'une machine est sous pression pour une raison quelconque, votre interprète php est la ressource la plus intensive en CPU. Cela commence à ressembler de plus en plus à un joli projet de week-end ;-)
Melvyn
3
C'est le genre de choses qui rendent le travail vraiment intéressant où vous devez vraiment penser à la mise en œuvre et aux problèmes et goulots d'étranglement potentiels. Je vois avec défi d'où vous venez avec le processus PHP unique. Pour le moment, ce n'est pas un problème pour nous, mais je peux voir comment cela aurait un impact à mesure que vous évoluez. Les valeurs de stock affichées sur la page ne sont pas des fonctionnalités essentielles, nous la chargeons donc comme ressource secondaire après le chargement sans impact sur l'utilisateur. C'est un stock honteux sur la liste des produits et non une fonctionnalité par défaut.
john-jh
1
Ouais, je joue avec l'idée maintenant d'avoir récupéré directement de Redis et de couper le php. Il y a un travail vraiment intéressant ici de Yichun Zhang sur Resty ouvert .
Melvyn
2

Je vois que vous avez déjà accepté et sans aucun doute implémenté quelque chose maintenant, mais je voudrais souligner à quel point vous étiez proche, addInventoryDataToCollection()mais il semble que vous ayez mal cité le fichier de configuration ou que nous utilisons des versions très différentes de magento. Ma copie CatalogInventory/etc/config.xmla une méthode différente appeléecatalog_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() est appelé <sales_quote_item_collection_products_after_load>

La source de addStockStatusToCollection()est:

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;
}

Vous pouvez soit définir l'indicateur require_stock_itemssur la collection avant qu'elle ne soit chargée, probablement pas aussi simple pour le bloc derrière la liste des catégories, soit appeler Mage::getModel('cataloginventory/stock')->addItemsToProducts($productCollection)manuellement sur la collection, une fois qu'elle est déjà chargée. addItemsToProducts()obtient tous les StockItems pour vous et les attache à votre 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;
}
Richard
la source
1

Utilisez-vous du vernis ou du FPC ou prévoyez-vous de le faire à l'avenir?

Nous avons constaté qu'avec le nombre de perforations / demandes ESI requises sur les listes de produits, la mise en cache ne valait presque pas la peine, alors nous avons opté pour une approche différente.

Nous avons implémenté une solution sur un site Web qui utilise une demande AJAX à un contrôleur personnalisé pour récupérer les données de stock des produits et le javascript gère les mises à jour DOM. La demande supplémentaire pour les données de stock prend environ 100 ms, ce qui n'a aucun impact sur le temps de chargement de la page (visible). Ajoutez à cela qu'avec le FPC amorcé qui réduit la demande de page à moins de 100 ms, vous avez un site rapide avec des frais généraux de faible performance pour afficher les données de stock sur les listes de produits.

Tout ce que vous devez faire en termes de modèle est d'ajouter le productId à chaque emballage de produits html afin que votre javascript sache quelles données de stock appliquer à chaque produit.

Si vous regardez des techniques de mise en cache supplémentaires pour les données de stock réelles, vous pouvez descendre bien en dessous de 100 ms en n'ayant pas à lancer Mage / frapper la base de données à chaque demande.

Désolé si cela ne correspond pas à ce que vous recherchez, mais nous avons trouvé que c'était la meilleure approche pour l'évolutivité et les performances par rapport à nos exigences.

john-jh
la source
2
Déployez le chaos monkey et voyez ce qui se passe lorsque votre stockage de cache tombe. La question mentionne spécifiquement de ne pas compter sur le cache. Ce sont des réponses comme celle-ci qui rendent notre travail de convaincre un client que FPC ne va pas réparer son modèle BuiltIn / MomsBasement beaucoup plus difficile.
Melvyn
Vous comprenez vraiment mal ma réponse si vous pensez que je suggère FPC / Vernis pour la performance et comme solution. J'ai déclaré que s'ils utilisent ou envisagent FPC / Vanish, ils devraient étudier cette approche pour réduire les perforations / demandes ESI. Nous obtenons les données de stock dont nous avons besoin en moins de 100 ms sans laisser de cache.
john-jh
Et vous obtiendriez les mêmes données en même temps en utilisant ESI. Votre approche avec ESI est fondamentalement différente. Et donc, sans cache, vous pouvez toujours obtenir les mêmes données, en moins de temps. Au lieu de passer par un autre routeur pour renvoyer JSON, vous écrivez un tableau de stock en JavaScript qui met à jour le DOM, etc.
Melvyn
1
La mise en cache va être utilisée, mais la question se situe davantage dans le sens d'optimisations des performances. Un peu de fond: magento.stackexchange.com/questions/13957/… (pas de cache / presque pas) Merci pour la réponse cependant, appréciez la contribution!
B00MER
Je ne sais pas s'il existe une méthode de "meilleure pratique" pour le faire dans M2, mais votre solution est de savoir comment nous l'avons toujours fait depuis Magento 1.x.
thdoan