Magento 1: Optymalizacje wydajności do usuwania encji

10

Obecnie próbuję ulepszyć kilka modułów pod względem wydajności.

Niektórzy z was mogą znać użycie walk()metody zbierania, która jest bardzo przydatna, aby uniknąć bezpośredniego przechodzenia między produktami.

Ponadto dzięki @Vinai można również użyć delete()metody zbierania .

Zauważyłem jednak, że rodzime pliki Magento 1 nie zawsze używają żadnej z tych metod usuwania.

Jednym z najgorszych kodów, jakie widziałem, jest massDelete()metoda, z app/code/core/Mage/Adminhtml/controllers/Catalog/ProductController.phpktórej produkty są ładowane w pętli przed usunięciem .

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

Przeprowadziłem więc testy wydajności, dodałem kilka wywołań logowania, aby sprawdzić czas i zużycie pamięci na usunięcie 100 produktów.

Test 1: walkmetoda

Zastąpiłem oryginalny kod wklejony powyżej tym kodem:

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

A moje wyniki są następujące na moim gównianym serwerze deweloperskim (średnia z 10 testów):

  • Kod oryginalny: 19,97 sekundy, użyto 15,84 MB
  • Kod niestandardowy: 17,12 sekund, użyto 15,45 MB

Tak więc przy usuwaniu 100 produktów mój niestandardowy kod jest 3 sekundy szybszy i zużywa 0,4 MB mniej.

Test 2: przy użyciu delete()metody zbierania

Zamieniłem oryginalny kod na ten:

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

A umysł jest zdumiony :

  • Kod oryginalny: 19,97 sekundy, użyto 15,84 MB
  • Kod niestandardowy: 1,24 sekundy, użyto 6,34 MB

Tak więc dla usunięcia 100 produktów mój niestandardowy kod jest 18 sekund szybszy i zużywa 9 MB mniej.

Jak stwierdzono w komentarzach, wygląda na to, że ta metoda nie wyzwala zdarzeń Magento (po załadowaniu, po usunięciu) ani opróżnieniu indeksu / pamięci podręcznej.

Pytanie

Moje pytanie brzmi zatem: czy istnieje powód, dla którego główny zespół Magento nie zastosował metody walk('delete')zbierania lub zdarzenia, delete()zamiast ładować produkty w pętli (co, jak wszyscy wiemy, jest bardzo złą praktyką)?

Głównym celem jest świadomość takich kluczowych punktów w przypadku rozwoju modułu: czy są jakieś szczególne przypadki, w których nie można użyć metody walk/ collection delete()?

EDYCJA: powód zdecydowanie nie jest spowodowany catalog_controller_product_deletewysłaniem zdarzenia, ponieważ ten sam kod można znaleźć w kilku miejscach (sprawdź massDeletemetody) w rdzeniu Magento. Użyłem przykładu produktów do podkreślenia wydajności, ponieważ zwykle są to największe podmioty

Raphael at Digital Pianism
źródło
3
Myślę, że to z powodu tego wydarzenia. Ale zgadzam się z tobą, to zły styl, zwłaszcza użycie getSingleton()jako miary wydajności, zamiast oczywistego użycia kolekcji. Aha, możliwe jest również wywołanie zdarzenia za pomocą kolekcji, ale nie za pomocą walk()skrótu.
Fabian Schmengler,
1
@ fschmengler tak też myślałem o tym wydarzeniu, ale jak powiedziałem w mojej edycji, dzieje się to w wielu miejscach, w których żadne wydarzenie nie jest wysyłane.
Raphael w Digital Pianism
3
Nie zaskakujący. delete()wykonuje zapytanie DELETE zamiast ładowania kolekcji i usuwania każdego produktu. Dzięki temu naprawdę przegrasz wydarzenia.
Fabian Schmengler,
5
@fschmengler Usuwanie kolekcji powoduje również usunięcie każdego pojedynczego elementu, ale pomija czyszczenie pamięci podręcznej i wyzwalanie niektórych zdarzeń magento i indeksujących. Stąd różnica musi pochodzić.
Vinai,
2
@Vinai masz rację. Życzenia z mojej strony
Fabian Schmengler,

Odpowiedzi:

4

Przeprowadziłem więc testy wydajności, dodałem kilka wywołań logowania, aby sprawdzić czas i zużycie pamięci na usunięcie 100 produktów

Uwaga dodatkowa, ale należy do tego skorzystać z programu Varien Profiler!

mój niestandardowy kod jest 2 sekundy szybszy i zużywa 0,4 MB mniej

Chociaż nie mam wątpliwości, że Twoja zmiana poprawiłaby wydajność, przydatne byłoby podanie wyników „przed” w celu porównania ulepszeń.

czy istnieje powód, dla którego główny zespół Magento nie używał walk('delete')zamiast ładowania produktów w pętli (co wszyscy wiemy, to bardzo zła praktyka)?

