magento performance issues? deep dive - l fowell.pdfa 1 second delay can equate to.. • 16%...

Post on 19-Mar-2020

2 Views

Category:

Documents

1 Downloads

Preview:

Click to see full reader

TRANSCRIPT

MAGENTO PERFORMANCE ISSUES?

FIX them, don’t MASK them

INTRODUCTION

Luke FowellHead of eCommerce

BOURNEMOUTH

Bournemouth is the fastest

growing digital economy in UK

WHY IS MAGENTO OFTEN SLOW?

• It’s a heavy eCommerce platform

• Resource hungry

• Wealth of features makes for a

large codebase

• The EAV model is expensive

• Flexibility #1, Performance #2

WHY DOES IT MATTER IF MY STORE IS SLOW?

• Lower revenue

• Lower conversion rate

• Increase in bounce rate

• Increase in basket abandonment

• Decrease in customer retention

• Negative impact on reputation

• Negative impact on search rankings

A 1 SECOND DELAY CAN EQUATE TO..

• 16% decrease in customer

satisfaction

• 7% loss in conversion

• This means that a store with

a turnover of £1000 per day

would lose £25,000 per year

• This is equivalent to Amazon

losing $1.6 billion in sales

each year

STATS ON PERFORMANCE

57% of visitors abandon a site after waiting

more than 3 seconds for a page to load

