Magento 1: optimizaciones de rendimiento para eliminar entidades

10

Actualmente estoy tratando de mejorar un par de módulos con respecto al rendimiento.

Algunos de ustedes pueden conocer el uso del walk()método en la colección, que es muy útil para evitar pasar directamente por los productos.

Además de eso y gracias a @Vinai, también se puede utilizar el delete()método de recopilación .

Pero noté que los archivos nativos de Magento 1 no siempre usan ninguno de esos métodos para la eliminación.

Uno de los peores códigos que he visto es el massDelete()método desde app/code/core/Mage/Adminhtml/controllers/Catalog/ProductController.phpdonde los productos se cargan en un bucle antes de la eliminación .

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

Así que realicé algunas pruebas de rendimiento, agregué algunas llamadas de registro para verificar el tiempo necesario y el uso de memoria para la eliminación de 100 productos.

Prueba 1: walkmétodo

Reemplacé el código original pegado anteriormente con este código:

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

Y mis resultados son los siguientes en mi servidor de desarrollo de mierda (promedio basado en 10 pruebas):

  • Código original: 19,97 segundos, 15,84 MB utilizados
  • Código personalizado: 17,12 segundos, 15,45 MB utilizados

Entonces, para la eliminación de 100 productos, mi código personalizado es 3 segundos más rápido y usa 0.4MB menos.

Prueba 2: uso del delete()método de recolección

Reemplacé el código original con este:

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

Y alucinante aquí están los resultados:

  • Código original: 19,97 segundos, 15,84 MB utilizados
  • Código personalizado: 1,24 segundos, 6,34 MB utilizados

Entonces, para la eliminación de 100 productos, mi código personalizado es 18 segundos más rápido y usa 9 MB menos.

Como se indicó en los comentarios, parece que este método no activa los eventos de Magento (después de la carga, después de eliminar) ni el vaciado del índice / caché.

Pregunta

Entonces mi pregunta es: ¿hay alguna razón por la cual el equipo central de Magento no utilizó el walk('delete')evento o mejor el delete()método de recolección en lugar de cargar los productos en un bucle (lo cual todos sabemos es una muy mala práctica)?

El objetivo principal es tener en cuenta estos puntos clave en el caso del desarrollo de un módulo: ¿hay casos particulares en los que no se puede usar el método walk/ colección delete()?

EDITAR: la razón definitivamente no se debe al catalog_controller_product_deleteenvío del evento, ya que el mismo código se puede encontrar en varios lugares (verifique los massDeletemétodos) en el núcleo de Magento. He utilizado el ejemplo de productos para resaltar el rendimiento, ya que generalmente son las entidades más grandes.

Raphael en Digital Pianism
fuente
3
Supongo que es por el evento. Pero estoy de acuerdo con usted, es un mal estilo, especialmente el uso getSingleton()como medida de rendimiento, en lugar del uso obvio de la colección. Ah, y también es posible activar el evento con una colección, pero no con el walk()acceso directo.
Fabian Schmengler
1
@fschmengler, sí, también pensé en el evento, pero como dije en mi edición, está sucediendo en muchos lugares donde no se envía ningún evento.
Raphael en Digital Pianism
3
No es sorprendente. delete()realiza una consulta DELETE en lugar de cargar la colección y eliminar cada producto. Con eso realmente perderás los eventos.
Fabian Schmengler
55
@fschmengler Una eliminación de colección también hace una eliminación para cada elemento individual, pero evita borrar el caché y desencadenar algunos eventos de magento e indexador. De ahí debe venir la diferencia.
Vinai
2
@ Viñai tienes razón. Ilusiones de mi parte
Fabian Schmengler

Respuestas:

4

Así que realicé algunas pruebas de rendimiento, agregué algunas llamadas de registro para verificar el tiempo necesario y el uso de memoria para la eliminación de 100 productos

Nota al margen, ¡pero debe considerar usar el Varien Profiler para esto!

mi código personalizado es 2 segundos más rápido y usa 0.4 MB menos

Si bien no dudo que su cambio mejoraría el rendimiento, sería útil proporcionar los resultados "anteriores" para comparar las mejoras.

¿Hay alguna razón por la cual el equipo central de Magento no utilizó los walk('delete')productos en lugar de cargarlos en un bucle (lo cual todos sabemos es una muy, muy mala práctica)?

