Upload
max-pronko
View
4.012
Download
4
Embed Size (px)
Citation preview
REAL USE CASES OF PERFORMANCE OPTIMISATION MAGENTO 2
by Max Pronko
ABOUT ME
➤ former Magento core member
➤ CTO at TheIrishStore and GiftsDirect
➤ Founder of Pronko Consulting
➤ 7+ years of Magento Experience
➤ Magento 2 Blog
AGENDA
➤ Performance Optimisation
➤ eCommerce Website Data
➤ Real Improvements
➤ Varnish
➤ Q&A
www.maxpronko.com
PERFORMANCE
www.maxpronko.com
PERFORMANCERefers to the speed in which pages are
downloaded and displayed on the user’s browser
www.maxpronko.com
IS PERFORMANCE IMPORTANT?
➤ Customer Experience
➤ Conversion Rate
➤ Organic Search
www.maxpronko.com
PERFORMANCE OPTIMISATIONwww.maxpronko.com Image: Fast and Furious
STEPS OF PERFORMANCE TUNING
➤ Assess the problem
➤ Measure performance
➤ Identify part of the system critical for performance improvement
➤ Modify part of system to remove the bottleneck
➤ Measure the performance after modification
➤ If better, adopt it or revert
https://en.wikipedia.org/wiki/Performance_tuningwww.maxpronko.com
ECOMMERCE WEBSITE PROFILE
➤ Seasonal business
➤ 4k products
➤ 500 Configurable Products
➤ 2k Configurable Variations
➤ 1.5k Simple Products
➤ 500 Categories
➤ Average Order Value: $100
➤ 2-3 products per Order
www.maxpronko.com
BEFORE VS AFTER OPTIMISATION
Before
Category Page
Product Page
Shopping Cart
Checkout Index
0 500 1000 1500 2000
Magento 2 Application
www.maxpronko.com
BEFORE VS AFTER OPTIMISATION
After
Category Page
Product Page
Shopping Cart
Checkout Index
0 500 1000 1500 2000
Magento 2 Application
www.maxpronko.com
MAGENTO 2 APPLICATION
www.maxpronko.com
MAGENTO 2 APPLICATION
Optimisations
www.maxpronko.com
MAGENTO 2 APPLICATION
914 318ms ms
Before After
www.maxpronko.com
LET’S START
Image: mezzotint.dewww.maxpronko.com
DISABLE UNUSED CORE MODULES
www.maxpronko.com
FOR EXISTING MAGENTO 2 INSTALLATION
> bin/magento module:disable Magento_Downloadable
> bin/magento setup:upgrade
> bin/magento setup:di:compile
> bin/magento setup:static-content:deploy
www.maxpronko.com
COMPOSER.JSON{
"repositories": [ { "type": "composer", "url": "https://repo.magento.com/" } ], "require": {
"magento/composer": "~1.0.0", "magento/framework": "100.1.1", "magento/language-en_us": "100.1.0", "magento/magento-composer-installer": "*", "magento/magento2-base": “2.1.1”, ...
}, }
www.maxpronko.com
WHAT TO DISABLE?
➤ Extra Languages
➤ Offline shipping and payments
➤ Backup, Captcha, Persistent, Rss
➤ Msrp, Send Friend, Weee
➤ Multishipping, Checkout Agreements
➤ Product types?
➤ More… depends on project
www.maxpronko.com
DISABLE UNUSED FEATURES
www.maxpronko.com
HOW TO DISABLE FEATURES?
www.maxpronko.com
DISABLING FEATURES
➤ Observer
➤ Plugin
➤ Layout
www.maxpronko.com
MAGENTO REPORTS
➤ vendor/magento/module-reports/etc/frontend/events.xml <config> <event name="catalog_product_compare_remove_product"> <observer name="reports" instance="Magento\Reports\Observer\CatalogProductCompareClearObserver" /> </event> <event name="customer_login"> <observer name="reports" instance="Magento\Reports\Observer\CustomerLoginObserver" /> </event> <event name="customer_logout"> <observer name="reports" instance="Magento\Reports\Observer\CustomerLogoutObserver" /> </event> <event name="catalog_controller_product_view"> <observer name="reports" instance="Magento\Reports\Observer\CatalogProductViewObserver" /> </event> <event name="sendfriend_product"> <observer name="reports" instance="Magento\Reports\Observer\SendfriendProductObserver" /> </event> <event name="catalog_product_compare_add_product"> <observer name="reports" instance="Magento\Reports\Observer\CatalogProductCompareAddProductObserver" /> </event> <event name="catalog_product_compare_item_collection_clear"> <observer name="reports" instance="Magento\Reports\Observer\CatalogProductCompareClearObserver" /> </event> <event name="sales_quote_item_save_before"> <observer name="reports" instance="Magento\Reports\Observer\CheckoutCartAddProductObserver" /> </event> <event name="wishlist_add_product"> <observer name="reports" instance="Magento\Reports\Observer\WishlistAddProductObserver" /> </event> <event name="wishlist_share"> <observer name="reports" instance="Magento\Reports\Observer\WishlistShareObserver" /> </event> </config>
www.maxpronko.com
DISABLE OBSERVERS
➤ app/code/Pronko/Performance/etc/frontend/events.xml <config> <event name="catalog_product_compare_remove_product"> <observer name="reports" disabled="true" /> </event> <event name="catalog_controller_product_view"> <observer name="reports" disabled="true" /> </event> <event name="sendfriend_product"> <observer name="reports" disabled="true" /> </event> <event name="catalog_product_compare_add_product"> <observer name="reports" disabled="true" /> </event> <event name="catalog_product_compare_item_collection_clear"> <observer name="reports" disabled="true" /> </event> <event name="wishlist_add_product"> <observer name="reports" disabled="true" /> </event> <event name="wishlist_share"> <observer name="reports" disabled="true" /> </event> </config>
www.maxpronko.com
DISABLE VIA PLUGIN
➤ app/code/Pronko/Performance/etc/di.xml <config> <type name="Magento\Backend\Model\Menu"> <arguments> <argument name="logger" xsi:type="object">Psr\Log\NullLogger</argument> </arguments> </type> <type name="Magento\Framework\Cache\InvalidateLogger"> <plugin name="DisableCacheInvalidateLog" type="Pronko\Performance\Plugin\InvalidateLoggerPlugin" /> </type> </config>
www.maxpronko.com
REMOVE BLOCKS FROM LAYOUT
➤ Debug Hints
➤ Find non-used Blocks
➤ Remove it
www.maxpronko.com
REMOVE BLOCKS FROM LAYOUT
➤ default.xml <referenceBlock name="copyright" remove="true" /> <referenceBlock name="store_switcher" remove="true" /> <referenceBlock name="store_language" remove="true" /> <referenceBlock name="store.settings.language" remove="true" /> <referenceBlock name="translate" remove="true" />
➤ catalog_product_view.xml <referenceBlock name="product.price.tier" remove="true"/> <referenceBlock name="product.info.upsell" remove="true"/> <referenceBlock name="product.info.extrahint.qtyincrements" remove="true"/> <referenceBlock name="product.tooltip" remove="true"/> <referenceBlock name="product.info.mailto" remove="true"/>
www.maxpronko.com
HOW ABOUT BLOCK CACHE?
www.maxpronko.com
BLOCK CACHE
➤ Reduce number of Database queries
➤ Collection Load
➤ Template processing
➤ Eliminate Inline Translation
www.maxpronko.com
ABSTRACT BLOCK CLASS
public function toHtml(){ $html = $this->_loadCache(); if ($html === false) { $this->_beforeToHtml(); $html = $this->_toHtml(); $this->_saveCache($html); } $html = $this->_afterToHtml($html); return $html; }
www.maxpronko.com
ABSTRACT BLOCK CLASS
public function toHtml(){ $html = $this->_loadCache(); if ($html === false) { $this->_beforeToHtml(); $html = $this->_toHtml(); $this->_saveCache($html); } $html = $this->_afterToHtml($html); return $html; }
no more final :)
www.maxpronko.com
ABSTRACT BLOCK CLASS
protected function _loadCache(){ if ($this->getCacheLifetime() === null || !$this->_cacheState->isEnabled(self::CACHE_GROUP)) { return false; } $cacheKey = $this->getCacheKey(); $cacheData = $this->_cache->load($cacheKey); if ($cacheData) { $cacheData = str_replace(//placeholder, //value, $cacheData ); } return $cacheData; }
www.maxpronko.com
CATEGORY PAGE
www.maxpronko.com
CACHED BLOCKS
Navigation
Product
Price
www.maxpronko.com
BreadcrumbLayered
Navigation
Product
Product List
NON-CACHED BLOCKS
www.maxpronko.com
LETS USE BLOCK CACHE
www.maxpronko.com
BREADCRUMBS BLOCK
default.xml
<referenceBlock name="breadcrumbs"> <arguments> <argument name="cache_lifetime" xsi:type="string">604800</argument> </arguments> </referenceBlock>
www.maxpronko.com
LAYERED NAVIGATION BLOCK
➤ Layer State Key
➤ Category
➤ Customer Group
➤ Store
➤ Filters
➤ Currency
www.maxpronko.com
LAYERED NAVIGATION BLOCK
<referenceBlock name="catalog.leftnav"> <arguments> <argument name="cache_lifetime" xsi:type="number">604800</argument> </arguments> </referenceBlock>
catalog_category_view.xml
www.maxpronko.com
LAYERED NAVIGATION PLUGIN DECLARATION
di.xml<config> <type name="Magento\LayeredNavigation\Block\Navigation"> <plugin name="LayeredNavigationCacheKey" type="Pronko\Performance\Plugin\LayeredNavigationPlugin"/> </type> <type name="Magento\LayeredNavigation\Block\Navigation\Category"> <plugin name="LayeredNavigationCacheKey" type="Pronko\Performance\Plugin\LayeredNavigationPlugin"/> </type> </config>
www.maxpronko.com
LAYERED NAVIGATION PLUGIN
class LayeredNavigationPlugin{ public function afterGetCacheKey(Navigation $block, $cacheKey) { $cacheKey .= $this->priceCurrency->getCurrency()->getCurrencyCode(); $category = $block->getLayer()->getCurrentCategory(); if ($category) { $cacheKey .= $category->getId(); } $stateKey = $block->getLayer()->getStateKey(); if ($stateKey) { $cacheKey .= $stateKey; } $cacheKey .= $this->toolbarModel->getCurrentPage(); $cacheKey .= $this->toolbarModel->getDirection(); $cacheKey .= $this->toolbarModel->getLimit(); $cacheKey .= $this->toolbarModel->getMode(); $cacheKey .= $this->toolbarModel->getOrder(); return $cacheKey; }}
www.maxpronko.com
PRODUCT LIST<?php$_productCollection = $block->getLoadedProductCollection();$_helper = $this->helper('Magento\Catalog\Helper\Output');?> <?php if (!$_productCollection->count()): ?> <div class="message info empty"><div><?php /* @escapeNotVerified */ echo __('We can\'t find products matching the selection.') ?></div></div> <?php else: ?> <?php echo $block->getToolbarHtml() ?> <?php echo $block->getAdditionalHtml() ?> <?php if ($block->getMode() == 'grid') { $viewMode = 'grid'; $image = 'category_page_grid'; $showDescription = false; $templateType = \Magento\Catalog\Block\Product\ReviewRendererInterface::SHORT_VIEW; } else { $viewMode = 'list'; $image = 'category_page_list'; $showDescription = true; $templateType = \Magento\Catalog\Block\Product\ReviewRendererInterface::FULL_VIEW; } /** * Position for actions regarding image size changing in vde if needed */ $pos = $block->getPositioned(); ?> <div class="products wrapper <?php /* @escapeNotVerified */ echo $viewMode; ?> products-<?php /* @escapeNotVerified */ echo $viewMode; ?>"> <?php $iterator = 1; ?> <ol class="products list items product-items"> <?php /** @var $_product \Magento\Catalog\Model\Product */ ?> <?php foreach ($_productCollection as $_product): ?> <?php /* @escapeNotVerified */ echo($iterator++ == 1) ? '<li class="item product product-item">' : '</li><li class="item product product-item">' ?> <div class="product-item-info" data-container="product-grid"> <?php $productImage = $block->getImage($_product, $image); if ($pos != null) { $position = ' style="left:' . $productImage->getWidth() . 'px;' . 'top:' . $productImage->getHeight() . 'px;"'; } ?> <?php // Product Image ?> <a href="<?php /* @escapeNotVerified */ echo $_product->getProductUrl() ?>" class="product photo product-item-photo" tabindex="-1"> <?php echo $productImage->toHtml(); ?> </a> <div class="product details product-item-details"> <?php $_productNameStripped = $block->stripTags($_product->getName(), null, true); ?> <strong class="product name product-item-name"> <a class="product-item-link" href="<?php /* @escapeNotVerified */ echo $_product->getProductUrl() ?>"> <?php /* @escapeNotVerified */ echo $_helper->productAttribute($_product, $_product->getName(), 'name'); ?> </a> </strong> <?php echo $block->getReviewsSummaryHtml($_product, $templateType); ?> <?php /* @escapeNotVerified */ echo $block->getProductPrice($_product) ?> <?php echo $block->getProductDetailsHtml($_product); ?> <div class="product-item-inner"> <div class="product actions product-item-actions"<?php echo strpos($pos, $viewMode . '-actions') ? $position : ''; ?>> <div class="actions-primary"<?php echo strpos($pos, $viewMode . '-primary') ? $position : ''; ?>> <?php if ($_product->isSaleable()): ?> <?php $postParams = $block->getAddToCartPostParams($_product); ?> <form data-role="tocart-form" action="<?php /* @escapeNotVerified */ echo $postParams['action']; ?>" method="post"> <input type="hidden" name="product" value="<?php /* @escapeNotVerified */ echo $postParams['data']['product']; ?>"> <input type="hidden" name="<?php /* @escapeNotVerified */ echo Action::PARAM_NAME_URL_ENCODED; ?>" value="<?php /* @escapeNotVerified */ echo $postParams['data'][Action::PARAM_NAME_URL_ENCODED]; ?>"> <?php echo $block->getBlockHtml('formkey')?> <button type="submit" title="<?php echo $block->escapeHtml(__('Add to Cart')); ?>" class="action tocart primary"> <span><?php /* @escapeNotVerified */ echo __('Add to Cart') ?></span> </button> </form> <?php else: ?> <?php if ($_product->getIsSalable()): ?> <div class="stock available"><span><?php /* @escapeNotVerified */ echo __('In stock') ?></span></div> <?php else: ?> <div class="stock unavailable"><span><?php /* @escapeNotVerified */ echo __('Out of stock') ?></span></div> <?php endif; ?> <?php endif; ?> </div> <div data-role="add-to-links" class="actions-secondary"<?php echo strpos($pos, $viewMode . '-secondary') ? $position : ''; ?>> <?php if ($this->helper('Magento\Wishlist\Helper\Data')->isAllow()): ?> <a href="#" class="action towishlist" title="<?php echo $block->escapeHtml(__('Add to Wish List')); ?>" aria-label="<?php echo $block->escapeHtml(__('Add to Wish List')); ?>" data-post='<?php /* @escapeNotVerified */ echo $block->getAddToWishlistParams($_product); ?>' data-action="add-to-wishlist" role="button"> <span><?php /* @escapeNotVerified */ echo __('Add to Wish List') ?></span> </a> <?php endif; ?> <?php $compareHelper = $this->helper('Magento\Catalog\Helper\Product\Compare'); ?> <a href="#" class="action tocompare" title="<?php echo $block->escapeHtml(__('Add to Compare')); ?>" aria-label="<?php echo $block->escapeHtml(__('Add to Compare')); ?>" data-post='<?php /* @escapeNotVerified */ echo $compareHelper->getPostDataParams($_product); ?>' role="button"> <span><?php /* @escapeNotVerified */ echo __('Add to Compare') ?></span> </a> </div> </div>
PRODUCT LIST
➤ Copy list.phtml and move foreach from the template
➤ Create new ProductList block class
➤ Create new Product/Item block class and item.phtml template
➤ Don’t forget about unique cacheKeyInfo
➤ Layered navigation, filter, toolbar, currency
➤ Set cache_lifetime value for both block classes
www.maxpronko.com
NEW PRODUCT LIST TEMPLATE
<?php if (!$block->getLoadedProductCollection()->count()): ?> <div class="message info empty"><div><?php /* @escapeNotVerified */ echo __('We can\'t find products matching the selection.') ?></div></div> <?php else: ?> <?php echo $block->getToolbarHtml() ?> <?php echo $block->getAdditionalHtml() ?> <div class="products wrapper <?php /* @escapeNotVerified */ echo $block->getViewMode(); ?> products-<?php /* @escapeNotVerified */ echo $block->getViewMode(); ?>"> <ol class="products list items product-items" itemscope itemtype="http://schema.org/ItemList"> <?php echo $block->getProductsListHtml(); ?> </ol> </div> <?php echo $block->getToolbarHtml() ?> <?php if (!$block->isRedirectToCartEnabled()) : ?> <script type="text/x-magento-init"> { "[data-role=tocart-form], .form.map.checkout": { "catalogAddToCart": {} } } </script> <?php endif; ?><?php endif; ?>
app/design/Pronko/default/Magento_Catalog/templates/product/list.phtml
www.maxpronko.com
CATEGORY PAGE - CACHED
www.maxpronko.com
RELATED PRODUCTSProduct Page
www.maxpronko.com
PRODUCT PAGE
www.maxpronko.com
33 Attributes EAV Tables
ACTIONS
➤ Don’t use getAttributesUsedInProductListing() method
➤ Decrease number of attributes loaded
➤ Decrease number of EAV Tables usage on product page
➤ Enable block cache for each related product
ATTRIBUTES CONFIG
namespace Magento\Catalog\Model;class Config extends \Magento\Eav\Model\Config{ public function getProductAttributes(){ if (is_null($this->_productAttributes)) { $this->_productAttributes = array_keys($this->getAttributesUsedInProductListing()); } return $this->_productAttributes; }
}
ATTRIBUTES CONFIG
namespace Pronko\Performance\Model\Config;class Related extends \Magento\Catalog\Model\Config{ const XML_PATH_PRODUCT_RELATED_ATTRIBUTES = 'catalog/related_product_attributes'; public function getProductAttributes() { return array_keys($this->_scopeConfig->getValue(self::XML_PATH_PRODUCT_RELATED_ATTRIBUTES)); }}
CONFIGURATION
<virtualType name="PronkoPerformanceProductRelatedContext" type="Magento\Catalog\Block\Product\Context"> <arguments> <argument name="catalogConfig" xsi:type="object">Pronko\Performance\Model\Config\Related</argument> </arguments> </virtualType> <type name="Pronko\Performance\Block\ProductList\Related"> <arguments> <argument name="context" xsi:type="object">PronkoPerformanceProductRelatedContext</argument> </arguments> </type>
di.xml
<config> <default> <catalog> <related_product_attributes> <name /> <tax_class_id /> <small_image /> <image_label /> <special_price /> </related_product_attributes> </catalog> </default> </config>
config.xml
CATALOG_PRODUCT_VIEW.XML
<referenceBlock name="catalog.product.related" remove="true" />
<referenceContainer name="content.aside"> <block class="Pronko\Performance\Block\ProductList\Related" name="pronko.catalog.product.related" template="Magento_Catalog::product/list/items.phtml"> <arguments> <argument name="type" xsi:type="string">related</argument> </arguments> </block> </referenceContainer>
3RD PARTY MODULES
www.maxpronko.com
DOUBLE CHECK
➤ HTTP POST requests
➤ Collections usage
➤ Slow file/database operations
➤ Performance Tests on staging server
www.maxpronko.com
VARNISH
www.maxpronko.com
X-MAGENTO-VARY
➤ Customer Logged In
➤ Customer Group
➤ Customer Segment (EE only)
➤ Store
➤ Currency
➤ Tax Rates
www.maxpronko.com
X-MAGENTO-VARY
X-Magento-Vary = MD5(context data)
sub vcl_hash { if (req.http.cookie ~ "X-Magento-Vary=") { hash_data(regsub(req.http.cookie, "^.*?X-Magento-Vary=([^;]+);*.*$", "\1")); } if (req.http.host) { hash_data(req.http.host); } else { hash_data(server.ip); }}
www.maxpronko.com
VARNISH HASH DATA
➤ 1 Website, no customer groups, same content for guest and logged in ➤ GeoIP functionality
sub vcl_hash { if (req.url ~ "(\?|&)(utm_source|utm_medium|utm_campaign|utm_content|gclid)=") { set req.url = regsuball(req.url, "&(utm_source|utm_medium|utm_campaign|utm_content|gclid)=([^&]+)", ""); set req.url = regsuball(req.url, "\?(utm_source|utm_medium|utm_campaign|utm_content|gclid)=([^&]+)", "?"); set req.url = regsub(req.url, "\?&", "?"); set req.url = regsub(req.url, "\?$", ""); }
hash_data(req.http.X-Currency);}
www.maxpronko.com
VARNISH PERFORMANCE BEST PRACTICES
➤ Warmup pages after website content update
➤ Plan content changes ahead
➤ Run cron to clean up cache, reindex and warmup
www.maxpronko.com
WHAT ELSE?
www.maxpronko.com
MORE PERFORMANCE OPTIMISATIONS
➤ Remove Compare Products feature
➤ Minify and merge JavaScript and CSS
➤ Enable Async Order Grid
➤ Enable Async Transactional Emails
www.maxpronko.com
TRACKING AND MARKETING
➤ Asynchronous script loading for everything
➤ Google Tag Manager is good for async
➤ Avoid additional collection loads for Facebook, adWords, etc.
www.maxpronko.com
PERFORMANCE TOOLS
www.maxpronko.com
SUMMARY
➤ Disable functionality
➤ Use block cache
➤ Optimise/reduce database queries
➤ Remove unused blocks, observers
➤ Warmup all pages
➤ Never stop improving your website
www.maxpronko.com
THANK YOUmax_pronko
Q&A Time
www.maxpronko.com