Z innych pytań na tym forum wiemy, co następuje:

  • Baza kodów Magento rozwijała się i ewoluowała przez wiele lat
  • Pracowało nad nim wielu programistów
  • Procesy rozwoju podstawowych rdzeni Magento uległy znacznej poprawie w czasie, gdy pracowali na platformie, nadążając za nowoczesnymi najlepszymi praktykami i technikami do tego stopnia, że ​​Magento 2 wykazuje obecnie wiele wiodących nowoczesnych praktyk projektowania aplikacji

Sugerowałbym więc, że przykład, który znalazłeś, jest prawdopodobnie jednym z potencjalnie wielu klejnotów ukrytych w kodzie, które zostały napisane dawno temu i / lub przez mniej doświadczonego programistę. Podobnie jak większość kodu podstawowego (i kodu społeczności!) Zostałby przetestowany na małym zestawie danych, a nie przetestowany w walce, więc wydajność może nie być ściśle monitorowana.

Czy Twoje ulepszenie jest korzystne i czy jest bardziej dostosowane do najlepszych praktyk niż oryginalny kod? Tak. Jako programista społeczności Magento [1.x] nie masz jednak możliwości wprowadzania sugerowanych ulepszeń, tak jak w przypadku Magento 2, więc sugeruję, aby zaimplementować to w module lokalnym, jeśli wymaga tego wydajność w jednym ze sklepów lub zignoruj ​​go, jeśli nie wpływa to na ciebie, ale zauważyłeś to podczas badań.

Jako aktualizacja edycji pytania, jestem pewien, że wiesz, że metoda walk w Varien_Data_Collection akceptuje dowolne wywołanie zwrotne, więc możesz użyć go do wszystkiego, co chcesz. Aby wywołać zdarzenie w oryginalnym przykładzie, możesz to zrobić za pomocą funkcji marszu, a także usunięcia.

Jedynym powodem, dla którego mogłem sobie wyobrazić, że załadowanie produktu przed jego usunięciem byłoby przydatne, może być to, że obserwatorzy dołączeni do tego wydarzenia mogą potrzebować pełnego zestawu danych niedostępnego bez uprzedniego załadowania produktu. W takim przypadku wyjaśniłoby to, dlaczego używają singletonu, a nie modelu, aby przynajmniej zminimalizować koszty ogólne obiektu.

Robbie Averill
źródło
Dzięki dodałem do postu wyniki przed i po. Więc uważasz, że nie ma żadnego konkretnego powodu poza tym, że jest to stary kod?
Raphael w Digital Pianism
2
Tak sądzę, tak. Ładowanie produktu przed jego usunięciem nie przyniosłoby żadnej innej korzyści niż odpalenie zdarzeń ładowania, które nie są istotne dla usunięcia. Zwykle ładujesz produkt, aby uzyskać pełny zestaw danych, który może być wymagany dla jednego z obserwatorów dołączonych do wydarzenia - w takim przypadku wyjaśniłoby to, dlaczego używają singletonu zamiast modelu.
Robbie Averill,
1
Zobacz moją edycję z większą ilością testów, wyniki są jeszcze bardziej szalone
Raphael z Digital Pianism,
0

Myślę, że robią to, aby odpalić catalog_controller_product_deletezdarzenie, którego używa Mage_Tag.

catalog_product_delete_beforelub catalog_product_delete_afteroznaczałoby to, że nie było to konieczne, choć myślałem. Zastanawiam się, czy to konkretne zdarzenie jest również używane do rejestrowania działań administratora.

Daniel Kenney
źródło
Myśl o tym, że zbyt ale na pewno nie powód, jak zdarza się również do massDelete()działaniaCustomerController.php
Raphael na cyfrowe pianistyki
Zobacz moją edycję z większą ilością testów, wyniki są jeszcze bardziej szalone
Raphael z Digital Pianism,
0

Myślę, że masowe usuwanie powinno działać jak usuwanie pojedynczego (w pełni załadowanego) produktu.

Ponieważ $collection->delete()odpowiedź jest już podana. Jeśli nie uruchomisz deleter_before, delete_afterprawdopodobnie mógłbym złamać niektóre rozszerzenia i ominąć niektórych obserwatorów używanych w jądrze.

$collection->walk('delete')prawdopodobnie działałoby, ale nadal ma tę wadę, że dane produktu nie są kompletne. Może to również uszkodzić niestandardowych obserwatorów, jeśli opierają się na dodatkowych danych, na przykład na obiekcie przedmiotu magazynowego.

Chyba, jeśli zmieni ->addAttributeToSelect('entity_id')się ->addAttributeToSelect('*')i dodaj ->setFlag('require_stock_items', true)(aby dodać dane do produktów seryjnych) nie wykona lepiej niż „pętla-Delete”.

Wygląda na zły styl, ale myślę, że jest odpowiedni zarówno do akcji masowego usuwania.

Używam walk()i delete()do niestandardowych modeli, ale wiem, że nie ma obserwatorów lub entity_idjest wystarczający. Wystarczy wspomnieć, walk()że działałby ze wszystkimi zdarzeniami używanymi w rdzeniu, ponieważ one wykorzystują tylko $product->getId(), ale nie wiesz o obserwatorach zewnętrznych.

sv3n
źródło