Desempenho: adicione níveis de estoque de inventário à lista de listagens de produtos.phtml de todos os tipos de produtos

12

TL; DR , o requisito é que um nível de estoque do inventário seja exibido na página de listagem de produtos da categoria com menos consultas / memória adicionais, tendo em mente o desempenho que segue a estrutura do Magento.


Depois de ler o artigo de Vinai Kopp sobre pré - carregamento para escalabilidade .

Qual é a melhor maneira de incluir os níveis de estoque do inventário nas páginas de listagem de produtos da categoria ( list.phtml ) com o mínimo de consultas / cargas adicionais por questões de desempenho?

Estou ciente de algumas abordagens:

O afterLoad () parece funcionar bem com amedia_galleryinclusão sem consultas adicionais, no entanto, não consegui implementar a mesma abordagem com o inventário.

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

Direct SQL para reunir os dados necessários em paralelo à coleção com uma product_idchave, por exemplo. Mas procurando mais meios através da estrutura.

Atualmente, estou simplesmente carregando o stock_itemobjeto via:

$_product->load('stock_item')->getTotalQty(); O que funciona, mas noto a adição de mais consultas para obter o total do estoque de todos os produtos da coleção.

...

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

...

estranhamente, isso funciona. A mágica acontece em Mage_Eav_Model_Entity_Abstract-> load ($ object, $ entityId, $ attribute). Se $ attribute estiver vazio, ele chamará loadAllAttribute ($ object). Portanto, $ product-> load ('blah') carregará todos os atributos ausentes, incluindo 'media_gallery' #: 1967 William Tran

Adicione os valores necessários à coleção já carregada.

A abordagem simples e óbvia de adicionar os dados necessários à coleção de produção de nível superior na camada / filtro, parece ser a melhor abordagem.

Eu notei um observador addInventoryDataToCollection () em Mage_CatalogInventory_Model_Observer que parece que conseguiria isso, mas adicionar o método a um observador de módulos personalizados não parece ser compatível.

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

O que resulta em:

Aviso: argumento inválido fornecido para foreach () em /app/code/core/Mage/CatalogInventory/Model/Resource/Stock/Item/Collection.php na linha 71

B00MER
fonte
1
Boa pergunta, boomer
Amit Bera

Respostas:

4

O problema real aqui não é o pré-carregamento, é a precisão. É relativamente fácil obter o valor do estoque de uma coleção de produtos:

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

Agora, com duas consultas, você tem todas as informações necessárias. Eles são difíceis de se relacionar, o que pode ser corrigido usando uma matriz associativa 'product_id' => 'stock'e escrevendo um getter. Além disso, o addProductsFilter pode ser otimizado:

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

Isso economiza a verificação de tipo e a clonagem de matriz.

O problema agora é bloquear o cache HTML. Essa página de categoria precisa ser limpa quando qualquer estoque for atualizado em um produto contido nela. Até onde eu sei, isso não é padrão, pois apenas uma alteração no status do estoque elimina uma página de categoria que contém um produto (ou, mais precisamente, uma alteração na visibilidade). Portanto, você precisará observar pelo menos, cataloginventory_stock_item_before_savetalvez alguns outros, e limpar o cache html do bloco (e o cache do FPC) para essa página de categoria.

Melvyn
fonte
Seu ponto final é o motivo pelo qual solicitamos mais informações para recuperar os dados de estoque. Se você possui categorias com produtos em movimento rápido, o cache passará a maior parte do tempo sendo liberado / invalidado. o único momento em que precisamos limpar um cache de página de categoria é se um produto for removido dessa categoria. Não há uma solução mágica que você precise para implementar a implementação mais eficiente caso a caso. Se você quiser, poderá armazenar em cache os dados de estoque, para que apenas eles sejam reconstruídos após a liberação, em vez de na página inteira.
John-jh
Se você implementar os dados de estoque da mesma maneira que faz agora, usando dados JavaScript para atualizar o DOM, poderá gravá-lo usando um bloco com sua própria chave de cache e invalidar apenas os dados de estoque. É assim que eu faria para a sua situação e não usaria um FPC baseado em ESI, mas que pode perfurar blocos no processador de solicitações. Não apenas pelo bit do roteador, mas também por exigir apenas um interpretador php por página. Quando uma máquina é pressionada por qualquer motivo, seu intérprete php é o recurso mais intensivo de CPU. Isso está começando a soar cada vez mais como um projeto de fim de semana agradável ;-)
Melvyn
3
Esse é o tipo de coisa que torna o trabalho realmente interessante, no qual você deve realmente pensar sobre a implementação e os possíveis problemas e gargalos. Vejo desafiadoramente de onde você vem com o único processo PHP, no momento, isso não é um problema para nós, mas posso ver como isso afetaria à medida que você aumenta a escala. Os valores de estoque exibidos na página não são uma funcionalidade crítica, portanto, carregamos como um recurso secundário após o carregamento, sem afetar o usuário. É um estoque de vergonha na lista de produtos, não um recurso padrão.
31415 John-jh
1
Sim, agora estou brincando com a idéia de obter isso diretamente do Redis e cortar o php. Há algum trabalho realmente interessante aqui de Yichun Zhang no Resty aberto .
Melvyn
2

