Pragnieniem każdego właściciela sklepu internetowego jest posiadanie serwisu, który działał szybko i sprawnie, a jak wiadomo czynniki te wprost wpływają na konwersje i zadowolenie klientów.

Platforma Adobe Magento 2 używa systemu cache'owania, który sprawia, że strona ładuje się szybciej, a serwer jest mniej obciążony. W magento 2 cache’owane są strony produktów, kategorii i strony cms. Strony te wyglądają tak samo dla każdego użytkownika odwiedzającego sklep, zaś ich zawartość po wygenerowaniu przez serwer jest przechowywana w pamięci. Kolejni odwiedzających otrzymują w ten sposób wcześniej zapisana zawartość (content nie jest generowany każdorazowo dla każdego usera). Mechanizmy Magento 2 automatycznie usuwają cache z pamięci, jeżeli zawartość się zmieni, np: przez edycję produktu lub kiedy produkt zostanie wyprzedany.

Opis sytuacji wyjściowej

Strony kategorii w Magento 2 są jednymi z najbardziej obciążających serwer, a jednocześnie bardzo często odwiedzanych stron. Cache tych stron odświeżane są za każdym razem, gdy dany produkt zostanie wyprzedany. Wynika to z faktu, że produkt ma się już więcej nie pokazywać lub był oznaczony jako niedostępny.

Zespół developerski Alekseon zauważył, że cache kategorii w Magento 2 jest odświeżany także w momencie, gdy zostanie wyprzedany produkt, który nie jest widoczny pojedynczo, a jedynie reprezentuje opcję produktu konfigurowalnego (np rozmiar). W takim przypadku usuwany jest cache wszystkich kategorii, w których znajduje się produkt konfigurowalny, a w przypadku wielu stron nie ma to wpływu na zawartość stron kategorii.

W praktyce okazuje się, że cache stron kategorii usuwany jest bardzo często, przez co klienci trafiają na stronę kategorii bez zbudowanego cache’a. Taka strona otwiera się wolniej oraz obciąża serwer w większym stopniu.

Co zmieniliśmy?

W sklepach naszych klientów zastosowaliśmy poprawkę, która w zauważalny sposób wpłynęła na poprawienie wydajności i szybkości ładowania stron. Przestaliśmy odświeżać cache kategorii w przypadku, gdy zmienił się jedynie status magazynowy produktu, który nie jest widoczny pojedynczo.

Kto najmocniej na tym skorzysta?

Najwięcej zyskają sklepy, które:

  • używają produktów niewidocznych pojedynczo jako opcji produktów konfigurowalnych
  • posiadają kategorie z dużą ilością produktów (duża liczba SKU)
  • posiadają produkty z niewielką ilością magazynową
  • mają duży ruch i częstą sprzedaż
  • często doświadczają stock-outów

Warto pamiętać przed wdrożeniem - efekt uboczny

Niestety w niektórych przypadkach nasza poprawka ma swoje konsekwencje:

- lista filtrów kategorii może być nieaktualne, jeżeli ich wartości są zależne od produktów niewidocznych pojedynczo, np kiedy mamy filtr dostępnych rozmiarów. (Nie wpływa to na wynik filtrowania)

- jeżeli wyświetlamy opcje produktów na kategorii, to mogą być one nieaktualne. (Na stronie produktu będą poprawne)

Jak zainstalować poprawkę

Nasza poprawka jest darmowa i dostępna na githubie.

https://github.com/Alekseon/magento2-category-cache-cleaner-improvement

Po zainstalowaniu należy ją włączyć w konfiguracji Magento:

stores -> Configuration -> Catalog -> Cache -> Refresh the Category Cache only if stock status changed for visible products

Uwagi techniczne

Nadpisaliśmy natywną klasę Magento 2 przez preferencję w di.xml:

Magento\CatalogInventory\Model\Indexer\Stock\CacheCleaner

Naszym celem była modyfikacja parametru $productIds przekazywanego do metody

getCategoryIdsByProductIds($productIds) w metodzie clean(...):
$productIdsForCategoryCacheClean = $this->getProductIdsForCategoryCacheClean($productStatusesBefore, $productStatusesAfter);
$categoryIds = $this->getCategoryIdsByProductIds($productIdsForCategoryCacheClean);

Natywna klasa CacheCleaner posiada poniższe metody prywatne które musieliśmy skopiować, ale nie zawierają żadnych naszych zmian:

private function getProductStockStatuses(array $productIds)
private function getProductIdsForCacheClean(array $productStatusesBefore, array $productStatusesAfter)
private function getCategoryIdsByProductIds(array $productIds)
private function getConnection()