Rendimiento: agregue niveles de inventario de inventario en la lista de productos list.phtml de todos los tipos de productos

12

TL; DR : el requisito es que se muestre un nivel de inventario en la página de listado de productos de categoría con tan pocas consultas / memoria adicionales con un rendimiento en mente que se adhiere al marco de Magento.


Después de leer el artículo de Vinai Kopp sobre precarga para la escalabilidad .

¿Cuál es la mejor manera de incluir los niveles de inventario en las páginas de listado de productos de categoría ( list.phtml ) con tan pocas consultas / cargas adicionales por motivos de rendimiento?

Soy consciente de algunos enfoques:

afterLoad () parece funcionar bien con lamedia_galleryinclusión sin consultas adicionales, sin embargo, no he tenido éxito al implementar el mismo enfoque con el inventario.

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

SQL directo para recopilar los datos necesarios en paralelo a la recopilación con una product_idclave, por ejemplo. Pero buscando más medios a través del marco.

Actualmente simplemente estoy cargando el stock_itemobjeto a través de:

$_product->load('stock_item')->getTotalQty(); Lo que funciona, pero noto la adición de más consultas para obtener el inventario total de todos los productos de la colección.

...

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

...

Curiosamente, esto funciona. La magia ocurre en Mage_Eav_Model_Entity_Abstract-> load ($ object, $ entityId, $ atributos). Si $ atributos está vacío, llamará a loadAllAttribute ($ objeto). Entonces $ product-> load ('blah') cargará todos los atributos faltantes, incluyendo 'media_gallery' - William Tran 19 de noviembre de 14 a las 4:45

Agregue los valores necesarios a la colección ya cargada.

El enfoque simple obvio de agregar los datos necesarios a la colección de producción de nivel superior en la capa / filtro, parece ser el mejor enfoque.

Me di cuenta de un observador addInventoryDataToCollection () en Mage_CatalogInventory_Model_Observer que parece que lo lograría, pero agregar el método a un observador de módulos personalizados no parece ser compatible.

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

Lo que resulta en:

Advertencia: argumento no válido proporcionado para foreach () en /app/code/core/Mage/CatalogInventory/Model/Resource/Stock/Item/Collection.php en la línea 71

B00MER
fuente
1
Buena pregunta boomer
Amit Bera

Respuestas:

4

El verdadero problema aquí no es la precarga, es la precisión. Es relativamente fácil obtener la cantidad de existencias para una colección de productos:

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

Ahora con dos consultas, tiene toda la información que necesita. Simplemente son difíciles de relacionar entre sí, lo que se puede solucionar mediante el uso de una matriz asociativa 'product_id' => 'stock'y escribiendo un captador. Además, addProductsFilter se puede optimizar:

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

Esto le ahorra la verificación de tipo y la clonación de matrices.

El problema ahora es Bloquear caché HTML. Esta página de categoría debe purgarse cuando se actualiza cualquier stock de un producto contenido en él. Hasta donde sé, esto no es estándar, ya que solo un cambio en el estado de las existencias elimina una página de categoría que contiene un producto (o más exactamente, un cambio de visibilidad). Por lo tanto, deberá observar al menos cataloginventory_stock_item_before_savey tal vez algunos otros y purgar el caché html de bloque (y el caché FPC) para esa página de categoría.

Melvyn
fuente
Su punto final es la razón por la que solicitamos la solicitud adicional para recuperar los datos de stock. Si tiene categorías con productos que se mueven rápidamente, el caché pasará la mayor parte de su tiempo enjuagado / invalidado. la única vez que necesitamos purgar un caché de página de categoría es si un producto se elimina de esa categoría. No hay una única solución mágica que deba resolver la implementación más eficiente caso por caso. Si lo desea, puede almacenar en caché los datos de inventario para que solo tenga que reconstruirse después del vaciado en lugar de la página completa.
john-jh
Si implementa los datos de inventario de la misma manera que lo hace ahora, usando datos de JavaScript para actualizar el DOM, puede escribir eso usando un bloque con su propia clave de caché y solo invalidar los datos de inventario. Así es como lo haría para su situación y no usaría un FPC basado en ESI, sino uno que pueda perforar bloques en el procesador de solicitudes. No solo para el bit del enrutador, sino también para requerir solo un intérprete php por página. Cuando una máquina se ve presionada por cualquier razón, su intérprete php es el recurso más intensivo de CPU. Esto comienza a sonar más y más como un buen proyecto de fin de semana ;-)
Melvyn
3
Este es el tipo de cosas que hacen que el trabajo sea realmente interesante en el que hay que pensar realmente en la implementación y los posibles problemas y cuellos de botella. Definitivamente veo de dónde vienes con el proceso PHP único, esto por el momento no es un problema para nosotros, pero puedo ver cómo impactaría a medida que escalas. Los valores de stock que se muestran en la página no son funciones críticas, por lo que lo cargamos como un recurso secundario después de la carga sin afectar al usuario. Es una acción vergonzosa en la lista de productos, no una característica predeterminada.
john-jh
1
Sí, estoy jugando con la idea de obtener esto directamente de Redis y cortar php. Aquí hay un trabajo realmente interesante de Yichun Zhang sobre Resty abierto .
Melvyn
2

