Magento 1: Optimisations des performances pour supprimer des entités

10

J'essaie actuellement d'améliorer quelques modules concernant les performances.

Certains d'entre vous connaissent peut-être l' utilisation de la walk()méthode de collecte, ce qui est très utile pour éviter de parcourir directement les produits.

En plus de cela et grâce à @Vinai, on peut également utiliser la delete()méthode de collecte .

Mais j'ai remarqué que les fichiers natifs de Magento 1 n'utilisent pas toujours l'une de ces méthodes pour la suppression.

L'un des pires codes que j'ai vus est la massDelete()méthode à partir de app/code/core/Mage/Adminhtml/controllers/Catalog/ProductController.phplaquelle les produits sont chargés dans une boucle avant la suppression .

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

J'ai donc fait quelques tests de performances, ajouté quelques appels de journalisation pour vérifier le temps pris et l'utilisation de la mémoire pour la suppression de 100 produits.

Test 1: walkméthode

J'ai remplacé le code d'origine collé ci-dessus par ce code:

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

Et mes résultats sont les suivants sur mon serveur de développement merdique (moyenne basée sur 10 tests):

  • Code d'origine: 19,97 secondes, 15,84 Mo utilisés
  • Code personnalisé: 17,12 secondes, 15,45 Mo utilisés

Ainsi, pour la suppression de 100 produits, mon code personnalisé est 3 secondes plus rapide et utilise 0,4 Mo de moins.

Test 2: utilisation de la delete()méthode de collecte

J'ai remplacé le code d'origine par celui-ci:

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

Et les résultats sont époustouflants :

  • Code d'origine: 19,97 secondes, 15,84 Mo utilisés
  • Code personnalisé: 1,24 seconde, 6,34 Mo utilisés

Donc, pour la suppression de 100 produits, mon code personnalisé est 18 secondes plus rapide et utilise 9 Mo de moins.

Comme indiqué dans les commentaires, il semble que cette méthode ne déclenche pas les événements Magento (après chargement, après suppression) ni le vidage d'index / cache.

Question

Ma question est donc la suivante: y a-t-il une raison pour laquelle l'équipe principale de Magento n'a pas mieux utilisé la méthode de walk('delete')collecte de l'événement ou au delete()lieu de charger les produits en boucle (ce que nous savons tous est une très très mauvaise pratique)?

L'objectif principal est de connaître ces points clés en cas de développement d'un module: y a-t-il des cas particuliers où l'on ne peut pas utiliser la méthode walk/ collection delete()?

EDIT: la raison n'est certainement pas à cause de l' catalog_controller_product_deleteévénement envoyé car le même code peut être trouvé à plusieurs endroits (vérifiez les massDeleteméthodes) dans le noyau Magento. J'ai utilisé l'exemple des produits pour mettre en évidence les performances car ce sont généralement les plus grandes entités

Raphael chez Digital Pianism
la source
3
Je suppose que c'est à cause de l'événement. Mais je suis d'accord avec vous, c'est un mauvais style, en particulier l'utilisation de getSingleton()comme mesure de performance, au lieu de l'utilisation évidente de la collection. Oh, et il est également possible de déclencher l'événement avec une collection, mais pas avec le walk()raccourci.
Fabian Schmengler
1
@fschmengler oui, j'ai aussi pensé à l'événement, mais comme je l'ai dit dans mon montage, cela se produit dans de nombreux endroits où aucun événement n'est envoyé.
Raphael au Digital Pianism
3
Pas étonnant. delete()effectue une requête DELETE au lieu de charger la collection et de supprimer chaque produit. Avec celui-là, vous perdrez vraiment les événements.
Fabian Schmengler
5
@fschmengler Une suppression de collection effectue également une suppression pour chaque élément individuel, mais elle contourne la suppression du cache et le déclenchement de certains événements magento et indexeur. C'est de là que doit provenir la différence.
Vinai
2
@Vinai vous avez raison. Un vœu pieux de mon côté
Fabian Schmengler

Réponses:

4

J'ai donc fait quelques tests de performances, ajouté quelques appels de journalisation pour vérifier le temps pris et l'utilisation de la mémoire pour la suppression de 100 produits

Remarque, mais vous devriez envisager d'utiliser le Varien Profiler pour cela!

mon code personnalisé est 2 secondes plus rapide et utilise 0,4 Mo de moins

Bien que je ne doute pas que votre modification améliorerait les performances, il serait utile de fournir les résultats "avant" pour comparer les améliorations.

