Magento 1: Otimizações de desempenho para excluir entidades

10

Atualmente, estou tentando melhorar alguns módulos em relação ao desempenho.

Alguns de vocês devem saber o uso do walk()método na coleta, o que é muito útil para evitar a repetição direta dos produtos.

Além disso, e graças ao @Vinai, também é possível usar o delete()método de coleta .

Mas notei que os arquivos nativos do Magento 1 nem sempre usam qualquer um desses métodos para exclusão.

Um dos piores códigos que eu já vi é o massDelete()método no app/code/core/Mage/Adminhtml/controllers/Catalog/ProductController.phpqual os produtos são carregados em um loop antes da exclusão .

foreach ($productIds as $productId) {
    $product = Mage::getSingleton('catalog/product')->load($productId);
    Mage::dispatchEvent('catalog_controller_product_delete', array('product' => $product));
    $product->delete();
}

Então fiz alguns testes de desempenho, adicionei algumas chamadas de log para verificar o tempo gasto e o uso de memória para exclusão de 100 produtos.

Teste 1: walkmétodo

Substituí o código original colado acima por este código:

$collection = Mage::getResourceModel('catalog/product_collection')
                        ->addAttributeToSelect('entity_id')
                        ->addIdFilter($productIds)
                        ->walk('delete');

E meus resultados são os seguintes no meu servidor de desenvolvimento de baixa qualidade (média com base em 10 testes):

  • Código original: 19.97 segundos, 15.84MB usado
  • Código personalizado: 17.12 segundos, 15.45MB usado

Portanto, para a exclusão de 100 produtos, meu código personalizado é 3 segundos mais rápido e usa 0,4 MB a menos.

Teste 2: Usando o delete()método de coleta

Substituí o código original por este:

$collection = Mage::getResourceModel('catalog/product_collection')
                        ->addAttributeToSelect('entity_id')
                        ->addIdFilter($productIds)
                        ->delete();

E a mente soprada aqui estão os resultados:

  • Código original: 19.97 segundos, 15.84MB usado
  • Código personalizado: 1,24 segundos, 6,34 MB usados

Portanto, para a exclusão de 100 produtos, meu código personalizado é 18 segundos mais rápido e usa 9 MB a menos.

Conforme declarado nos comentários, parece que esse método não aciona os eventos Magento (após o carregamento, após a exclusão) nem o fluxo de índice / cache.

Questão

Portanto, minha pergunta é: existe uma razão pela qual a equipe principal do Magento não usou walk('delete')melhor o delete()método de coleta ou evento , em vez de carregar produtos em um loop (que todos sabemos que é uma prática muito, muito ruim)?

O objetivo principal é estar ciente desses pontos-chave no caso de um desenvolvimento de módulo: existem casos específicos em que não é possível usar o método walk/ collection delete()?

EDIT: o motivo definitivamente não é por causa do catalog_controller_product_deleteenvio do evento, pois o mesmo código pode ser encontrado em vários locais (verifique os massDeletemétodos) no núcleo do Magento. Usei o exemplo de produtos para destacar o desempenho, pois geralmente são as maiores entidades

Raphael na Digital Pianism
fonte
3
Eu acho que é por causa do evento. Mas eu concordo com você, é um estilo ruim, especialmente o uso getSingleton()como medida de desempenho, em vez do uso óbvio da coleção. Ah, e também é possível disparar o evento com uma coleção, mas não com o walk()atalho.
Fabian Schmengler
1
@fschmengler yeah Eu pensei no evento também, mas como eu disse na minha edição, ele está acontecendo em muitos lugares onde nenhum evento está sendo despachado.
Raphael no Digital Pianism
3
Não é surpreendente. delete()faz uma consulta DELETE em vez de carregar a coleção e excluir cada produto. Com isso, você realmente perderá os eventos.
Fabian Schmengler
5
@fschmengler Uma exclusão de coleção também exclui cada item individual, mas ignora a limpeza do cache e aciona alguns eventos do magento e indexador. É daí que a diferença deve vir.
Vinai
2
@Vinai você está certo. Wishful thinking do meu lado
Fabian Schmengler

Respostas:

4

Fiz alguns testes de desempenho, adicionei algumas chamadas de log para verificar o tempo gasto e o uso de memória para exclusão de 100 produtos

Nota lateral, mas você deve usar o Varien Profiler para isso!

meu código personalizado é 2 segundos mais rápido e usa 0,4 MB a menos

Embora eu não duvide que sua alteração melhore o desempenho, seria útil fornecer os resultados "anteriores" para comparar as melhorias.