Veo que ya ha aceptado y, sin duda, ha implementado algo por ahora, sin embargo, me gustaría señalar lo cerca que estaba, addInventoryDataToCollection()pero parece que ha citado incorrectamente el archivo de configuración o estamos usando versiones muy diferentes de magento. Mi copia de CatalogInventory/etc/config.xmltiene un método diferente paracatalog_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() se llama en <sales_quote_item_collection_products_after_load>

La fuente addStockStatusToCollection()es:

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

Puede establecer el indicador require_stock_itemsen la colección antes de que se cargue, probablemente no sea tan fácil para el bloque detrás de la lista de categorías, o puede llamar Mage::getModel('cataloginventory/stock')->addItemsToProducts($productCollection)manualmente a la colección, después de que ya esté cargada. addItemsToProducts()obtiene todos los artículos de stock para usted y los adjunta a su colección de productos

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;
}
Ricardo
fuente
1

¿Estás usando Varnish o FPC en absoluto, o planeas hacerlo en el futuro?

Descubrimos que con la cantidad de perforaciones / solicitudes de ESI requeridas en las listas de productos, casi no valía la pena tener el almacenamiento en caché en su lugar, por lo que optamos por un enfoque diferente.

Implementamos una solución en un sitio web que utiliza una solicitud AJAX a un controlador personalizado para recuperar los datos de stock de los productos y el JavaScript maneja las actualizaciones DOM. La solicitud adicional de los datos de inventario tarda ~ 100 ms, lo que no afecta en absoluto el tiempo de carga de la página (visible) general. Si a esto le sumamos que con el FPC preparado reduciendo la solicitud de la página a menos de 100 ms, tiene un sitio rápido con una sobrecarga de bajo rendimiento para mostrar datos de stock en las listas de productos.

Todo lo que necesita hacer con una plantilla inteligente es agregar el productId a cada contenedor de productos html para que su javascript sepa qué datos de stock aplicar a cada producto.

Si observa técnicas de almacenamiento en caché adicionales para los datos de existencias reales, podría caer muy por debajo de 100 ms al no tener que iniciar Mage / golpear la base de datos en cada solicitud.

Lo sentimos si esto no está en la línea de lo que está buscando, pero descubrimos que es el mejor enfoque para la escalabilidad y el rendimiento según nuestros requisitos.

john-jh
fuente
2
Implemente chaos monkey y vea qué sucede cuando su almacenamiento en caché se cae. La pregunta menciona específicamente no confiar en el caché. Son respuestas como esta las que hacen que nuestro trabajo de convencer a un cliente de que FPC no va a arreglar su plantilla BuiltIn / MomsBasement mucho más difícil.
Melvyn
Realmente no entiendes mi respuesta si crees que estoy sugiriendo FPC / Varnish para el rendimiento y como la solución. Dije que si están usando o considerando FPC / Vanish, deberían investigar este enfoque para reducir las perforaciones / solicitudes de ESI. Estamos obteniendo los datos de stock que necesitamos en menos de 100 ms sin apalancar el caché.
john-jh
Y obtendría los mismos datos al mismo tiempo usando ESI. Su enfoque con ESI es fundamentalmente diferente. Y así, sin ningún caché, aún puede obtener los mismos datos, en menos tiempo. En lugar de pasar por otro enrutador para enviar JSON de vuelta, usted escribe una matriz de valores en JavaScript que actualiza el DOM, etc. Demonios, póngalo en JSON si lo desea, simplemente corte el enrutador.
Melvyn
1
Se usará el almacenamiento en caché, pero la pregunta es más similar a las optimizaciones de rendimiento. Algunos antecedentes: magento.stackexchange.com/questions/13957/… (sin caché / casi ninguno) ¡Gracias por la respuesta, sin embargo, agradezco la entrada!
B00MER
No estoy seguro si hay una forma de "mejor práctica" para hacer esto en M2, pero su solución es cómo siempre lo hemos hecho desde Magento 1.x.
Thdoan