[Source: http://www.marketingprofs.com/]

QUICK WINS YOU GET FOR FREE

• Enable Caching

• Flat Product & Category Enabled

• Compilation

• Merge JS & CSS

• Database Log Cleaning

• Disable Debug Logging

SOME THOUGHTS ON ENVIRONMENT

• Less preference on software choice

• Software set up properly

• Community advice & guides

• Performance “buzzwords”

• A harmonious system, software working

together

SO WHERE IS THE PROBLEM?

• We have found approximately 75% of performance issues are related to code

• Often Introduced by a developer during implementation

• Usually relate to some kind of database interaction

Application Level

LOOPS / COLLECTIONS

This is one of the most common culprits of performance

bottlenecks in Magento

Doing anything memory intensive inside a loop is

undoubtedly going to be amplified

Therefore you need to be meticulous about keeping code

lean when inside a loop

LOOPS / COLLECTIONS – EXAMPLE 1

foreach($this->getProductCollection() as $bestseller)

{

$_product = Mage::getModel(‘catalog/product’)->load($bestseller->getId());

echo $_product->getSubtitle();

}

Assuming getProductCollection()returns 12 products

This will result in 13 unnecessary database calls

LOOPS / COLLECTIONS – EXAMPLE 1

$_bestseller_products = $this->getProductCollection();

$_bestseller_products->addAttributeToSelect(‘subtitle’);

foreach($_bestseller_products as $bestseller)

{

echo $bestseller->getSubtitle();

}

This time it only makes 1 database call

LOOPS / COLLECTIONS– EXAMPLE 1

LOOPS / COLLECTIONS – EXAMPLE 2

Model.

class Wcm_Demo_Model_Message extends Mage_Core_Model_Abstract

{

public function markAsSeen(){

$this->getResource()->updateViewedStatus($this->getMessageId(), 1);

return $this;

}

}

Resource Model.

class Wcm_Demo_Model_Resource_Message extends Mage_Core_Model_Resource_Db_Abstract

{

public function updateViewedStatus($messageId, $status){

$data = array(‘message_id’ => $messageId, ‘status’ => $status);

$adapter->insert($this->getMainTable(), $data);

}

}

LOOPS / COLLECTIONS – EXAMPLE 2

foreach($this->getMessageCollection() as $message)

{

echo $message->markAsSeen()->toHtml();

}

This code will mark each message as “seen” when it outputs it to the page

Assuming there are 10 messages, that will result in 11 database queries

LOOPS / COLLECTIONS – EXAMPLE 2

Model.

class Wcm_Demo_Model_Message extends Mage_Core_Model_Abstract

{

public function markAsSeen($items = null){

if(is_array($items))

{

$this->getResource()->updateViewedStatuses($items, 1);

}

else

{

$this->getResource()->updateViewedStatus($this->getMessageId(), 1);

}

return $this;

}

}

LOOPS / COLLECTIONS – EXAMPLE 2

Resource Model.

class Wcm_Demo_Model_Resource_Message extends Mage_Core_Model_Resource_Db_Abstract

{

public function updateViewedStatus($messageId, $status){

$data = array(‘message_id’ => $messageId, ‘status’ => $status);

$adapter->insert($this->getMainTable(), $data);

}

public function updateViewedStatuses($messageIds, $status){

$data = array();

foreach($messageIds as $messageId){

$data[] = array(‘message_id’ => $messageId, ‘status’ => $status);

}

$adapter->insertMultiple($this->getMainTable(), $data);

}

}

LOOPS / COLLECTIONS – EXAMPLE 2

$seen = array();

foreach($this->getMessageCollection() as $message)

{

echo $message->toHtml();

$seen[] = $message->getMessageId();

}

Mage::getModel(‘demo/message’)->markAsSeen($seen);

Inserting the data has been aggregated into a single database

round-trip

$products = Mage::getModel(‘catalog/product’)->getCollection()

->setStoreId(Mage::app()->getStore()->getId())

->addFieldToFilter(‘is_featured’, 1);

Mage::getSingleton(‘catalog/product_status’)

->addVisibleFilterToCollection($products);

Mage::getSingleton(‘catalog/product_visibility’)

->addVisibleInCatalogFilterToCollection($products);

return $products->getFirstItem()->getSku();

LOOPS / COLLECTIONS – EXAMPLE 3

Loading full product collection to return a single item

LOOPS / COLLECTIONS – EXAMPLE 3

$products = Mage::getModel(‘catalog/product’)->getCollection()

->setStoreId(Mage::app()->getStore()->getId())

->addFieldToFilter(‘is_featured’, 1)

->setPageSize(1);

Mage::getSingleton(‘catalog/product_status’)

->addVisibleFilterToCollection($products);

Mage::getSingleton(‘catalog/product_visibility’)

->addVisibleInCatalogFilterToCollection($products);

return $products->getFirstItem()->getSku();

Applying a limitation will improve code performance

LOOPS / COLLECTIONS – EXAMPLE 3

Approximate performance improvement based on 500 products

38.4% decrease in load time

USE OF THE LOAD METHOD - EXAMPLE 1

<?php $productId = Mage::app()->getRequest()->getParam('id'); ?>

<div class="product">

<a href="<?php echo Mage::getModel('catalog/product')->load($productId)->getUrl(); ?>">

<div class="rollover">

<div class="rollover-container">

<h5 class="txt-white">

<?php echo Mage::getModel('catalog/product')->load($productId)->getName(); ?>

</h5>

<hr class="bg-white">

<span class="txt-white">

<?php echo Mage::getModel('catalog/product')->load($productId)->getSubtitle(); ?>

</span>

</div>

</div>

<img src="<?php echo Mage::getModel('catalog/product')->load($productId)->getImageUrl(); ?>"

/>

</a>

</div>

USE OF THE LOAD METHOD - EXAMPLE 1

<?php

$productId = Mage::app()->getRequest()->getParam('id');

$product = Mage::getModel(‘catalog/product’)->load($productId);

?>

<div class="product">

<a href="<?php echo $product->getUrl(); ?>">

<div class="rollover">

<div class="rollover-container">

<h5 class="txt-white">

<?php echo $product->getName(); ?>

</h5>

<hr class="bg-white">

<span class="txt-white">

<?php echo $product->getSubtitle(); ?>

</span>

</div>

</div>

<img src="<?php echo $product->getImageUrl(); ?>" />

</a>

</div>

REAL LIFE EXAMPLE

REAL LIFE EXAMPLE

<?php $i=0; foreach ($_productCollection as $_product): ?>

<?php if($_product->getTypeId() == 'configurable'): ?>

<?php $_type = $_product->getTypeInstance(true); ?>

<?php foreach($_type->getUsedProducts(null, $_product) as $_used_product): ?>

<?php

$_size_id = $_used_product->getSize();

$_size_label = $_used_product->getAttributeText(‘size’);

$_sizes[$_size_id] = $_size_label;

?>

<?php endforeach; ?>

<?php krsort($_sizes); ?>

<p><?php echo $this->__('Available in: %s', implode(', ', $_sizes)); ?></p>

<?php endif; ?>

<?php endforeach ?>

REAL LIFE EXAMPLE

With 5 Configurable Products, each with 5 associated simple products

REAL LIFE EXAMPLE

<?php $i=0; foreach ($_productCollection as $_product): ?>

<?php if($_product->getTypeId() == 'configurable'): ?>

<?php foreach($_product->getChildrenProducts() as $_used_product): ?>

<?php

$_size_id = $_used_product->getSize();

$_size_label = $_used_product->getAttributeText(‘size’);

$_sizes[$_size_id] = $_size_label;

?>

<?php endforeach; ?>

<?php krsort($_sizes); ?>

<p><?php echo $this->__('Available in: %s', implode(', ', $_sizes)); ?></p>

<?php endif; ?>

<?php endforeach ?>

REAL LIFE EXAMPLE – CONFIG.XML

<config>

<frontend>

<events>

<catalog_block_product_list_collection>

<observers>

<add_sizes_to_list>

<class>wcm_demo/observer</class>

<method>productListCollectionLoadAfter</method>

</add_sizes_to_list>

</observers>

</catalog_block_product_list_collection>

</events>

</frontend>

</config>

REAL LIFE EXAMPLE – OBSERVER.PHP

public function productListCollectionLoadAfter(Varien_Event_Observer $observer)

{

$products = $observer->getCollection();

$productIds = array();

foreach($products->getItems() as $product) {

$productIds[] = $product->getId();

}

$collection = Mage::getResourceModel(‘catalog/product_type_configurable_product_collection’);

$collection->addStoreFilter(Mage::app()->getStore()->getId())

->addAttributeToSelect(array(‘size’));

$collection->getSelect()->where(‘link_table.parent_id in (?)’, $productIds);

foreach($collection as $childProduct) {

foreach($childProduct->getParentIds() as $parentId) {

if (!isset($mapping[$parentId])) $mapping[$parentId] = array();

$mapping[$parentId][] = $childProduct;

}

}

foreach($mapping as $parentId => $childrenProducts) {

$products[$parentId]->setChildrenProducts($childrenProducts);

}

}

REAL LIFE EXAMPLE

REAL LIFE EXAMPLE

Same output, except in 0.0024 seconds instead of 0.48

An improvement of 19900%

PHP CODE OPTIMISATIONS

• Use single quotes rather than double quotes

• Use === instead of ==

• Disable debugging messages

• Use JSON over XML where possible

• Use isset() over count(), strlen() or sizeof()

• Use if/else statements over switch statements

PHP CODE OPTIMISATIONS

Maintainability > Micro Optimisations

PHP CODE OPTIMISATIONS

“ We should forget about small efficiencies, say about 97% of the

time: premature optimisation is the root of all evil”

Donald Knuth

Profilers

FINDING YOUR PERFORMANCE ISSUES

Xhprof

Built-in

Profiler

Xdebug

Built-in

ProfilerBlackfire.io

FINDING YOUR PERFORMANCE ISSUES

Magento’s built in profiler

FINDING YOUR PERFORMANCE ISSUES

Try Aoe_Profiler (http://bit.ly/aoeprofiler) by Fabrizio Branca

FINDING YOUR PERFORMANCE ISSUES

Only optimize after measuring & profiling

Otherwise you will almost certainly optimize the wrong

things

TAKEAWAYS

• Simple things first

• Focus on quality code

• Consider performance

implications

• Refactor your code

• Focus solely on

performance “buzzwords”

• Cover up performance

issues

• Optimise without

measuring and profiling

DO DON’T

THANK YOU

@lukefowell / @weclickmedia

luke.fowell@weclickmedia.com

top related