existe uma razão pela qual a equipe principal do Magento não usou os walk('delete')produtos em vez de carregar um loop (o que todos sabemos que é uma prática muito muito ruim)?

Bem, sabemos de outras perguntas neste fórum o seguinte:

  • A base de código Magento se desenvolveu e evoluiu ao longo de muitos anos
  • Muitos desenvolvedores trabalham nisso
  • Os processos principais de fluxo de trabalho de desenvolvimento do Magento melhoraram drasticamente ao longo do tempo em que trabalharam na plataforma, acompanhando as melhores práticas e técnicas modernas até o ponto em que o Magento 2 agora exibe muitas práticas principais de design de aplicativos modernos

Portanto, sugiro que o exemplo que você encontrou seja provavelmente um dos muitos itens escondidos no código que foram escritos há muito tempo e / ou por um desenvolvedor menos experiente. Como grande parte do código principal (e do código da comunidade!), Ele teria sido testado em um pequeno conjunto de dados e não testado em batalha; portanto, o desempenho pode não ter sido monitorado de perto.

Sua melhoria é benéfica e está mais alinhada às melhores práticas do que o código original? Sim. Você, como desenvolvedor da comunidade Magento [1.x], no entanto, não tem capacidade de contribuir com melhorias sugeridas, como você faz com o Magento 2, então minha sugestão seria implementar isso em um módulo local se você precisar para desempenho em uma de suas lojas. , ou ignore-o se não estiver afetando você, mas você percebeu isso ao fazer alguma pesquisa.

Como uma atualização para a edição da sua pergunta, tenho certeza de que você sabe que o método walk em Varien_Data_Collection aceita um retorno de chamada arbitrário; portanto, você pode usá-lo para qualquer coisa que desejar. Para despachar o evento no exemplo original, você pode fazer isso com a função walk, bem como com a exclusão.

A única razão pela qual eu poderia imaginar que seria útil carregar o produto antes de excluí-lo pode ser que os observadores conectados a esse evento possam precisar de um conjunto de dados completo não disponível sem antes carregar o produto. Se for esse o caso, explicaria por que eles usam um singleton em vez de um modelo para minimizar pelo menos as despesas gerais do objeto.

Robbie Averill
fonte
Obrigado, adicionei os resultados antes e depois à publicação. Então você acha que não há uma razão específica além do fato de ser um código antigo?
Raphael no Digital Pianism
2
Esse seria o meu palpite, sim. Carregar o produto antes de excluí-lo não traria nenhum benefício além de disparar os eventos de carregamento, que não são relevantes para exclusão. Normalmente, você carrega um produto para obter seu conjunto completo de dados, o que pode ser necessário para um dos observadores conectados ao evento - se esse for o caso, explicaria por que eles estão usando um singleton em vez de modelo.
Robbie Averill
1
Ver a minha edição com mais testes, os resultados são ainda mais louco
Raphael em Digital pianismo
0

Meu pensamento é que eles estão fazendo isso para disparar o catalog_controller_product_deleteevento que está sendo usado pelo Mage_Tag.

catalog_product_delete_beforeou catalog_product_delete_aftersignificaria que isso era desnecessário, embora eu pensasse. Pergunto-me se esse evento específico também é usado para o log de ações do administrador.

Daniel Kenney
fonte
Pensei sobre isso também, mas isso definitivamente não é a razão, uma vez que também acontece para a massDelete()ação doCustomerController.php
Raphael em Digital pianismo
Ver a minha edição com mais testes, os resultados são ainda mais louco
Raphael em Digital pianismo
0

Acho que a exclusão em massa deve funcionar como excluir um único produto (totalmente carregado).

Para $collection->delete()a resposta já está dada. Se você não acionar deleter_before, delete_aftereu poderia quebrar algumas extensões e ignorar alguns observadores usados ​​no núcleo.

$collection->walk('delete')possivelmente funcionaria, mas ainda tem a desvantagem de que os dados do produto não estão completos. Isso também pode interromper observadores personalizados se eles confiarem em dados adicionais, por exemplo, objeto de item de estoque.

Eu acho que, se você alterar ->addAttributeToSelect('entity_id')a ->addAttributeToSelect('*')e adicione ->setFlag('require_stock_items', true)(para adicionar dados de estoque para produtos) não terá um desempenho melhor, em seguida, "loop-exclusão".

Parece um estilo ruim, mas acho que é certo para ambas as ações de exclusão em massa.

Também uso walk()e delete()para modelos personalizados, mas sei que não há observadores ou entity_idé suficiente. Apenas para mencionar, walk()funcionaria com todos os eventos usados ​​no núcleo, porque eles usam apenas $product->getId(), mas você não conhece observadores de terceiros.

sv3n
fonte