Leistung: Fügen Sie Lagerbestände aller Produkttypen in die Produktliste "list.phtml" ein


12

TL; DR , Die Anforderung besteht darin, dass der Lagerbestand auf der Seite mit den Produktlisten der Kategorie mit möglichst wenig zusätzlichen Abfragen / Speicher angezeigt wird, um die Leistung zu gewährleisten, die dem Magento-Framework entspricht.


Nach dem Lesen von Vinai Kopps Artikel über das Vorladen für Skalierbarkeit .

Was ist der beste Weg, um die Lagerbestände auf den Seiten mit den Produktlisten der Kategorie ( list.phtml ) mit möglichst wenigen zusätzlichen Abfragen / Ladevorgängen aus Gründen der Leistung einzuschließen?

Mir sind einige Ansätze bekannt:

afterLoad () scheint gut mitmedia_galleryInklusion ohne zusätzliche Abfragenzu funktionieren, es ist mir jedoch nicht gelungen, denselben Ansatz mit Inventory zu implementieren.

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

Direktes SQL zum Sammeln der erforderlichen Daten parallel zur Sammlung, beispielsweise mit einem product_idSchlüssel. Suche aber eher nach einem Mittel durch das Framework.

Momentan lade ich das stock_itemObjekt einfach über:

$_product->load('stock_item')->getTotalQty(); Was funktioniert, aber ich stelle fest, dass weitere Abfragen hinzugefügt wurden, um den Lagerbestand aller Produkte in der Sammlung zu ermitteln.

...

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

...

seltsamerweise funktioniert dies. Die Magie geschieht in Mage_Eav_Model_Entity_Abstract-> load ($ object, $ entityId, $ attributes). Wenn $ attributes leer ist, wird loadAllAttribute ($ object) aufgerufen. $ Product-> load ('blah') lädt also alle fehlenden Attribute, einschließlich 'media_gallery' - William Tran 19. November 14 um 4:45

Fügen Sie der bereits geladenen Sammlung die erforderlichen Werte hinzu.

Der naheliegende einfache Ansatz, die erforderlichen Daten der Produktionssammlung der obersten Ebene im Layer / Filter hinzuzufügen, scheint der beste Ansatz zu sein.

Ich habe in Mage_CatalogInventory_Model_Observer einen Beobachter namens addInventoryDataToCollection () festgestellt , der sich so anhört, als würde er dies erreichen. Das Hinzufügen der Methode zu einem benutzerdefinierten Modulbeobachter scheint jedoch nicht kompatibel zu sein.

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

Was in ... endet:

Warnung: Ungültiges Argument für foreach () in /app/code/core/Mage/CatalogInventory/Model/Resource/Stock/Item/Collection.php in Zeile 71


1
Gute Frage Boomer
Amit Bera

Antworten:


4

Das eigentliche Problem ist hier nicht das Vorladen, sondern die Genauigkeit. Es ist relativ einfach, den Lagerbestand für eine Sammlung von Produkten zu ermitteln:

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

Jetzt haben Sie mit zwei Abfragen alle Informationen, die Sie benötigen. Sie sind nur schwer miteinander in Beziehung zu setzen, was durch die Verwendung eines assoziativen Arrays 'product_id' => 'stock'und das Schreiben eines Getters behoben werden kann . Außerdem kann addProductsFilter optimiert werden:

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

Dies erspart Ihnen die Typprüfung und das Klonen von Arrays.

Das Problem ist jetzt Block HTML Cache. Diese Kategorieseite muss gelöscht werden, wenn der Lagerbestand eines darin enthaltenen Produkts aktualisiert wird. Soweit ich weiß, ist dies kein Standard, da nur eine Änderung des Lagerstatus eine Kategorieseite löscht, die ein Produkt enthält (genauer gesagt eine Änderung der Sichtbarkeit). Sie müssen also mindestens cataloginventory_stock_item_before_saveeinige andere beobachten und den Block-HTML-Cache (und den FPC-Cache) für diese Kategorieseite leeren.


Ihr letzter Punkt ist der Grund, warum wir uns für die zusätzliche Anforderung entschieden haben, die Bestandsdaten abzurufen. Wenn Sie Kategorien mit sich schnell bewegenden Produkten haben, wird der Cache die meiste Zeit geleert / ungültig gemacht. Das einzige Mal, dass wir einen Kategorieseiten-Cache leeren müssen, ist, wenn ein Produkt aus dieser Kategorie entfernt wird. Es gibt keine magische Lösung, bei der Sie von Fall zu Fall die effizienteste Implementierung ermitteln müssen. Wenn Sie möchten, können Sie die Bestandsdaten zwischenspeichern, sodass nur diese nach dem Leeren neu erstellt werden müssen und nicht die gesamte Seite.
John-jh

Wenn Sie die Bestandsdaten auf die gleiche Weise wie jetzt implementieren und JavaScript-Daten zum Aktualisieren des DOM verwenden, können Sie dies mithilfe eines Blocks mit einem eigenen Cache-Schlüssel schreiben und nur die Bestandsdaten ungültig machen. Auf diese Weise würde ich es für Ihre Situation tun und keine ESI-basierte FPC verwenden, sondern eine, die Lochblöcke in den Anforderungsprozessor lochen kann. Nicht nur für das Router-Bit, sondern auch dafür, dass nur ein PHP-Interpreter pro Seite benötigt wird. Wenn eine Maschine aus irgendeinem Grund unter Druck gerät, ist Ihr PHP-Interpreter die CPU-intensivste Ressource. Dies klingt immer mehr nach einem schönen Wochenendprojekt ;-)
Melvyn

