You are on page 1of 25

Understanding Full Page Cache Concept in Magento®

 What is Full Page Cache?


 Explanation of feature with some example.
 Directory Structure & Configuration files required/used?
 Important flow-chart related to FPC.
 Important function list related to FPC.
 Explanation of those important functions.
 How to manage FPC via Admin.
 Known issues for FPC.
 Most common mistake with FPC.
 Important references related to FPC.
 What is the difference between cache and Full Page cache in Magento
What is Full Page Cache?

Magento is a well known open source e-Commerce platform. On other side it is also a complex
pieces of PHP software ever created. When it comes to complexity Magento has relatively low
performance when compared to custom made e-Commerce solutions. In order to improve this
situation, Magento uses the concept of Full Page Cache.

Basically Cache is a component that stores duplicate data that can served faster. Stored data is in
the form of values that have been processed earlier or duplicate of some values that are stored
elsewhere. If a user request some data and that data is already present in cache, this request can
be served by simply reading the cache, which in result is faster serving. Otherwise fetch from its
original storage location which in result is slow.

In general, caching improves response time and reduces load on server to increase page speed.
The pages which takes a second to load now loads in fraction of the time. Most techniques cache
static pages whose contents are rarely changes. However, most e-commerce sites serve dynamic
content, containing personalized information or data that changes more frequently. Caching
dynamic content requires more sophisticated caching techniques, such as those provided by Full
Page Cache (FPC) module for Magento Enterprise Edition.

Full page caching is a way for Magento to load content for a user without having to fully
initialize the application. This makes the entire website fast, but makes it less dynamic. Magento
has built-in ways to achieve a more dynamic page through a process called “hole-punching.”

In Magento, there are three pages which are by default cached.

 Product Listing Pages


 Product Display Pages
 CMS Pages(Homepage)

Explanation of feature with some example.


Following are the features of full page cache
 Supports block updates (hole punching).
 Reduce servers response time.
 Supports multiple currencies.
 Supports multiple stores.
 Supports multiple languages.
 Supports mobile themes.
 Cache management in Magento admin.
 Cache lifetime.

Explanation:

Hole Punch Support In Full Page Cache, we know that the whole content of data stores in
cache. Most technique cache's the static content(that rarely changes) but what, if there is dynamic
content. At this point the concept of hole punching come into the picture.

Reduce Server Response Time After using page cache, reduces load on server which in return
increases the speed of page to load faster.

Multiple Currency Support Supports Multiple currency. Whether you have 1 currency or 100
currencies the page cache will not generate issues and user will always see the expected
currency.

Multiple Store / Language Support As we know Magento supports multiple store views,which
are typically used for different languages. Even after page cache there is no effect of cache on
languages.

Mobile Theme Support Mobile themes, theme overrides, and more are easily supported .
Simply input the required patterns you want to match on (example: iphone|blackberry|android)
and your cache will be set accordingly.

Cache Management in Magento Admin: We can enable or disable page cache in the admin
panel. Even we can clean cache storage through Magento admin.

Cache Lifetime: We can set lifetime of cache. If you set it to false, Zend cache will get default
value of 7200 seconds. If you want never expired cache set it to 9999999999, which is
Zend_Cache maximum value.

Directory Structure & Configuration files required/used?


Important flow-chart related to FPC.
1. Flow chart of magento_full_page_cache_applyInApp.
1. Flow chart of magento_full_page_cache_applyInApp.2. Flow chart
magento_full_page_cache_applyWithoutApp
3. Flow Chart magento_full_page_cache_extractContent
4. Flow chart magento_full_page_cache_processContainers
5. Flow chart magento_full_page_cache_processContent
Important function list related to FPC.
Following tables are affected with target rule.

 extractContent($content)
 processContent($content)
 processContainers($content)
 applyWithoutApp($content)
 applyInApp($content)

Explanation of those important functions.


