How Magento 2 Decides If A Page Is Cacheable

Published: October 20, 2017

Tags:

A high page cache hit rate is one of the most important factors in having a performant Magento 2 site. Unfortunately, it’s very easy to screw up.

I can safely say I've seen issues with cacheable="false" in every store with #Varnish I worked on, mostly in 3rd party extensions.#Magento2 pic.twitter.com/cHoOuFOSlp

— Miguel Balparda (@mbalparda) August 16, 2017

In this post I’ll go into the Magento internals to demonstrate how Magento decides whether or not to cache a response. This is useful to know to help debug in cases where a site is not getting the most bang for buck out of the page cache.

The Moment Of Truth

The decision on whether or not a Magento will instruct the caching backend to save the response is made in Magento\PageCache\Model\Layout\LayoutPlugin::afterGenerateXml(). Below I will paste the full method, including the DocBlock…

/**
 * Set appropriate Cache-Control headers
 * We have to set public headers in order to tell Varnish and Builtin app that page should be cached
 *
 * @param \Magento\Framework\View\Layout $subject
 * @param mixed $result
 * @return mixed
 */
public function afterGenerateXml(\Magento\Framework\View\Layout $subject, $result)
{
    if ($subject->isCacheable() && $this->config->isEnabled()) {
        $this->response->setPublicHeaders($this->config->getTtl());
    }
    return $result;
}

As you can see, after generating the final layout XML Magento will call the isCacheable method on the layout object, if this returns true and caching is enabled, it will set the appropriate response headers to instruct the caching backend to cache the response.

Checking If The Response Is Cacheable

Next up, let’s take a look at how Magento decides if the response is cacheable. This is defined in Magento\Framework\View\Layout::isCacheable

/**
 * Check is exists non-cacheable layout elements
 *
 * @return bool
 */
public function isCacheable()
{
    $this->build();
    $cacheableXml = !(bool)count($this->getXml()->xpath('//' . Element::TYPE_BLOCK . '[@cacheable="false"]'));
    return $this->cacheable && $cacheableXml;
}

As you can see, after build-ing the layout XML, the method checks if the final resulting layout XML contains any blocks with the cacheable attribute set to false. If it finds any, the entire response will not be cached.

This is where a lot of mistakes happen. Developers think that if their block contains private content the “solution” is to set the cacheable attribute to false in the layout XML. In truth, what they’ve done at this point, is not to prevent their block from being cached, but prevented the entire page from being cached for any page using their block. I’ve seen this in the category, product, or even default layout handles.

Varnish’s Say

After Magento\PageCache\Model\Layout\LayoutPlugin sniffs the response for cacheable=false blocks, Varnish gets the final say on whether or not the response will be cached. We can see in the VCL that there are some rules that will prevent the response from being cached.

# We only deal with GET and HEAD by default
if (req.method != "GET" && req.method != "HEAD") {
    return (pass);
}

# Bypass shopping cart, checkout and search requests
if (req.url ~ "/checkout" || req.url ~ "/catalogsearch") {
    return (pass);
}

Saved To The Cache!

If the response passes Magento’s isCacheable check and meets the requirements of the VCL it will make it to the cache.

Hopefully this is happening on your site as often as possible because…

Performance = High Cache Hit Rate + Optimized Uncached Execution

Max ChadwickHi, I'm Max!

I'm a software developer who mainly works in PHP, but also dabbles in Ruby and Go. Technical topics that interest me are monitoring, security and performance.

During the day I solve challenging technical problems at Something Digital where I mainly work with the Magento platform. I also blog about tech, work on open source and hunt for bugs.

If you'd like to get in touch with me the best way is on Twitter.