3
Dies ist die Art von Dingen, die den Job wirklich interessant machen, bei denen man wirklich über die Implementierung und die möglichen Probleme und Engpässe nachdenken muss. Ich sehe trotzig ein, woher Sie mit dem einzelnen PHP-Prozess kommen. Dies ist im Moment kein Problem für uns, kann aber sehen, wie sich dies auf Ihre Skalierung auswirken würde. Die auf der Seite angezeigten Lagerwerte sind keine kritischen Funktionen, daher laden wir sie nach dem Laden als sekundäre Ressource, ohne den Benutzer zu beeinträchtigen. Es ist eine Schande, dass der Lagerbestand auf der Produktliste kein Standardmerkmal ist.
John-jh

1
Ja, ich spiele jetzt mit der Idee, dies direkt von Redis holen zu lassen und PHP auszuschneiden. Hier gibt es einige wirklich interessante Arbeiten von Yichun Zhang zu Open Resty .
Melvyn

2

Ich sehe, dass Sie bereits etwas akzeptiert und zweifellos bereits implementiert haben. Ich möchte jedoch darauf hinweisen, wie nah Sie dran waren, addInventoryDataToCollection()aber es sieht so aus, als hätten Sie die Konfigurationsdatei falsch zitiert, oder wir verwenden sehr unterschiedliche Versionen von Magento. Meine Kopie von CatalogInventory/etc/config.xmlhat eine andere Methode verlangtcatalog_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() wird angerufen <sales_quote_item_collection_products_after_load>

Die Quelle für addStockStatusToCollection()ist:

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

Sie können entweder das Flag require_stock_itemsfür die Sammlung setzen, bevor sie geladen wird, was für den Block hinter der Kategorieliste wahrscheinlich nicht so einfach ist, oder Sie können Mage::getModel('cataloginventory/stock')->addItemsToProducts($productCollection)die Sammlung manuell aufrufen , nachdem sie bereits geladen wurde. addItemsToProducts()Ruft alle StockItems für Sie ab und hängt sie an Ihre ProductCollection an

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

Verwenden Sie Varnish oder FPC überhaupt oder planen Sie es in Zukunft?

Wir haben festgestellt, dass es sich bei der Anzahl der erforderlichen Lochungen / ESI-Anforderungen in den Produktlisten kaum gelohnt hat, das Caching einzurichten, sodass wir uns für einen anderen Ansatz entschieden haben.

Wir haben eine Lösung auf einer Website implementiert, die eine AJAX-Anfrage an einen benutzerdefinierten Controller verwendet, um die Bestandsdaten für die Produkte abzurufen, und das Javascript übernimmt die DOM-Aktualisierungen. Die zusätzliche Anforderung der Bestandsdaten dauert ca. 100 ms, was sich überhaupt nicht auf die gesamte (sichtbare) Ladezeit der Seite auswirkt. Wenn Sie die Seitenanforderung mit vorbereiteter FPC auf unter 100 ms reduzieren, haben Sie eine schnelle Site mit geringem Leistungsaufwand für die Anzeige von Bestandsdaten in den Produktlisten.

Alles, was Sie tun müssen, um eine Vorlage zu erstellen, ist das Hinzufügen der productId zu jedem Produkt-Wrapper-HTML, damit Ihr Javascript weiß, welche Bestandsdaten für jedes Produkt gelten sollen.

Wenn Sie sich zusätzliche Zwischenspeichertechniken für die tatsächlichen Bestandsdaten ansehen, können Sie diese deutlich unter 100 ms senken, indem Sie nicht bei jeder Anforderung Mage / Hit in die Datenbank einleiten müssen.

Es tut uns leid, wenn dies nicht dem entspricht, was Sie suchen, aber wir haben festgestellt, dass dies der beste Ansatz für Skalierbarkeit und Leistung in Bezug auf unsere Anforderungen ist.


2
Stellen Sie Chaos Monkey bereit und sehen Sie, was passiert, wenn der Cache-Speicher ausfällt. In der Frage wird ausdrücklich darauf hingewiesen, sich nicht auf den Cache zu verlassen. Es sind Antworten wie diese, die es uns zur Aufgabe machen, einen Kunden davon zu überzeugen, dass FPC seine BuiltIn / MomsBasement-Vorlage nicht so sehr reparieren wird.
Melvyn,

Sie missverstehen meine Antwort wirklich, wenn Sie denken, dass ich FPC / Varnish für die Leistung und als Lösung vorschlage. Ich erklärte, wenn sie FPC / Vanish verwenden oder in Betracht ziehen, sollten sie diesen Ansatz untersuchen, um Locher / ESI-Anforderungen zu reduzieren. Wir erhalten die benötigten Bestandsdaten in weniger als 100 ms, ohne den Cache zu verlagern.
John-jh

Mit ESI erhalten Sie dieselben Daten zur selben Zeit. Ihr Ansatz bei ESI unterscheidet sich grundlegend. Ohne Cache können Sie also in kürzerer Zeit dieselben Daten abrufen. Anstatt noch einen anderen Router zu durchlaufen, um JSON zurückzusenden, schreiben Sie ein Standardarray in JavaScript, das das DOM usw. aktualisiert. Hölle, setzen Sie es in JSON ein, wenn Sie möchten, schneiden Sie einfach den Router aus.
Melvyn

1
Zwischenspeicherung wird verwendet, die Frage geht jedoch eher um Leistungsoptimierungen. Einige Hintergrundinformationen: magento.stackexchange.com/questions/13957/… (kein Cache / kaum welche) Vielen Dank für die Antwort, aber danke für die Eingabe!
B00MER

Wir sind uns nicht sicher, ob es in M2 eine "Best Practice" -Methode gibt, aber Ihre Lösung ist, wie wir es seit Magento 1.x immer gemacht haben.
Donnerstag,
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.