Vejo que você já aceitou e sem dúvida implementou algo até agora, no entanto, gostaria de salientar o quão perto você estava, addInventoryDataToCollection()mas parece que você citou incorretamente o arquivo de configuração ou estamos usando versões muito diferentes do magento. Minha cópia CatalogInventory/etc/config.xmltem um método diferente chamadocatalog_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() é chamado <sales_quote_item_collection_products_after_load>

A fonte para 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;
}

Você pode definir o sinalizador require_stock_itemsna coleção antes de ser carregado, provavelmente não é tão fácil para o bloco atrás da lista de categorias ou pode chamar Mage::getModel('cataloginventory/stock')->addItemsToProducts($productCollection)manualmente a coleção, depois que ela já estiver carregada. addItemsToProducts()obtém todos os StockItems para você e os anexa à sua 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
fonte
1

Você está usando verniz ou FPC, ou planeja no futuro?

Descobrimos que, com a quantidade de solicitações de perfurações / ESI exigidas nas listagens de produtos, quase não valia a pena ter o cache no local, então optamos por uma abordagem diferente.

Implementamos uma solução em um site que usa uma solicitação AJAX para um controlador personalizado para recuperar os dados de estoque dos produtos e o javascript manipula as atualizações do DOM. A solicitação adicional para os dados de estoque leva aproximadamente 100 ms, o que não afeta o tempo total de carregamento da página (visível). Além disso, com o FPC inicializado, a solicitação da página cai para menos de 100 ms, você tem um site rápido com sobrecarga de baixo desempenho para exibir dados de estoque nas listas de produtos.

Tudo o que você precisa fazer de modelo é adicionar o productId a cada wrapper de produtos html, para que seu javascript saiba quais dados de estoque serão aplicados a cada produto.

Se você observar técnicas de armazenamento em cache adicionais para os dados de estoque reais, poderá cair muito abaixo de 100ms por não precisar iniciar o Mage / acessar o banco de dados em cada solicitação.

Desculpe se isso não coincide com o que você está procurando, mas achamos que é a melhor abordagem para escalabilidade e desempenho em relação aos nossos requisitos.

john-jh
fonte
2
Implante o caos monkey e veja o que acontece quando o armazenamento em cache cai. A pergunta menciona especificamente não confiar no cache. São respostas como essa que tornam nosso trabalho convencer um cliente de que o FPC não corrigirá muito mais o modelo BuiltIn / MomsBasement.
Melvyn
Você realmente entende mal a minha resposta se acha que estou sugerindo FPC / Varnish para desempenho e como solução. Eu afirmei que, se eles estão usando ou considerando o FPC / Vanish, eles devem investigar essa abordagem para reduzir solicitações de perfurações / ESI. Estamos obtendo os dados de estoque necessários em menos de 100 ms sem deixar o cache de cache.
31415 John-jh
E você obteria os mesmos dados ao mesmo tempo usando o ESI. Sua abordagem com o ESI é fundamentalmente diferente. E assim, sem nenhum cache, você ainda pode obter os mesmos dados, em menos tempo. Em vez de passar por outro roteador para enviar de volta o JSON, você escreve uma matriz de ações em JavaScript que atualiza o DOM etc.
Melvyn
1
O cache será usado, mas a questão está mais na linha de otimizações de desempenho. Alguns antecedentes: magento.stackexchange.com/questions/13957/… (sem cache / quase nenhum) Obrigado pela resposta, no entanto, agradeço a entrada!
B00MER
Não tenho certeza se existe uma maneira "recomendada" de fazer isso no M2, mas sua solução é como sempre o fizemos desde o Magento 1.x.
Thdan