1]extractContent($content)purpose of the function: This function is uesd to get page content
from cache storage
/**
* Get page content from cache storage
*
* @param string $content
* @return string|false
*/
public function extractContent($content)
{
$cacheInstance = Enterprise_PageCache_Model_Cache::getCacheInstance();
/*
* Apply design change
*/
$designChange = $cacheInstance->load($this->getRequestCacheId() .
self::DESIGN_CHANGE_CACHE_SUFFIX);
if ($designChange) {
$designChange = unserialize($designChange);
if (is_array($designChange) && isset($designChange[\'package\'])
&& isset($designChange[\'theme\'])) {
$designPackage = Mage::getSingleton(\'core/design_package\');
$designPackage->setPackageName($designChange[\'package\'])
->setTheme($designChange[\'theme\']);
}
}

if (!$this->_designExceptionExistsInCache) {
//no design exception value - error
//must be at least empty value
return false;
}
if (!$content && $this->isAllowed()) {
$subprocessorClass = $this->getMetadata(\'cache_subprocessor\');
if (!$subprocessorClass) {
return $content;
}

/*
* @var Enterprise_PageCache_Model_Processor_Default
*/
$subprocessor = new $subprocessorClass;
$this->setSubprocessor($subprocessor);
$cacheId = $this->prepareCacheId($subprocessor-
>getPageIdWithoutApp($this));
$content = $cacheInstance->load($cacheId);

if ($content) {
if (function_exists(\'gzuncompress\')) {
$content = gzuncompress($content);
}
$content = $this->_processContent($content);

// restore response headers


$responseHeaders = $this->getMetadata(\'response_headers\');
if (is_array($responseHeaders)) {
foreach ($responseHeaders as $header) {
Mage::app()->getResponse()-
>setHeader($header[\'name\'], $header[\'value\'], $header[\'replace\']);
}
}

// renew recently viewed products


$productId = $cacheInstance->load($this->getRequestCacheId() .
\'_current_product_id\');
$countLimit = $cacheInstance->load($this-
>getRecentlyViewedCountCacheId());
if ($productId && $countLimit) {

Enterprise_PageCache_Model_Cookie::registerViewedProducts($productId,
$countLimit);
}
}

}
return $content;
}

2]processContent($content)purpose of the function: This function is used to determine and


process all defined containers and it directly request to process action which is in
pagecache/request if necessary for additional processing.
/**
* Determine and process all defined containers.
* Direct request to pagecache/request/process action if necessary for
additional processing
*
* @param string $content
* @return string|false
*/
protected function _processContent($content)
{
$containers = $this->_processContainers($content);
$isProcessed = empty($containers);
// renew session cookie
$sessionInfo = Enterprise_PageCache_Model_Cache::getCacheInstance()-
>load($this->getSessionInfoCacheId());

if ($sessionInfo) {
$sessionInfo = unserialize($sessionInfo);
foreach ($sessionInfo as $cookieName => $cookieInfo) {
if (isset($_COOKIE[$cookieName]) &&
isset($cookieInfo[\'lifetime\'])
&& isset($cookieInfo[\'path\']) &&
isset($cookieInfo[\'domain\'])
&& isset($cookieInfo[\'secure\']) &&
isset($cookieInfo[\'httponly\'])
) {
$lifeTime = (0 == $cookieInfo[\'lifetime\']) ? 0 : time()
+ $cookieInfo[\'lifetime\'];
setcookie($cookieName, $_COOKIE[$cookieName], $lifeTime,
$cookieInfo[\'path\'], $cookieInfo[\'domain\'],
$cookieInfo[\'secure\'], $cookieInfo[\'httponly\']
);
}
}
} else {
$isProcessed = false;
}

if
(isset($_COOKIE[Enterprise_PageCache_Model_Cookie::COOKIE_FORM_KEY])) {
$formKey =
$_COOKIE[Enterprise_PageCache_Model_Cookie::COOKIE_FORM_KEY];
} else {
$formKey = Enterprise_PageCache_Helper_Data::getRandomString(16);

Enterprise_PageCache_Model_Cookie::setFormKeyCookieValue($formKey);
}

Enterprise_PageCache_Helper_Form_Key::restoreFormKey($content,
$formKey);

/**
* restore session_id in content whether content is completely
processed or not
*/
$sidCookieName = $this->getMetadata(\'sid_cookie_name\');
$sidCookieValue = $sidCookieName && isset($_COOKIE[$sidCookieName]) ?
$_COOKIE[$sidCookieName] : \'\';
Enterprise_PageCache_Helper_Url::restoreSid($content,
$sidCookieValue);

if ($isProcessed) {
return $content;
} else {
Mage::register(\'cached_page_content\', $content);
Mage::register(\'cached_page_containers\', $containers);
Mage::app()->getRequest()
->setModuleName(\'pagecache\')
->setControllerName(\'request\')
->setActionName(\'process\')
->isStraight(true);
// restore original routing info
$routingInfo = array(
\'aliases\' => $this-
>getMetadata(\'routing_aliases\'),
\'requested_route\' => $this-
>getMetadata(\'routing_requested_route\'),
\'requested_controller\' => $this-
>getMetadata(\'routing_requested_controller\'),
\'requested_action\' => $this-
>getMetadata(\'routing_requested_action\')
);

Mage::app()->getRequest()->setRoutingInfo($routingInfo);
return false;
}
}

3]processContainers($content) purpose of the function: This function is used to process the


container of Full Page Cache.
/**
* Process Containers
*
* @param $content
* @return array
*/
protected function _processContainers(&$content)
{
$placeholders = array();
preg_match_all(

Enterprise_PageCache_Model_Container_Placeholder::HTML_NAME_PATTERN,
$content, $placeholders, PREG_PATTERN_ORDER
);
$placeholders = array_unique($placeholders[1]);
$containers = array();
foreach ($placeholders as $definition) {
$placeholder = new
Enterprise_PageCache_Model_Container_Placeholder($definition);
$container = $placeholder->getContainerClass();
if (!$container) {
continue;
}

$container = new $container($placeholder);


$container->setProcessor($this);
if (!$container->applyWithoutApp($content)) {
$containers[] = $container;
} else {
preg_match($placeholder->getPattern(), $content, $matches);
if (array_key_exists(1,$matches)) {
$containers = array_merge($this-
>_processContainers($matches[1]), $containers);
$content = preg_replace($placeholder->getPattern(),
str_replace(\'$\', \'\\\\$\', $matches[1]), $content);
}
}
}
return $containers;
}

4]applyInApp($content)purpose of the function: This function is used to generate and apply


container content in contoller after application is intialized.
/**
* Generate and apply container content in controller after application is
initialized
*
* @param string $content
* @return bool
*/
public function applyInApp(&$content)
{
$blockContent = $this->_renderBlock();
if ($blockContent === false) {
return false;
}

if
(Mage::getStoreConfig(Enterprise_PageCache_Model_Processor::XML_PATH_CACHE_DEB
UG)) {
$debugBlock = new Enterprise_PageCache_Block_Debug();
$debugBlock->setDynamicBlockContent($blockContent);
$debugBlock->setTags($this->_getPlaceHolderBlock()-
>getCacheTags());

$debugBlock->setType($this->_placeholder->getName());
$this->_applyToContent($content, $debugBlock->toHtml());
} else {
$this->_applyToContent($content, $blockContent);
}

$subprocessor = $this->_processor->getSubprocessor();
if ($subprocessor) {
$contentWithoutNestedBlocks = $subprocessor-
>replaceContentToPlaceholderReplacer($blockContent);
$this->saveCache($contentWithoutNestedBlocks);
}

return true;
}

5]applyWithoutApp($content)purpose of the function: This function is used to generate


placeholder content before application is initialized and apply to page content if possible.
/**
* Generate placeholder content before application was initialized and apply
to page content if possible
*
* @param string $content
* @return bool
*/
public function applyWithoutApp(&$content)
{
$cacheId = $this->_getCacheId();

if ($cacheId === false) {


$this->_applyToContent($content, \'\');
return true;
}

$block = $this->_loadCache($cacheId);
if ($block === false) {
return false;
}

$block = Enterprise_PageCache_Helper_Url::replaceUenc($block);
$this->_applyToContent($content, $block);
return true;
}

How to manage FPC via Admin.


Following tables are affected with target rule.

 Log in to the Magento Admin Panel as an administrator.


 Click System --> Configuration
 Expand Cache ManagmentIt shows Cache Storage Managment:

For Full page cache Associated tag: FPC


After clearing or refreshing a cache, always refresh your browser to see new changes the
result in your store

1] To refresh Full Page Cache: Sometimes full page cache appears in yellow color that is
“Invalidate” so all time we have to refresh it:

 Log in to the Magento Admin Panel as an administrator.


 Click System > Configuration
 Expand Cache Managment
 click the checkbox of Page Cache
 Select Action 'Refresh'
 Click on submit
Known issues for FPC.

 Currency switching issue:

This issue is found on many sites that the currency not switch. When currency switched,
doesn’t refreshed cache. Users does not see the correct currency on site. Sometimes
product price is not change when switching currency as per the currency. It always
display same currency on site which is saved in full page cache.

 Cart quantity issue:

The Cart Sidebar won't get updated when product is added to cart or it shows different
cart quantity on Homepage and different cart quantity on another pages. It does not
display new or updated cart quantity . Always get quantity of product from cache which
is not hole-punch.

 Welcome container(logIn) issue:

Issue with Welcome message having random names when Logged in. It shows different
online customer name randomly on different page.

Most common mistake with FPC.

How to fix cart quantity issue with Full Page Cache

Before few days client found an issue that the Cart Sidebar won't get updated when product is
added to cart or it shows different cart quantity on Homepage and different cart quantity on
another pages. It does not display new or updated cart quantity.

In cartheader.phtml

Then add following script in your cartheader.phtml


jQuery(document).ready(function(){
jQuery('.cartcount').html('0') ;
})
Important references related to FPC.

 http://stackoverflow.com/questions/9120413/how-do-i-include-a-dynamic-block-in-the-
product-page-with-full-page-caching-turn
 http://magento.stackexchange.com/questions/8481/how-to-hole-punch-a-child-block-
added-inside-catalog-product-list-block
 http://www.pixafy.com/blog/2013/03/overcoming-magentos-full-page-cache-through-
hole-punching/
 http://invisiblezero.net/magento-ee-punch-hole-in-full-page-cache/
 http://www.magentothemess.com/archives/1739
 http://www.developers-paradise.com/wp-content/uploads/2014/03/NRG_-
MDP_Presentation_Max_Gubar_Ibiza_2012.pdf
 http://www.kingletas.com/2012/09/how-does-magento-full-page-cache-works.html
 http://stackoverflow.com/questions/8126548/trying-get-dynamic-content-hole-punched-
through-magentos-full-page-cache
 http://magento.stackexchange.com/questions/13957/full-page-cache-on-ce-1-8-an-fpc-
magento-module-varnish-both

What is the difference between cache and Full Page cache in Magento
Cache:

When a user visiting a website page for the first time, Magento generate and deliver this page to
the visitor and automatically save a copy of it to the cache. Then all those pages get stored in a
cache. Each time when user request arrives, the system does not request Magento to generate a
page, but returns the copy of the page from cache. Neither cache will connect to the Magento
database or load any Magento files ,it can load the request from cache.

Magento block caching depends of three things:


cache_lifetime => cache lifetime in seconds
cache_tags => cache type identifiers primarly used for deleting right cache at
the right time
cache_key => cache identyfier
For example :
protected function _construct()
{
$this->addData(array(
'cache_lifetime' => 3600,
'cache_tags' => array(Mage_Catalog_Model_Product::CACHE_TAG),
'cache_key' => $this->getProduct()->getId(),
));
}

we need some conditional approach because constructor approach isn’t enough in most
scenarios.

public function getCacheKey(){}


public function getCacheLifetime(){}
public function getCacheTags(){}
Full Page Cache:

Full page caching is a way for Magento to load content for a user without having to fully
initialize the application. This makes the entire website fast, but makes it less dynamic. Magento
has built-in ways to achieve a more dynamic page through a process called “hole-punching”.
With FPC complete page is cached and hole punching(container) system where by containers are
replaced with complete html if needed.

Hole-Punching

The page is composed of block and template files. To keep this dynamic relationship active with
FPC on we are given the ability to surround our block and template files with something called a
placeholder, or container.

To retrieve content, containers have two major functions that are important to us:
applyInApp($content) and applyWithoutApp($content).

Magento FPC knows of four states:

Depending on the page, content, and cache lifetimes, Magento will handle each request slightly
different.

1. Page is in cache with no dynamic blocks: Magento did a minimal amount of work to achieve
a page load

2. Page is in cache, dynamic blocks are cached: Similar to State 1, Magento gets a request to
load a webpage and finds the page is cached. Magento then comes across a container for a
dynamic block. From here it determines if the block is cached or not and if so, will proceed to fill
the container with the cached block using applyWithoutApp($content) so we have another full
page load with very minimal overhead.

3. Page is in cache, dynamic blocks are not cached: In state 2 was unable to fetch and deliver
the dynamic block content, so the block content needs to be generated, even though the rest of
the page has been found in the FPC. For this purpose the FPC module sets the request to
pagecache/request/process, and the regular Magento application initialization and routing is
followed. The method fetches the previously instantiated container class for the dynamic block,
and calls applyInApp($content) on it.

4. Page is not in cache: In this state Magento did not find the requested page in the cache, so the
page must be processed as usual.

By Satish Mantri| July 25th, 2014|Articles, Blogs, Magento, Oscp-Blog|0 Comments

You might also like