Y a-t-il une raison pour laquelle l'équipe principale de Magento n'a pas utilisé le walk('delete')au lieu de charger des produits dans une boucle (ce que nous savons tous que c'est une très très mauvaise pratique)?

Eh bien, nous savons par d'autres questions sur ce forum ce qui suit:

  • La base de code Magento s'est développée et a évolué au fil des années
  • Beaucoup de développeurs y ont travaillé
  • Les processus de flux de travail de développement de base de Magento se sont considérablement améliorés au fil du temps qu'ils ont travaillé sur la plate-forme, rattrapant les meilleures pratiques et techniques modernes au point où Magento 2 présente désormais de nombreuses pratiques de conception d'applications modernes de premier plan.

Je dirais donc que l'exemple que vous avez trouvé est probablement l'un des nombreux joyaux potentiellement cachés dans le code qui ont été écrits il y a longtemps et / ou par un développeur moins expérimenté. Comme une grande partie du code de base (et du code de la communauté!), Il aurait été testé sur un petit ensemble de données et non testé en combat, donc les performances peuvent ne pas avoir été étroitement surveillées.

Votre amélioration est-elle bénéfique et plus conforme aux meilleures pratiques que le code d'origine? Oui. En tant que développeur communautaire Magento [1.x], vous n'avez cependant pas la possibilité de contribuer aux améliorations suggérées, comme vous le faites avec Magento 2, donc ma suggestion serait de l'implémenter dans un module local si vous en avez besoin pour les performances dans l'un de vos magasins. , ou ignorez-le si cela ne vous affecte pas, mais vous l'avez remarqué en faisant des recherches.

En tant que mise à jour de la modification de votre question, je suis sûr que vous savez que la méthode de marche de Varien_Data_Collection accepte un rappel arbitraire, vous seriez donc libre de l'utiliser pour tout ce que vous vouliez probablement. Pour distribuer l'événement dans l'exemple d'origine, vous pouvez le faire avec la fonction de marche, ainsi que la suppression.

La seule raison pour laquelle je pourrais imaginer que le chargement du produit avant de le supprimer serait utile peut être que les observateurs attachés à cet événement peuvent avoir besoin d'un ensemble de données complet non disponible sans charger le produit au préalable. Si tel est le cas, cela expliquerait pourquoi ils utilisent un singleton plutôt qu'un modèle pour au moins minimiser les frais généraux des objets.

Robbie Averill
la source
Merci d'avoir ajouté les résultats avant et après au message. Vous pensez donc qu'il n'y a pas de raison particulière à part le fait qu'il s'agit d'un ancien code?
Raphael au Digital Pianism
2
Ce serait ma supposition, oui. Le chargement du produit avant sa suppression ne servirait à rien d'autre que le déclenchement des événements de chargement, qui ne sont pas pertinents pour la suppression. Normalement, vous chargez un produit pour obtenir son ensemble de données complet, ce qui peut être requis pour l'un des observateurs attachés à l'événement - si tel est le cas, cela expliquerait pourquoi ils utilisent un singleton au lieu du modèle.
Robbie Averill
1
Voir mon montage avec plus de tests, les résultats sont encore plus fous
Raphael au Digital Pianism
0

Je pense qu'ils le font pour déclencher l' catalog_controller_product_deleteévénement qui est utilisé par Mage_Tag.

catalog_product_delete_beforeou catalog_product_delete_aftersignifierait que ce n'était pas nécessaire, même si je pense. Je me demande si cet événement particulier est également utilisé pour la journalisation des actions d'administration.

Daniel Kenney
la source
J'y ai pensé aussi, mais ce n'est certainement pas la raison car cela se produit également pour l' massDelete()action deCustomerController.php
Raphael au Digital Pianism
Voir mon montage avec plus de tests, les résultats sont encore plus fous
Raphael au Digital Pianism
0

Je pense que la suppression en masse devrait fonctionner comme supprimer un seul produit (entièrement chargé).

Car $collection->delete()la réponse est déjà donnée. Si vous ne déclenchez pas deleter_before, delete_afterje pourrais éventuellement casser certaines extensions et contourner certains observateurs utilisés dans le noyau.

$collection->walk('delete')pourrait éventuellement fonctionner, mais présente toujours l'inconvénient que les données produit ne sont pas complètes. Cela peut également interrompre les observateurs personnalisés s'ils s'appuient sur des données supplémentaires, par exemple un objet de stock.

Je suppose que , si vous changez ->addAttributeToSelect('entity_id')de ->addAttributeToSelect('*')et ajouter ->setFlag('require_stock_items', true)(pour ajouter des données de stocks aux produits) , il ne fonctionnera pas mieux que « boucle de suppression ».

On dirait un mauvais style, mais je pense que c'est bon pour les deux actions de suppression en masse.

J'utilise aussi walk()et delete()pour des modèles personnalisés, mais je sais qu'il n'y a pas d'observateurs ou entity_idc'est suffisant. Juste pour mentionner, walk()fonctionnerait avec tous les événements utilisés dans le noyau, car ils n'utilisent que $product->getId(), mais vous ne connaissez pas les observateurs tiers.

sv3n
la source