magento performance issues? deep dive - l fowell.pdfa 1 second delay can equate to.. • 16%...
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