In my last article on this topic I explained how Magento Enterprise Edition Full Page Cache works, and how to use performance boost it provides at your own controller action. In the second part of this mini article series, I will try to explain concept of Enterprise_PageCache containers used for allowing dynamic behavior for certain blocks, even though page is served from full page cache.
What's nice about Magento Enterprise Edition full page cache is that we get to adjust performance through amount of dynamic content we serve. Additional complexity gained by supporting such mechanisms definitely comes with an overhead, but without some level of dynamic content, powerful eCommerce platform like Magento simply can not work. Idea behind handling dynamic content with Magento Enterprise Edition full page cache is to create class derived from Enterprise_PageCache_Model_Container_Abstract
, for each block that has dynamic content inside. There are two architecturally important methods here:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <?php // app/code/core/Enterprise/PageCache/Model/Container/Abstract.php /* * Function bodies are intentionally empty. Please refer to your copy of * Magento Enterprise Edition source code. */ abstract class Enterprise_PageCache_Model_Container_Abstract { // Other methods public function applyWithoutApp(&$content) { /* * Function bodies are intentionally empty. Please refer to your copy of * Magento Enterprise Edition source code. */ } public function applyInApp(&$content) { /* * Function bodies are intentionally empty. Please refer to your copy of * Magento Enterprise Edition source code. */ } // Other methods } |
Like all WithoutApp
suffixed methods, applyWithoutApp()
is used for fetching FPC hole markup from full page cache storage. This happens very early in the request process, without initializing Magento. More precisely, FPC holes are processed right after main page content has been fetched from full page cache storage. Additionally, role of applyWithoutApp()
function is applying fetched container markup to page content by replacing placeholder strings with actual block content. This also indicates that HTML for areas marked as full page cache holes is being stored inside full page cache storage separately from main page markup.
applyInApp()
function is being called for dynamic blocks content which couldn't be retrieved from full page cache, mostly by instantiating associated block and grabbing content from block's toHtml()
method. This happens with Magento app()
being initialized, therefore having to run applyInApp()
decreases performance to some degree, but not as much as not having the main page content inside full page cache. The described process takes place on a hijacked router that comes with Magento Enterprise Edition, and not inside router that would normally handle this request if FPC wasn't around. Separate router is used because it is required to fill FPC holes without much processing, in most cases without using layout and finish request processing as soon as possible. Additionally, applyInApp()
updates FPC cache entries for blocks it had to instantiate.
Besides implementing applyWithoutApp()
and applyInApp()
for our container, we must inform the system which block is our custom container designed for. This is done trough cache.xml file residing in our module etc
subdirectory. So let's imagine we want to punch a hole for our somenamespace_somename/someblock
, here's an example cache.xml we might use:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?php <!-- app/code/local/Namespace/Name/etc/cache.xml --> <?xml version="1.0" encoding="UTF-8"?> <config> <placeholders> <catalog_navigation> <block>somenamespace_somename/someblock</block> <name>some.name.in.layout</name> <placeholder>SOMEIDENTIFIER</placeholder> <container>Namespace_Name_Model_Enterprise_PageCache_Container_Someblock</container> <cache_lifetime>86400</cache_lifetime> </catalog_navigation> </placeholders> </config> |
Here's brief description of each node from preceding XML code snippet:
- block - grouped class name for block for whom FPC hole is intended. It's used by code triggered at
core_block_abstract_to_html_after
event andapplyInApp()
function, in order to render appropriate block and save it's markup inside full page cache storage. - name - name in layout. This node is optional and when it's given, block will not be treated as full page cache hole if it has other name in layout.
- placeholder - uppercase string used to mark areas with dynamic content inside page markup, when trying to replace block markup using
applyWithoutApp()
orapplyInApp()
functions. - container - name of the class whose
applyWithoutApp()
andapplyInApp()
functions are to be used when dealing with this FPC hole. - cache_lifetime - regardless to caching logic implemented inside our
apply
prefixed functions, our FPC hole will be refreshed by instantiating it's block after this time expires.
If you were diligent enough to come this far, let's connect the dots in order to see how Magento Enterprise Edition full page cache deals with dynamic content. So we have main caching logic implemented more or less inside getPageIdInApp()
and getPageIdWithoutApp()
functions as described in my previous article. The main issue with dynamic content is that main caching logic isn't dynamic enough to handle these Magento blocks, thus we must implement caching logic specific to each dynamic block (something community calls FPC hole). This specific caching logic is expressed through applyWithoutApp()
and applyInApp()
inside container we create for each dynamic block. You've probably realized that if applyWithoutApp()
fails to pull content from FPC storage, applyInApp()
will get called to instantiate block and refresh FPC entry. This is done so we could store and fetch dynamic block content under cache IDs tied to cookies maintained by PageCache module. For example there are cookies handling information like is user logged in, what user ID it has, what user has in his cart and similar, for more turn to Enterprise_PageCache_Model_Cookie
. So whenever these cookies change, all the blocks that are tied to these cookies get refreshed using appropriate applyInApp()
functions on the next page load. Like most brilliant ideas, mechanism used by FPC to handle dynamic content is really simple. The best of all, Enterprise_PageCache comes with numerous FPC holes expressing different caching logics, and it's usually enough to browse through app/code/core/Enterprise/PageCache/Model/Container/*
files to get an idea on how to handle your dynamic content.
Whenever we load page at one of the FPC enabled routes (by default category view, product view, cms, no route) at Magento EE installation, we are inside one of the following three states:
State | Performance gain |
---|---|
Page not in FPC | None |
Page in FPC, not all containers pulled from FPC | Moderate gain |
Page in FPC, all containers pulled from FPC | Significant gain |
So assuming full page cache is freshly enabled, visitors first page load populates desired page cache entry, second visit all FPC hole entries (including ones tied to customer related cookies), and third visit of the same page is fastest due to not requiring Magento initialization. With multiple visitors this first step is usually done for most visited pages, thus customer usually stops placing significant stress to hardware at his second page load, unless he visits non FPC page then. So whatever combination we take, FPC almost always increases performance and decreases server load, and you get the impression that store works faster the more customers are online.
I hope you have enjoyed this article and if you have any questions, feel free to use the comment form bellow. If you didn't quite understand what this article is about, I invite you to first go through one of my older articles explaining what Magento EE full page cache is and how it works. Happy coding.