Bueno, sabemos por otras preguntas en este foro lo siguiente:

  • La base de código de Magento se ha desarrollado y evolucionado durante muchos años.
  • Ha tenido muchos desarrolladores trabajando en ello
  • Los procesos de flujo de trabajo de desarrollo principal de Magento han mejorado dramáticamente con el tiempo que han trabajado en la plataforma, poniéndose al día con las mejores prácticas y técnicas modernas hasta el punto en que Magento 2 ahora exhibe muchas prácticas modernas de diseño de aplicaciones líderes

Por lo tanto, sugeriría que el ejemplo que ha encontrado es probablemente una de las muchas gemas potencialmente ocultas en el código que se escribió hace mucho tiempo y / o por un desarrollador menos experimentado. Al igual que gran parte del código central (¡y el código de la comunidad!), Habría sido probado en un pequeño conjunto de datos y no probado en batalla, por lo que es posible que el rendimiento no haya sido monitoreado de cerca.

¿Es su mejora beneficiosa y está más estrechamente alineada con las mejores prácticas que el código original? Si. Sin embargo, usted como desarrollador de Magento [1.x] de la comunidad no tiene la capacidad de contribuir con las mejoras sugeridas, como lo hace con Magento 2, por lo que mi sugerencia sería implementar esto en un módulo local si lo requiere para el rendimiento en una de sus tiendas , o ignórelo si no lo está afectando pero lo ha notado mientras investigaba.

Como actualización de la edición de su pregunta, estoy seguro de que sabe que el método de caminata en Varien_Data_Collection acepta una devolución de llamada arbitraria, por lo que podrá usarlo para cualquier cosa que desee. Para enviar el evento en el ejemplo original, puede hacerlo con la función de caminar, así como con la eliminación.

La única razón por la que podría imaginar que sería útil cargar el producto antes de eliminarlo es que los observadores adjuntos a ese evento pueden necesitar un conjunto de datos completo no disponible sin cargar primero el producto. Si ese es el caso, explicaría por qué usan un singleton en lugar de un modelo para minimizar al menos los gastos generales del objeto.

Robbie Averill
fuente
Gracias. Agregué los resultados del antes y el después a la publicación. Entonces, ¿crees que no hay una razón particular aparte del hecho de que es un código antiguo?
Raphael en Digital Pianism
2
Esa sería mi suposición, sí. Cargar el producto antes de eliminarlo no serviría para nada más que disparar los eventos de carga, que no son relevantes para la eliminación. Normalmente carga un producto para obtener su conjunto de datos completo, que puede ser necesario para uno de los observadores adjuntos al evento; si ese es el caso, explicaría por qué están usando un singleton en lugar de un modelo.
Robbie Averill
1
Vea mi edición con más pruebas, los resultados son aún más locos
Raphael en Digital Pianism
0

catalog_controller_product_deleteCreo que lo están haciendo para disparar el evento que está siendo utilizado por Mage_Tag.

catalog_product_delete_beforeo catalog_product_delete_aftersignificaría que esto era innecesario, aunque creo. Me pregunto si este evento en particular también se usa para el registro de acciones de administrador.

Daniel Kenney
fuente
Pensé en eso también, pero definitivamente esa no es la razón, ya que también sucede por la massDelete()acción deCustomerController.php
Raphael en Digital Pianism
Vea mi edición con más pruebas, los resultados son aún más locos
Raphael en Digital Pianism
0

Creo que la eliminación masiva debería funcionar como eliminar un solo producto (completamente cargado).

Porque $collection->delete()la respuesta ya está dada. Si no se dispara deleter_before, delete_afterpodría romper algunas extensiones y omitir algunos observadores utilizados en el núcleo.

$collection->walk('delete')posiblemente funcionaría, pero aún tiene la desventaja de que los datos del producto no están completos. Esto también puede romper observadores personalizados si confían en datos adicionales, por ejemplo, un objeto de stock.

Creo que, si cambia ->addAttributeToSelect('entity_id')a ->addAttributeToSelect('*')y añadir ->setFlag('require_stock_items', true)(añadir los datos de saldos a los productos) no se obtienen mejores resultados a continuación "bucle-delete".

Parece un mal estilo, pero creo que es adecuado para ambas acciones de eliminación masiva.

Yo uso walk()y delete()para modelos personalizados también, pero sé que no hay observadores o entity_ides suficiente. Solo por mencionar, walk()funcionaría con todos los eventos utilizados en el núcleo, porque solo se usan $product->getId(), pero no se sabe de observadores de terceros.

sv3n
fuente